diff --git a/.gitignore b/.gitignore index 65556df299..f86f68b748 100644 --- a/.gitignore +++ b/.gitignore @@ -37,10 +37,10 @@ project.lock.json *.CppClean.log *msbuild.log -src/*/Debug/ -src/*/x64/Debug/ -src/*/Release/ -src/*/x64/Release/ +src/*/*/Debug/ +src/*/*/x64/Debug/ +src/*/*/Release/ +src/*/*/x64/Release/ x64/ *vcxproj.filters @@ -49,12 +49,13 @@ x64/ *.lib *.idb -src/AspNetCore/aspnetcore_msg.h -src/AspNetCore/aspnetcore_msg.rc -src/AspNetCore/version.h -src/RequestHandler/version.h -src/CommonLib/aspnetcore_msg.h -src/CommonLib/aspnetcore_msg.rc +src/*/AspNetCore/aspnetcoremodule.h +src/*/AspNetCore/aspnetcore_msg.h +src/*/AspNetCore/aspnetcore_msg.rc +src/*/*/version.h +src/*/RequestHandler/version.h +src/*/CommonLib/aspnetcore_msg.h +src/*/CommonLib/aspnetcore_msg.rc test/*/Debug test/*/Release test/gtest-1.8.0/msvc/Debug diff --git a/IISIntegration.sln b/IISIntegration.sln index 3cefc93200..9875c3523a 100644 --- a/IISIntegration.sln +++ b/IISIntegration.sln @@ -60,14 +60,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProcessWebSite", "test\We {439824F9-1455-4CC4-BD79-B44FA0A16552} = {439824F9-1455-4CC4-BD79-B44FA0A16552} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\IISLib\IISLib.vcxproj", "{4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "src\CommonLib\CommonLib.vcxproj", "{55494E58-E061-4C4C-A0A8-837008E72F85}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RequestHandler", "src\RequestHandler\RequestHandler.vcxproj", "{D57EA297-6DC2-4BC0-8C91-334863327863}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IIS", "src\Microsoft.AspNetCore.Server.IIS\Microsoft.AspNetCore.Server.IIS.csproj", "{46A8612B-418B-4D70-B3A7-A21DD0627473}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StressTestWebSite", "test\WebSites\StressTestWebSite\StressTestWebSite.csproj", "{13FD8F12-FFBE-4D01-B4AC-444F2994B04F}" @@ -86,6 +78,22 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLibTests", "test\Comm EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gtest", "test\gtest-1.8.0\msvc\gtest.vcxproj", "{2AF210A9-5BDC-45E8-95DD-07B5A2616493}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCoreModuleV1", "AspNetCoreModuleV1", "{16E521CE-77F1-4B1C-A183-520A41C4F372}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCoreModuleV2", "AspNetCoreModuleV2", "{06CA2C2B-83B0-4D83-905A-E0C74790009E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\AspNetCoreModuleV1\IISLib\IISLib.vcxproj", "{4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCoreModuleV1\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCoreModuleV2\AspNetCore\AspNetCore.vcxproj", "{EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "src\AspNetCoreModuleV2\CommonLib\CommonLib.vcxproj", "{55494E58-E061-4C4C-A0A8-837008E72F85}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\AspNetCoreModuleV2\IISLib\IISLib.vcxproj", "{09D9D1D6-2951-4E14-BC35-76A23CF9391A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RequestHandler", "src\AspNetCoreModuleV2\RequestHandler\RequestHandler.vcxproj", "{D57EA297-6DC2-4BC0-8C91-334863327863}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -176,46 +184,6 @@ Global {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x64.Build.0 = Release|x64 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x86.ActiveCfg = Release|x86 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x86.Build.0 = Release|x86 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Any CPU.ActiveCfg = Debug|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}.Debug|x86.ActiveCfg = Debug|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x86.Build.0 = Debug|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Any CPU.ActiveCfg = Release|x64 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|x64 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|x64 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x86.ActiveCfg = Release|Win32 - {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x86.Build.0 = Release|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Any CPU.ActiveCfg = Debug|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}.Debug|x86.ActiveCfg = Debug|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x86.Build.0 = Debug|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Any CPU.ActiveCfg = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|x64 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x86.ActiveCfg = Release|Win32 - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x86.Build.0 = Release|Win32 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Any CPU.ActiveCfg = Debug|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.ActiveCfg = Debug|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.Build.0 = Debug|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.ActiveCfg = Debug|Win32 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.Build.0 = Debug|Win32 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Any CPU.ActiveCfg = Release|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.ActiveCfg = Release|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.Build.0 = Release|x64 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.ActiveCfg = Release|Win32 - {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.Build.0 = Release|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.ActiveCfg = Debug|x64 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.Build.0 = Debug|x64 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.ActiveCfg = Debug|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.Build.0 = Debug|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Any CPU.ActiveCfg = Release|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.ActiveCfg = Release|x64 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.Build.0 = Release|x64 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.ActiveCfg = Release|Win32 - {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.Build.0 = Release|Win32 {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|Any CPU.Build.0 = Debug|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -260,6 +228,86 @@ Global {FC2A97F8-A749-4C04-97D1-97500066A820}.Release|x64.Build.0 = Release|x64 {FC2A97F8-A749-4C04-97D1-97500066A820}.Release|x86.ActiveCfg = Release|x86 {FC2A97F8-A749-4C04-97D1-97500066A820}.Release|x86.Build.0 = Release|x86 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x64.ActiveCfg = Debug|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x64.Build.0 = Debug|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x86.ActiveCfg = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x86.Build.0 = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|Any CPU.ActiveCfg = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x64.ActiveCfg = Release|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x64.Build.0 = Release|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x86.ActiveCfg = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x86.Build.0 = Release|Win32 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Debug|x64.ActiveCfg = Debug|x64 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Debug|x64.Build.0 = Debug|x64 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Debug|x86.ActiveCfg = Debug|Win32 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Debug|x86.Build.0 = Debug|Win32 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Release|Any CPU.ActiveCfg = Release|Win32 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Release|x64.ActiveCfg = Release|x64 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Release|x64.Build.0 = Release|x64 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Release|x86.ActiveCfg = Release|Win32 + {2AF210A9-5BDC-45E8-95DD-07B5A2616493}.Release|x86.Build.0 = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.ActiveCfg = Debug|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.Build.0 = Debug|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x86.ActiveCfg = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x86.Build.0 = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Any CPU.ActiveCfg = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x86.ActiveCfg = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x86.Build.0 = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.ActiveCfg = Debug|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.Build.0 = Debug|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x86.ActiveCfg = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x86.Build.0 = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Any CPU.ActiveCfg = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x86.ActiveCfg = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x86.Build.0 = Release|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x64.ActiveCfg = Debug|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x64.Build.0 = Debug|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x86.ActiveCfg = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x86.Build.0 = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|Any CPU.ActiveCfg = Release|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x64.ActiveCfg = Release|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x64.Build.0 = Release|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x86.ActiveCfg = Release|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x86.Build.0 = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.ActiveCfg = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.Build.0 = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.Build.0 = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Any CPU.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.ActiveCfg = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.Build.0 = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.Build.0 = Release|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x64.ActiveCfg = Debug|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x64.Build.0 = Debug|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x86.ActiveCfg = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x86.Build.0 = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|Any CPU.ActiveCfg = Release|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x64.ActiveCfg = Release|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x64.Build.0 = Release|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x86.ActiveCfg = Release|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x86.Build.0 = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.ActiveCfg = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.Build.0 = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.Build.0 = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Any CPU.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.ActiveCfg = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.Build.0 = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -272,15 +320,21 @@ Global {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {9BC4AFCB-325D-4C81-8228-8CF301CE2F97} = {C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9} {679FA2A2-898B-4320-884E-C2D294A97CE1} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} - {439824F9-1455-4CC4-BD79-B44FA0A16552} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} - {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} - {55494E58-E061-4C4C-A0A8-837008E72F85} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} - {D57EA297-6DC2-4BC0-8C91-334863327863} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} {46A8612B-418B-4D70-B3A7-A21DD0627473} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} {13FD8F12-FFBE-4D01-B4AC-444F2994B04F} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} {064D860B-4D7C-4B1D-918F-E020F1B99E2A} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {FC2A97F8-A749-4C04-97D1-97500066A820} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {2AF210A9-5BDC-45E8-95DD-07B5A2616493} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {16E521CE-77F1-4B1C-A183-520A41C4F372} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {06CA2C2B-83B0-4D83-905A-E0C74790009E} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {16E521CE-77F1-4B1C-A183-520A41C4F372} + {439824F9-1455-4CC4-BD79-B44FA0A16552} = {16E521CE-77F1-4B1C-A183-520A41C4F372} + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {55494E58-E061-4C4C-A0A8-837008E72F85} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {09D9D1D6-2951-4E14-BC35-76A23CF9391A} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {D57EA297-6DC2-4BC0-8C91-334863327863} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5} diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index ceddc22350..eea701bb65 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -2,7 +2,8 @@ "adx-nonshipping": { "rules": [], "packages": { - "Microsoft.AspNetCore.AspNetCoreModule": {} + "Microsoft.AspNetCore.AspNetCoreModule": {}, + "Microsoft.AspNetCore.AspNetCoreModuleV1": {} } }, "Default": { diff --git a/build/build.msbuild b/build/build.msbuild index 4a35e31288..55ed0be07c 100644 --- a/build/build.msbuild +++ b/build/build.msbuild @@ -2,8 +2,9 @@ - - + + + diff --git a/build/repo.targets b/build/repo.targets index dccb8b0f28..42969acbad 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -20,9 +20,11 @@ Text="Could not find an installation of Visual Studio with the C++ development tools." Condition="'$(VisualStudioMSBuildx86Path)' == ''" /> - - + @@ -37,6 +39,14 @@ + + NuGetPackage + Microsoft.AspNetCore.AspNetCoreModuleV1 + $(PackageVersion) + $(RepositoryRoot) + + + ZipArchive $(RepositoryRoot) @@ -44,14 +54,22 @@ - - - - + + + + + + - + + - - - - + + + + + + - - - - + + + + + + - - + + + $(NativePlatform)\ + - - - - - + + + + + + + + + @@ -56,7 +61,7 @@ - + $(MSBuildThisFileDirectory)..\test\TestTasks\bin\$(Configuration)\$(TargetFramework)\TestTasks @@ -76,11 +81,11 @@ - + - + diff --git a/nuget/AspNetCore.nuspec b/nuget/AspNetCoreV1.nuspec similarity index 58% rename from nuget/AspNetCore.nuspec rename to nuget/AspNetCoreV1.nuspec index 9da2714f9d..f89b7ff754 100644 --- a/nuget/AspNetCore.nuspec +++ b/nuget/AspNetCoreV1.nuspec @@ -1,7 +1,7 @@ - + - Microsoft.AspNetCore.AspNetCoreModule + Microsoft.AspNetCore.AspNetCoreModuleV1 Microsoft ASP.NET Core Module $VERSION$ Microsoft @@ -13,17 +13,15 @@ true ASP.NET Core Module en-US - Microsoft.AspNetCore.AspNetCoreModule + Microsoft.AspNetCore.AspNetCoreModuleV1 - - - - - + + + diff --git a/nuget/AspNetCoreV2.nuspec b/nuget/AspNetCoreV2.nuspec new file mode 100644 index 0000000000..10745f52c4 --- /dev/null +++ b/nuget/AspNetCoreV2.nuspec @@ -0,0 +1,31 @@ + + + + Microsoft.AspNetCore.AspNetCoreModule + Microsoft ASP.NET Core Module + $VERSION$ + Microsoft + Microsoft + https://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm + © .NET Foundation. All rights reserved. + https://www.asp.net/ + https://go.microsoft.com/fwlink/?LinkID=288859 + true + ASP.NET Core Module + en-US + Microsoft.AspNetCore.AspNetCoreModule + + + + + + + + + + + + + + + diff --git a/src/AspNetCoreModuleV1/AspNetCore/AspNetCore.vcxproj b/src/AspNetCoreModuleV1/AspNetCore/AspNetCore.vcxproj new file mode 100644 index 0000000000..e1c11dddb6 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/AspNetCore.vcxproj @@ -0,0 +1,284 @@ + + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {439824F9-1455-4CC4-BD79-B44FA0A16552} + Win32Proj + AspNetCoreModule + AspNetCore + aspnetcore + false + 10.0.15063.0 + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + + NotUsing + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + $(IntDir)$(TargetName).pch + ..\IISLib;inc\ + ProgramDatabase + MultiThreadedDebug + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + true + kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + Source.def + + + ..\Commonlib + + + + + NotUsing + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + $(IntDir)$(TargetName).pch + ..\IISLib;inc\ + ProgramDatabase + MultiThreadedDebug + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + true + kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + Source.def + + + ..\Commonlib + + + + + Level4 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + ..\IISLib;inc\ + precomp.hxx + MultiThreaded + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + false + true + true + Source.def + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;winhttp.lib;odbc32.lib;ws2_32.lib;odbccp32.lib;wbemuuid.lib;iphlpapi.lib;pdh.lib;rpcrt4.lib;%(AdditionalDependencies) + + + ..\Commonlib + + + + + Level4 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) + precomp.hxx + ..\IISLib;inc\ + MultiThreaded + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + + + Windows + false + true + true + Source.def + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + + + ..\Commonlib + + + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + + + + + + + + + Document + mc %(FullPath) + Compiling Event Messages ... + %(Filename).rc;%(Filename).h;MSG0409.bin + + + \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/application.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/application.h new file mode 100644 index 0000000000..e0ef14ec9c --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/application.h @@ -0,0 +1,293 @@ +// 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 = TRUE; + LARGE_INTEGER li = {0}; + CHAR *pszBuff = NULL; + HANDLE handle = INVALID_HANDLE_VALUE; + + handle = CreateFile( m_Path.QueryStr(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL ); + + if( handle == INVALID_HANDLE_VALUE ) + { + if ( GetLastError() == ERROR_FILE_NOT_FOUND ) + { + fResult = FALSE; + } + + // This Load() member function is supposed be called only when the change notification event of file creation or file modification happens. + // If file is currenlty locked exclusively by other processes, we might get INVALID_HANDLE_VALUE even though the file exists. In that case, we should return TRUE here. + goto Finished; + } + + if(!GetFileSizeEx( handle, &li )) + { + goto Finished; + } + + if( li.HighPart != 0 ) + { + // > 4gb file size not supported + // todo: log a warning at event log + goto Finished; + } + + DWORD bytesRead = 0; + + if(li.LowPart > 0) + { + pszBuff = new CHAR[ li.LowPart + 1 ]; + + if( ReadFile( handle, pszBuff, li.LowPart, &bytesRead, NULL ) ) + { + m_Contents.Copy( pszBuff, bytesRead ); + } + } + +Finished: + if( handle != INVALID_HANDLE_VALUE ) + { + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + } + + if( pszBuff != NULL ) + { + delete[] pszBuff; + pszBuff = NULL; + } + + return fResult; + } + + mutable LONG m_cRefs; + STRA m_Contents; + STRU m_Path; +}; + +class APPLICATION_MANAGER; + +class APPLICATION +{ +public: + + APPLICATION() : m_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; + } + + ~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/AspNetCoreModuleV1/AspNetCore/Inc/applicationmanager.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/applicationmanager.h new file mode 100644 index 0000000000..d9e626262d --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/applicationmanager.h @@ -0,0 +1,158 @@ +// 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, + _Out_ APPLICATION ** ppApplication + ); + + 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; + } + + if(m_pHttp502ErrorPage != NULL) + { + delete m_pHttp502ErrorPage; + m_pHttp502ErrorPage = 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: \ + https://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; +}; diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcoreconfig.h new file mode 100644 index 0000000000..95b4303cee --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcoreconfig.h @@ -0,0 +1,207 @@ +// 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_WINDOWS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/windowsAuthentication" +#define CS_BASIC_AUTHENTICATION_SECTION L"system.webServer/security/authentication/basicAuthentication" +#define CS_ANONYMOUS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/anonymousAuthentication" +#define CS_AUTHENTICATION_ENABLED L"enabled" +#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath" +#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments" +#define CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT L"startupTimeLimit" +#define CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT L"shutdownTimeLimit" +#define CS_ASPNETCORE_WINHTTP_REQUEST_TIMEOUT L"requestTimeout" +#define CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE L"rapidFailsPerMinute" +#define CS_ASPNETCORE_STDOUT_LOG_ENABLED L"stdoutLogEnabled" +#define CS_ASPNETCORE_STDOUT_LOG_FILE L"stdoutLogFile" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLES L"environmentVariables" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE L"environmentVariable" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME L"name" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE L"value" +#define CS_ASPNETCORE_PROCESSES_PER_APPLICATION L"processesPerApplication" +#define CS_ASPNETCORE_FORWARD_WINDOWS_AUTH_TOKEN L"forwardWindowsAuthToken" +#define CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE L"disableStartUpErrorPage" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE L"recycleOnFileChange" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE L"file" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE_PATH L"path" + +#define 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 + ); + + ENVIRONMENT_VAR_HASH* + QueryEnvironmentVariables( + VOID + ) + { + return m_pEnvironmentVariables; + } + + DWORD + QueryRapidFailsPerMinute( + VOID + ) + { + return m_dwRapidFailsPerMinute; + } + + DWORD + QueryStartupTimeLimitInMS( + VOID + ) + { + return m_dwStartupTimeLimitInMS; + } + + DWORD + QueryShutdownTimeLimitInMS( + VOID + ) + { + return m_dwShutdownTimeLimitInMS; + } + + DWORD + QueryProcessesPerApplication( + VOID + ) + { + return m_dwProcessesPerApplication; + } + + DWORD + QueryRequestTimeoutInMS( + VOID + ) + { + return m_dwRequestTimeoutInMS; + } + + STRU* + QueryArguments( + VOID + ) + { + return &m_struArguments; + } + + STRU* + QueryApplicationPath( + VOID + ) + { + return &m_struApplication; + } + + STRU* + QueryProcessPath( + VOID + ) + { + return &m_struProcessPath; + } + + BOOL + QueryStdoutLogEnabled() + { + return m_fStdoutLogEnabled; + } + + BOOL + QueryForwardWindowsAuthToken() + { + return m_fForwardWindowsAuthToken; + } + + BOOL + QueryWindowsAuthEnabled() + { + return m_fWindowsAuthEnabled; + } + + BOOL + QueryBasicAuthEnabled() + { + return m_fBasicAuthEnabled; + } + + BOOL + QueryAnonymousAuthEnabled() + { + return m_fAnonymousAuthEnabled; + } + + BOOL + QueryDisableStartUpErrorPage() + { + return m_fDisableStartUpErrorPage; + } + + STRU* + QueryStdoutLogFile() + { + return &m_struStdoutLogFile; + } + +private: + + // + // private constructor + // + ASPNETCORE_CONFIG(): + m_fStdoutLogEnabled( FALSE ), + m_pEnvironmentVariables( NULL ) + { + } + + HRESULT + Populate( + IHttpContext *pHttpContext + ); + + 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/AspNetCoreModuleV1/AspNetCore/Inc/debugutil.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/debugutil.h new file mode 100644 index 0000000000..7378462efb --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/Inc/environmentvariablehash.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/environmentvariablehash.h new file mode 100644 index 0000000000..062090ac17 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/environmentvariablehash.h @@ -0,0 +1,155 @@ +// 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 ENVIRONMENT_VAR_ENTRY +{ +public: + ENVIRONMENT_VAR_ENTRY(): + _cRefs(1) + { + } + + HRESULT + Initialize( + PCWSTR pszName, + PCWSTR pszValue + ) + { + HRESULT hr = S_OK; + if (FAILED(hr = _strName.Copy(pszName)) || + FAILED(hr = _strValue.Copy(pszValue))) + { + } + return hr; + } + + VOID + Reference() const + { + InterlockedIncrement(&_cRefs); + } + + VOID + Dereference() const + { + if (InterlockedDecrement(&_cRefs) == 0) + { + delete this; + } + } + + PWSTR const + QueryName() + { + return _strName.QueryStr(); + } + + PWSTR const + QueryValue() + { + return _strValue.QueryStr(); + } + +private: + ~ENVIRONMENT_VAR_ENTRY() + { + } + + STRU _strName; + STRU _strValue; + mutable LONG _cRefs; +}; + +class ENVIRONMENT_VAR_HASH : public HASH_TABLE +{ +public: + ENVIRONMENT_VAR_HASH() + {} + + PWSTR + ExtractKey( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + return pEntry->QueryName(); + } + + DWORD + CalcKeyHash( + PWSTR pszName + ) + { + return HashStringNoCase(pszName); + } + + BOOL + EqualKeys( + PWSTR pszName1, + PWSTR pszName2 + ) + { + return (_wcsicmp(pszName1, pszName2) == 0); + } + + VOID + ReferenceRecord( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + pEntry->Reference(); + } + + VOID + DereferenceRecord( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + pEntry->Dereference(); + } + + static + VOID + CopyToMultiSz( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + STRU strTemp; + MULTISZ *pMultiSz = static_cast(pvData); + DBG_ASSERT(pMultiSz); + DBG_ASSERT(pEntry); + strTemp.Copy(pEntry->QueryName()); + strTemp.Append(pEntry->QueryValue()); + pMultiSz->Append(strTemp.QueryStr()); + } + + static + VOID + CopyToTable( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + // best effort copy, ignore the failure + ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pNewEntry != NULL) + { + pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue()); + ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); + DBG_ASSERT(pHash); + pHash->InsertRecord(pNewEntry); + // Need to dereference as InsertRecord references it now + pNewEntry->Dereference(); + } + } + +private: + ENVIRONMENT_VAR_HASH(const ENVIRONMENT_VAR_HASH &); + void operator=(const ENVIRONMENT_VAR_HASH &); +}; diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/filewatcher.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/filewatcher.h new file mode 100644 index 0000000000..16d3942a2f --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/filewatcher.h @@ -0,0 +1,126 @@ +// 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) +#define FILE_WATCHER_ENTRY_BUFFER_SIZE 4096 +#ifndef CONTAINING_RECORD +// +// Calculate the address of the base of the structure given its type, and an +// address of a field within the structure. +// + +#define CONTAINING_RECORD(address, type, field) \ + ((type *)((PCHAR)(address)-(ULONG_PTR)(&((type *)0)->field))) + +#endif // !CONTAINING_RECORD +#define FILE_NOTIFY_VALID_MASK 0x00000fff +#define FILE_WATCHER_ENTRY_SIGNATURE ((DWORD) 'FWES') +#define FILE_WATCHER_ENTRY_SIGNATURE_FREE ((DWORD) 'sewf') + +class APPLICATION; + +class FILE_WATCHER{ +public: + + FILE_WATCHER(); + + ~FILE_WATCHER(); + + HRESULT Create(); + + HANDLE + QueryCompletionPort( + VOID + ) const + { + return m_hCompletionPort; + } + + static + DWORD + WINAPI ChangeNotificationThread(LPVOID); + + static + void + WINAPI FileWatcherCompletionRoutine + ( + DWORD dwCompletionStatus, + DWORD cbCompletion, + OVERLAPPED * pOverlapped + ); + +private: + HANDLE m_hCompletionPort; + HANDLE m_hChangeNotificationThread; +}; + +class FILE_WATCHER_ENTRY +{ +public: + FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor); + + OVERLAPPED _overlapped; + + HRESULT + Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ APPLICATION* pApplication, + _In_ HANDLE hImpersonationToken + ); + + VOID + ReferenceFileWatcherEntry() const + { + InterlockedIncrement(&_cRefs); + } + + VOID + DereferenceFileWatcherEntry() const + { + if (InterlockedDecrement(&_cRefs) == 0) + { + delete this; + } + } + + BOOL + QueryIsValid() const + { + return _fIsValid; + } + + VOID + MarkEntryInValid() + { + _fIsValid = FALSE; + } + + HRESULT Monitor(); + + VOID StopMonitor(); + + HRESULT + HandleChangeCompletion( + _In_ DWORD dwCompletionStatus, + _In_ DWORD cbCompletion + ); + +private: + virtual ~FILE_WATCHER_ENTRY(); + + DWORD _dwSignature; + BUFFER _buffDirectoryChanges; + HANDLE _hImpersonationToken; + HANDLE _hDirectory; + FILE_WATCHER* _pFileMonitor; + APPLICATION* _pApplication; + STRU _strFileName; + STRU _strDirectoryName; + LONG _lStopMonitorCalled; + mutable LONG _cRefs; + BOOL _fIsValid; + SRWLOCK _srwLock; +}; diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/forwarderconnection.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/forwarderconnection.h new file mode 100644 index 0000000000..a3f5dfdabe --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/forwardinghandler.h new file mode 100644 index 0000000000..dcd8531dac --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/forwardinghandler.h @@ -0,0 +1,468 @@ +// 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 "forwarderconnection.h" +#include "protocolconfig.h" +#include "serverprocess.h" +#include "application.h" +#include "tracelog.h" +#include "websockethandler.h" + +#define ASPNETCORE_DEBUG_STRU_BUFFER_SIZE 100 +#define ASPNETCORE_DEBUG_STRU_ARRAY_SIZE 100 + +enum FORWARDING_REQUEST_STATUS +{ + FORWARDER_START, + FORWARDER_SENDING_REQUEST, + FORWARDER_RECEIVING_RESPONSE, + FORWARDER_RECEIVED_WEBSOCKET_RESPONSE, + FORWARDER_RESET_CONNECTION, + FORWARDER_DONE +}; + +extern HTTP_MODULE_ID g_pModuleId; +extern IHttpServer * g_pHttpServer; +extern BOOL g_fAsyncDisconnectAvailable; +extern PCWSTR g_pszModuleName; +extern HMODULE g_hModule; +extern HMODULE g_hWinHttpModule; +extern DWORD g_dwTlsIndex; +extern DWORD g_OptionalWinHttpFlags; + +#ifdef DEBUG +extern STRA g_strLogs[ASPNETCORE_DEBUG_STRU_ARRAY_SIZE]; +extern DWORD g_dwLogCounter; +#endif // DEBUG + +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)); + if (pThis == NULL) + { + //error happened, nothing can be done here + return; + } + DBG_ASSERT(pThis->m_Signature == FORWARDING_HANDLER_SIGNATURE); + pThis->OnWinHttpCompletionInternal(hRequest, + dwInternetStatus, + lpvStatusInformation, + dwStatusInformationLength); + } + + static + HRESULT + StaticInitialize( + BOOL fEnableReferenceCountTracing + ); + + static + VOID + StaticTerminate(); + + static + PCWSTR + QueryErrorFormat() + { + return sm_strErrorFormat.QueryStr(); + } + + static + HANDLE + QueryEventLog() + { + return sm_hEventLog; + } + + VOID + TerminateRequest( + BOOL fClientInitiated + ); + + static HINTERNET sm_hSession; + + HRESULT + SetStatusAndHeaders( + PCSTR pszHeaders, + DWORD cchHeaders + ); + + HRESULT + OnSharedRequestEntity( + ULONGLONG ulOffset, + LPCBYTE pvBuffer, + DWORD cbBuffer + ); + + VOID + SetStatus( + FORWARDING_REQUEST_STATUS status + ) + { + m_RequestStatus = status; + } + + virtual + ~FORWARDING_HANDLER( + VOID + ); + +private: + + // + // Begin OnMapRequestHandler phases. + // + + HRESULT + CreateWinHttpRequest( + __in const IHttpRequest * pRequest, + __in const PROTOCOL_CONFIG * pProtocol, + __in HINTERNET hConnect, + __inout STRU * pstrUrl, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess + ); + + // + // End OnMapRequestHandler phases. + // + + VOID + RemoveRequest(); + + HRESULT + GetHeaders( + const PROTOCOL_CONFIG * pProtocol, + PCWSTR * ppszHeaders, + DWORD * pcchHeaders, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess + ); + + HRESULT + DoReverseRewrite( + __in IHttpResponse *pResponse + ); + + BYTE * + GetNewResponseBuffer( + DWORD dwBufferSize + ); + + VOID + FreeResponseBuffers(); + + VOID + OnWinHttpCompletionInternal( + HINTERNET hRequest, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength + ); + + HRESULT + OnWinHttpCompletionSendRequestOrWriteComplete( + HINTERNET hRequest, + DWORD dwInternetStatus, + __out BOOL * pfClientError, + __out BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusHeadersAvailable( + HINTERNET hRequest, + __out BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusDataAvailable( + HINTERNET hRequest, + DWORD dwBytes, + __out BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusReadComplete( + __in IHttpResponse * pResponse, + DWORD dwStatusInformationLength, + __out BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnSendingRequest( + DWORD cbCompletion, + HRESULT hrCompletionStatus, + __out BOOL * pfClientError + ); + + HRESULT + OnReceivingResponse(); + + HRESULT + OnWebSocketWinHttpSendComplete( + HINTERNET hRequest, + LPVOID pvStatus, + DWORD hrCompletion, + DWORD cbCompletion, + BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWebSocketWinHttpReceiveComplete( + HINTERNET hRequest, + LPVOID pvStatus, + DWORD hrCompletion, + DWORD cbCompletion, + BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWebSocketIisSendComplete( + DWORD hrCompletion, + DWORD cbCompletion + ); + + HRESULT + OnWebSocketIisReceiveComplete( + DWORD hrCompletion, + DWORD cbCompletion + ); + + HRESULT + DoIisWebSocketReceive( + VOID + ); + + VOID + TerminateWebsocket( + VOID + ); + + DWORD m_Signature; + mutable LONG m_cRefs; + + IHttpContext * m_pW3Context; + + // + // 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_fResponseHeadersReceivedAndSet; + volatile BOOL m_fClientDisconnected; + // + // A safety guard flag indicating no more IIS PostCompletion is allowed + // + volatile BOOL m_fFinishRequest; + // + // A safety guard flag to prevent from unexpect callback which may signal IIS pipeline + // more than once with non-pending status + // + volatile BOOL m_fDoneAsyncCompletion; + volatile BOOL m_fHasError; + // + // WinHttp may hit AV under race if handle got closed more than once simultaneously + // Use two bool variables to guard + // + volatile BOOL m_fHttpHandleInClose; + volatile BOOL m_fWebSocketHandleInClose; + // + // Record the number of winhttp handles in use + // release IIS pipeline only after all handles got closed + // + volatile LONG m_dwHandlers; + + BOOL m_fDoReverseRewriteHeaders; + BOOL m_fServerResetConn; + 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; + + volatile 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/AspNetCoreModuleV1/AspNetCore/Inc/path.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/path.h new file mode 100644 index 0000000000..05545acfd5 --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/Inc/processmanager.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/processmanager.h new file mode 100644 index 0000000000..b91e8e6bfb --- /dev/null +++ b/src/AspNetCoreModuleV1/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]->SendSignal(); + m_ppServerProcessList[i]->DereferenceServerProcess(); + m_ppServerProcessList[i] = NULL; + } + } + } + + volatile LONG m_cRapidFailCount; + DWORD m_dwRapidFailTickStart; + DWORD m_dwProcessesPerApplication; + volatile DWORD m_dwRouteToProcessIndex; + + SRWLOCK m_srwLock; + SERVER_PROCESS **m_ppServerProcessList; + + // + // m_hNULHandle is used to redirect stdout/stderr to NUL. + // If Createprocess is called to launch a batch file for example, + // it tries to write to the console buffer by default. It fails to + // start if the console buffer is owned by the parent process i.e + // in our case w3wp.exe. So we have to redirect the stdout/stderr + // of the child process to NUL or to a file (anything other than + // the console buffer of the parent process). + // + + HANDLE m_hNULHandle; + mutable LONG m_cRefs; + + volatile static BOOL sm_fWSAStartupDone; + volatile BOOL m_fServerProcessListReady; +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/protocolconfig.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/protocolconfig.h new file mode 100644 index 0000000000..d9d730c544 --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/Inc/proxymodule.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/proxymodule.h new file mode 100644 index 0000000000..1adbcffae8 --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/Inc/resource.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h new file mode 100644 index 0000000000..64dde656b0 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h @@ -0,0 +1,20 @@ +// 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"Application '%s' started process '%d' successfully and is listening on port '%d'." +#define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded." +#define ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG L"Application '%s' failed to parse processPath and arguments due to internal error, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s'but failed to get its status, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s', ErrorCode = '0x%x : %x." +#define ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but failed to listen on the given port '%d'" +#define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but either crashed or did not reponse or did not listen on the given port '%d', ErrorCode = '0x%x'" +#define ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG L"Warning: Could not create stdoutLogFile %s, ErrorCode = %d." +#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'." +#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'." +#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"App_offline file '%s' was detected." diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/responseheaderhash.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/responseheaderhash.h new file mode 100644 index 0000000000..7ef127366b --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h new file mode 100644 index 0000000000..4d6bf6c145 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.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. + +#pragma once + +#define MIN_PORT 1025 +#define MAX_PORT 48000 +#define MAX_RETRY 10 +#define MAX_ACTIVE_CHILD_PROCESSES 16 +#define LOCALHOST "127.0.0.1" +#define ASPNETCORE_PORT_STR L"ASPNETCORE_PORT" +#define ASPNETCORE_PORT_ENV_STR L"ASPNETCORE_PORT=" +#define ASPNETCORE_APP_PATH_ENV_STR L"ASPNETCORE_APPL_PATH=" +#define ASPNETCORE_APP_TOKEN_ENV_STR L"ASPNETCORE_TOKEN=" +#define ASPNETCORE_APP_PATH_ENV_STR L"ASPNETCORE_APPL_PATH=" +#define HOSTING_STARTUP_ASSEMBLIES_ENV_STR L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES" +#define HOSTING_STARTUP_ASSEMBLIES_NAME L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=" +#define HOSTING_STARTUP_ASSEMBLIES_VALUE L"Microsoft.AspNetCore.Server.IISIntegration" +#define ASPNETCORE_IIS_AUTH_ENV_STR L"ASPNETCORE_IIS_HTTPAUTH=" +#define ASPNETCORE_IIS_AUTH_WINDOWS L"windows;" +#define ASPNETCORE_IIS_AUTH_BASIC L"basic;" +#define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;" +#define ASPNETCORE_IIS_AUTH_NONE L"none" + +class PROCESS_MANAGER; +class 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_ BOOL fWindowsAuthEnabled, + _In_ BOOL fBasicAuthEnabled, + _In_ BOOL fAnonymousAuthEnabled, + _In_ ENVIRONMENT_VAR_HASH* pEnvironmentVariables, + _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; + } + + 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 + QueryPortStr() + { + return m_struPort.QueryStr(); + } + + 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_ DWORD * pdwProcessId, + _Out_ BOOL * pfReady + ); + + HRESULT + RegisterProcessWait( + _In_ PHANDLE phWaitHandle, + _In_ HANDLE hProcessToWaitOn + ); + + HRESULT + GetChildProcessHandles( + ); + + 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( + _In_ DWORD dwNumber + ) + { + DWORD digits = 0; + + if( dwNumber == 0 ) + { + digits = 1; + goto Finished; + } + + while( dwNumber > 0) + { + dwNumber = dwNumber / 10; + digits ++; + } + Finished: + return digits; + } + + static + VOID + SendShutDownSignal( + LPVOID lpParam + ); + + VOID + SendShutDownSignalInternal( + VOID + ); + + HRESULT + SendShutdownHttpMessage( + VOID + ); + + VOID + TerminateBackendProcess( + VOID + ); + + FORWARDER_CONNECTION *m_pForwarderConnection; + BOOL m_fStdoutLogEnabled; + BOOL m_fWindowsAuthEnabled; + BOOL m_fBasicAuthEnabled; + BOOL m_fAnonymousAuthEnabled; + + STTIMER m_Timer; + SOCKET m_socket; + + STRU m_struLogFile; + STRU m_struFullLogFile; + STRU m_ProcessPath; + STRU m_Arguments; + STRU m_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; + DWORD m_cChildProcess; + DWORD m_dwChildProcessIds[MAX_ACTIVE_CHILD_PROCESSES]; + DWORD m_dwProcessId; + DWORD m_dwListeningProcessId; + + STRA m_straGuid; + + HANDLE m_hJobObject; + HANDLE m_hStdoutHandle; + // + // m_hProcessHandle is the handle to process this object creates. + // + HANDLE m_hProcessHandle; + HANDLE m_hListeningProcessHandle; + HANDLE m_hProcessWaitHandle; + HANDLE m_hShutdownHandle; + // + // m_hChildProcessHandle is the handle to process created by + // m_hProcessHandle process if it does. + // + HANDLE m_hChildProcessHandles[MAX_ACTIVE_CHILD_PROCESSES]; + HANDLE m_hChildProcessWaitHandles[MAX_ACTIVE_CHILD_PROCESSES]; + + PROCESS_MANAGER *m_pProcessManager; + ENVIRONMENT_VAR_HASH *m_pEnvironmentVarTable ; +}; \ No newline at end of file diff --git a/src/RequestHandler/sttimer.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/sttimer.h similarity index 100% rename from src/RequestHandler/sttimer.h rename to src/AspNetCoreModuleV1/AspNetCore/Inc/sttimer.h diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/websockethandler.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/websockethandler.h new file mode 100644 index 0000000000..aadfcb6884 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/websockethandler.h @@ -0,0 +1,221 @@ +// 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(); + + virtual + ~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, + BOOL* fHandleCreated + ); + + 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 + }; + + 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 _fHandleClosed; + + volatile + BOOL _fCleanupInProgress; + + volatile + BOOL _fIndicateCompletionToIis; + + volatile + BOOL _fReceivedCloseMsg; + + static + LIST_ENTRY sm_RequestsListHead; + + static + SRWLOCK sm_RequestsListLock; + + static + TRACE_LOG * sm_pTraceLog; +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/winhttphelper.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/winhttphelper.h new file mode 100644 index 0000000000..b301a76cf2 --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/Source.def similarity index 100% rename from src/AspNetCore/Source.def rename to src/AspNetCoreModuleV1/AspNetCore/Source.def diff --git a/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.mc b/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.mc new file mode 100644 index 0000000000..50bf87b656 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.mc @@ -0,0 +1,78 @@ +;/*++ +; +;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 +. + +Messageid=1006 +SymbolicName=ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST +Language=English +%1 +. + +Messageid=1012 +SymbolicName=ASPNETCORE_EVENT_RECYCLE_APPOFFLINE +Language=English +%1 +. + +; +;#endif // _ASPNETCORE_MODULE_MSG_H_ +; diff --git a/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml b/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml new file mode 100644 index 0000000000..c1590816b7 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc b/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc new file mode 100644 index 0000000000..fc9f40f623 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc @@ -0,0 +1,117 @@ +// Microsoft Visual C++ generated resource script. +// +#include +#include "version.h" +#include "resource.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +///////////////////////////////////////////////////////////////////////////// +// +// 11 +// + +//1 11 +//BEGIN +// 0x0001, 0x0000, 0x03e8, 0x0000, 0x03ed, 0x0000, 0x0010, 0x0000, 0x0010, +// 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, +// 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, +// 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, +// 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, +// 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, +// 0x0000, 0x0000 +//END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION FileVersion + PRODUCTVERSION ProductVersion + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Microsoft" + VALUE "FileDescription", "IIS ASP.NET Core Module" + VALUE "FileVersion", FileVersionStr + VALUE "InternalName", "aspnetcore.dll" + VALUE "LegalCopyright", "Copyright (C) 2016" + VALUE "OriginalFilename", "aspnetcore.dll" + VALUE "ProductName", "ASP.NET Core Module" + VALUE "ProductVersion", ProductVersionStr + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_INVALID_PROPERTY "Property name '%s' in system.webServer/aspNetCore section has invalid value '%s' which does not conform to the prescribed format" + IDS_SERVER_ERROR "There was a connection error while trying to route the request." +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/src/AspNetCoreModuleV1/AspNetCore/resource.h b/src/AspNetCoreModuleV1/AspNetCore/resource.h new file mode 100644 index 0000000000..493c3e2797 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by aspnetcoremodule.rc +// +#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 +#define IDS_INVALID_PROPERTY 1000 +#define IDS_SERVER_ERROR 1001 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/application.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/application.cxx new file mode 100644 index 0000000000..f80435cfbb --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/application.cxx @@ -0,0 +1,164 @@ +// 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_pAppOfflineHtm != NULL) + { + m_pAppOfflineHtm->DereferenceAppOfflineHtm(); + m_pAppOfflineHtm = NULL; + } + + if (m_pFileWatcherEntry != NULL) + { + // Mark the entry as invalid, + // StopMonitor will close the file handle and trigger a FCN + // the entry will delete itself when processing this FCN + m_pFileWatcherEntry->MarkEntryInValid(); + m_pFileWatcherEntry->StopMonitor(); + m_pFileWatcherEntry = NULL; + } + + if (m_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); + + return hr; +} + +VOID +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) + { + m_fAppOfflineFound = FALSE; + } + 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 ) + { + if (pNewAppOfflineHtm->Load()) + { + // + // loaded the new app_offline.htm + // + pOldAppOfflineHtm = (APP_OFFLINE_HTM *)InterlockedExchangePointer((VOID**)&m_pAppOfflineHtm, pNewAppOfflineHtm); + + if (pOldAppOfflineHtm != NULL) + { + pOldAppOfflineHtm->DereferenceAppOfflineHtm(); + pOldAppOfflineHtm = NULL; + } + } + else + { + // ignored the new app_offline file because the file does not exist. + pNewAppOfflineHtm->DereferenceAppOfflineHtm(); + pNewAppOfflineHtm = NULL; + } + } + } +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/applicationmanager.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/applicationmanager.cxx new file mode 100644 index 0000000000..41f20013b7 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/applicationmanager.cxx @@ -0,0 +1,178 @@ +// 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, + _Out_ APPLICATION ** ppApplication +) +{ + HRESULT hr = S_OK; + 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(pszApplicationId); + 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, pszApplicationId, 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; +} + + +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); + if(m_pHttp502ErrorPage != NULL) + { + delete m_pHttp502ErrorPage; + } + m_pHttp502ErrorPage = pHttp502ErrorPage; + *ppErrorPage = m_pHttp502ErrorPage; + } + } + +Finished: + if (fExclusiveLock) + { + ReleaseSRWLockExclusive(&m_srwLock); + } + + if (FAILED(hr)) + { + if (pHttp502ErrorPage != NULL) + { + delete pHttp502ErrorPage; + } + } + + return hr; +} diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/aspnetcoreconfig.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/aspnetcoreconfig.cxx new file mode 100644 index 0000000000..d8bcd8d777 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/aspnetcoreconfig.cxx @@ -0,0 +1,416 @@ +// 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()); + } + if(m_pEnvironmentVariables != NULL) + { + m_pEnvironmentVariables->Clear(); + delete m_pEnvironmentVariables; + m_pEnvironmentVariables = NULL; + } +} + +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 + { + // set appliction info here instead of inside Populate() + // as the destructor will delete the backend process + hr = pAspNetCoreConfig->QueryApplicationPath()->Copy(pHttpApplication->GetApplicationId()); + if (FAILED(hr)) + { + goto Finished; + } + } + + *ppAspNetCoreConfig = pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + +Finished: + + if (pAspNetCoreConfig != NULL) + { + delete pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + } + + return hr; +} + +HRESULT +ASPNETCORE_CONFIG::Populate( + IHttpContext *pHttpContext +) +{ + HRESULT hr = S_OK; + STACK_STRU(strSiteConfigPath, 256); + STRU strEnvName; + STRU strEnvValue; + STRU strExpandedEnvValue; + IAppHostAdminManager *pAdminManager = NULL; + IAppHostElement *pAspNetCoreElement = NULL; + IAppHostElement *pWindowsAuthenticationElement = NULL; + IAppHostElement *pBasicAuthenticationElement = NULL; + IAppHostElement *pAnonymousAuthenticationElement = NULL; + IAppHostElement *pEnvVarList = NULL; + IAppHostElement *pEnvVar = NULL; + IAppHostElementCollection *pEnvVarCollection = NULL; + ULONGLONG ullRawTimeSpan = 0; + ENUM_INDEX index; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + 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)) + { + goto Finished; + } + + 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)) + { + goto Finished; + } + + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_ARGUMENTS, + &m_struArguments); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementDWORDProperty(pAspNetCoreElement, + CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE, + &m_dwRapidFailsPerMinute); + if (FAILED(hr)) + { + goto Finished; + } + + // + // rapidFailsPerMinute cannot be greater than 100. + // + if (m_dwRapidFailsPerMinute > MAX_RAPID_FAILS_PER_MINUTE) + { + m_dwRapidFailsPerMinute = MAX_RAPID_FAILS_PER_MINUTE; + } + + hr = GetElementDWORDProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESSES_PER_APPLICATION, + &m_dwProcessesPerApplication); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementDWORDProperty( + pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT, + &m_dwStartupTimeLimitInMS + ); + if (FAILED(hr)) + { + goto Finished; + } + + m_dwStartupTimeLimitInMS *= MILLISECONDS_IN_ONE_SECOND; + + hr = GetElementDWORDProperty( + pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT, + &m_dwShutdownTimeLimitInMS + ); + if (FAILED(hr)) + { + goto Finished; + } + m_dwShutdownTimeLimitInMS *= MILLISECONDS_IN_ONE_SECOND; + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_FORWARD_WINDOWS_AUTH_TOKEN, + &m_fForwardWindowsAuthToken); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE, + &m_fDisableStartUpErrorPage); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementRawTimeSpanProperty( + pAspNetCoreElement, + CS_ASPNETCORE_WINHTTP_REQUEST_TIMEOUT, + &ullRawTimeSpan + ); + if (FAILED(hr)) + { + goto Finished; + } + + m_dwRequestTimeoutInMS = (DWORD)TIMESPAN_IN_MILLISECONDS(ullRawTimeSpan); + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_STDOUT_LOG_ENABLED, + &m_fStdoutLogEnabled); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_STDOUT_LOG_FILE, + &m_struStdoutLogFile); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementChildByName(pAspNetCoreElement, + CS_ASPNETCORE_ENVIRONMENT_VARIABLES, + &pEnvVarList); + if (FAILED(hr)) + { + goto Finished; + } + + hr = pEnvVarList->get_Collection(&pEnvVarCollection); + if (FAILED(hr)) + { + goto Finished; + } + + for (hr = FindFirstElement(pEnvVarCollection, &index, &pEnvVar); + SUCCEEDED(hr); + hr = FindNextElement(pEnvVarCollection, &index, &pEnvVar)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + if (FAILED(hr = GetElementStringProperty(pEnvVar, + CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME, + &strEnvName)) || + FAILED(hr = GetElementStringProperty(pEnvVar, + CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE, + &strEnvValue)) || + FAILED(hr = strEnvName.Append(L"=")) || + FAILED(hr = STRU::ExpandEnvironmentVariables(strEnvValue.QueryStr(), &strExpandedEnvValue))) + { + goto Finished; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(hr = pEntry->Initialize(strEnvName.QueryStr(), strExpandedEnvValue.QueryStr())) || + FAILED(hr = m_pEnvironmentVariables->InsertRecord(pEntry))) + { + goto Finished; + } + strEnvName.Reset(); + strEnvValue.Reset(); + strExpandedEnvValue.Reset(); + pEnvVar->Release(); + pEnvVar = NULL; + pEntry->Dereference(); + pEntry = NULL; + } + +Finished: + + if (pAspNetCoreElement != NULL) + { + pAspNetCoreElement->Release(); + pAspNetCoreElement = NULL; + } + + if (pEnvVarList != NULL) + { + pEnvVarList->Release(); + pEnvVarList = NULL; + } + + if (pEnvVar != NULL) + { + pEnvVar->Release(); + pEnvVar = NULL; + } + + if (pEnvVarCollection != NULL) + { + pEnvVarCollection->Release(); + pEnvVarCollection = NULL; + } + + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx new file mode 100644 index 0000000000..086ed8774f --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx @@ -0,0 +1,481 @@ +// 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) +{ +} + +FILE_WATCHER::~FILE_WATCHER() +{ + if (m_hChangeNotificationThread != NULL) + { + CloseHandle(m_hChangeNotificationThread); + m_hChangeNotificationThread = NULL; + } +} + +HRESULT +FILE_WATCHER::Create( + VOID +) +{ + HRESULT hr = S_OK; + + m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, + 0, + 0); + + if (m_hCompletionPort == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + m_hChangeNotificationThread = CreateThread(NULL, + 0, + ChangeNotificationThread, + this, + 0, + NULL); + + if (m_hChangeNotificationThread == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + + CloseHandle(m_hCompletionPort); + m_hCompletionPort = NULL; + + goto Finished; + } + +Finished: + return hr; +} + +DWORD +WINAPI +FILE_WATCHER::ChangeNotificationThread( + LPVOID pvArg +) +/*++ + +Routine Description: + +IO completion thread + +Arguments: + +None + +Return Value: + +Win32 error + +--*/ +{ + FILE_WATCHER * pFileMonitor; + BOOL fSuccess = FALSE; + DWORD cbCompletion = 0; + OVERLAPPED * pOverlapped = NULL; + DWORD dwErrorStatus; + ULONG_PTR completionKey; + + pFileMonitor = (FILE_WATCHER*)pvArg; + DBG_ASSERT(pFileMonitor != NULL); + + while (TRUE) + { + fSuccess = GetQueuedCompletionStatus( + pFileMonitor->m_hCompletionPort, + &cbCompletion, + &completionKey, + &pOverlapped, + INFINITE); + + DBG_ASSERT(fSuccess); + DebugPrint(1, "FILE_WATCHER::ChangeNotificationThread"); + dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError(); + + if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) + { + continue; + } + + DBG_ASSERT(pOverlapped != NULL); + 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); + pMonitorEntry->DereferenceFileWatcherEntry(); + DBG_ASSERT(pMonitorEntry != NULL); + + pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, cbCompletion); + + if (pMonitorEntry->QueryIsValid()) + { + // + // Continue monitoring + // + pMonitorEntry->Monitor(); + } + else + { + // + // Marked by application distructor + // Deference the entry to delete it + // + pMonitorEntry->DereferenceFileWatcherEntry(); + } +} + + +FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : + _pFileMonitor(pFileMonitor), + _hDirectory(INVALID_HANDLE_VALUE), + _hImpersonationToken(NULL), + _pApplication(NULL), + _lStopMonitorCalled(0), + _cRefs(1), + _fIsValid(TRUE) +{ + _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE; + InitializeSRWLock(&_srwLock); +} + +FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY() +{ + _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE_FREE; + + if (_hDirectory != INVALID_HANDLE_VALUE) + { + CloseHandle(_hDirectory); + _hDirectory = INVALID_HANDLE_VALUE; + } + + if (_hImpersonationToken != NULL) + { + CloseHandle(_hImpersonationToken); + _hImpersonationToken = NULL; + } +} + +#pragma warning(disable:4100) + +HRESULT +FILE_WATCHER_ENTRY::HandleChangeCompletion( + _In_ DWORD dwCompletionStatus, + _In_ DWORD cbCompletion +) +/*++ + +Routine Description: + +Handle change notification (see if any of associated config files +need to be flushed) + +Arguments: + +dwCompletionStatus - Completion status +cbCompletion - Bytes of completion + +Return Value: + +HRESULT + +--*/ +{ + HRESULT hr = S_OK; + FILE_NOTIFY_INFORMATION * pNotificationInfo; + BOOL fFileChanged = FALSE; + STRU strEventMsg; + + AcquireSRWLockExclusive(&_srwLock); + if (!_fIsValid) + { + goto Finished; + } + + // When directory handle is closed then HandleChangeCompletion + // happens with cbCompletion = 0 and dwCompletionStatus = 0 + // From documentation it is not clear if that combination + // of return values is specific to closing handles or whether + // it could also mean an error condition. Hence we will maintain + // explicit flag that will help us determine if entry + // is being shutdown (StopMonitor() called) + // + if (_lStopMonitorCalled) + { + goto Finished; + } + + // + // There could be a FCN overflow + // Let assume the file got changed instead of checking files + // Othersie we have to cache the file info + // + if (cbCompletion == 0) + { + fFileChanged = TRUE; + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); + DBG_ASSERT(pNotificationInfo != NULL); + + while (pNotificationInfo != NULL) + { + // + // check whether the monitored file got changed + // + if (_wcsnicmp(pNotificationInfo->FileName, + _strFileName.QueryStr(), + pNotificationInfo->FileNameLength/sizeof(WCHAR)) == 0) + { + fFileChanged = TRUE; + break; + } + // + // Advance to next notification + // + if (pNotificationInfo->NextEntryOffset == 0) + { + pNotificationInfo = NULL; + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*) + ((PBYTE)pNotificationInfo + + pNotificationInfo->NextEntryOffset); + } + } + } + + if (fFileChanged) + { + LPCWSTR apsz[1]; + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, + _strFileName.QueryStr()))) + { + 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_RECYCLE_APPOFFLINE, + NULL, + 1, + 0, + apsz, + NULL); + } + } + // + // so far we only monitoring app_offline + // + _pApplication->UpdateAppOfflineFileHandle(); + } + +Finished: + ReleaseSRWLockExclusive(&_srwLock); + return hr; +} + +#pragma warning( error : 4100 ) + +HRESULT +FILE_WATCHER_ENTRY::Monitor(VOID) +{ + HRESULT hr = S_OK; + DWORD cbRead; + + AcquireSRWLockExclusive(&_srwLock); + ReferenceFileWatcherEntry(); + ZeroMemory(&_overlapped, sizeof(_overlapped)); + + if(!ReadDirectoryChangesW(_hDirectory, + _buffDirectoryChanges.QueryPtr(), + _buffDirectoryChanges.QuerySize(), + FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline + FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS, + &cbRead, + &_overlapped, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + ReleaseSRWLockExclusive(&_srwLock); + return hr; +} + +VOID +FILE_WATCHER_ENTRY::StopMonitor(VOID) +{ + // + // Flag that monitoring is being stopped so that + // we know that HandleChangeCompletion() call + // can be ignored + // + InterlockedExchange(&_lStopMonitorCalled, 1); + + AcquireSRWLockExclusive(&_srwLock); + + if (_hDirectory != INVALID_HANDLE_VALUE) + { + CloseHandle(_hDirectory); + _hDirectory = INVALID_HANDLE_VALUE; + } + + ReleaseSRWLockExclusive(&_srwLock); +} + +HRESULT +FILE_WATCHER_ENTRY::Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ APPLICATION* pApplication, + _In_ HANDLE hImpersonationToken +) +{ + HRESULT hr = S_OK; + BOOL fRet = FALSE; + + if (pszDirectoryToMonitor == NULL || + pszFileNameToMonitor == NULL || + pApplication == NULL) + { + DBG_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; + } + + // + // Resize change buffer to something "reasonable" + // + if (!_buffDirectoryChanges.Resize(FILE_WATCHER_ENTRY_BUFFER_SIZE)) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Finished; + } + + if (hImpersonationToken != NULL) + { + fRet = DuplicateHandle(GetCurrentProcess(), + hImpersonationToken, + GetCurrentProcess(), + &_hImpersonationToken, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + + if (!fRet) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + else + { + if (_hImpersonationToken != NULL) + { + CloseHandle(_hImpersonationToken); + _hImpersonationToken = NULL; + } + } + + _hDirectory = CreateFileW( + _strDirectoryName.QueryStr(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + + if (_hDirectory == INVALID_HANDLE_VALUE) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (CreateIoCompletionPort( + _hDirectory, + _pFileMonitor->QueryCompletionPort(), + NULL, + 0) == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // Start monitoring + // + hr = Monitor(); + +Finished: + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/forwarderconnection.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/forwarderconnection.cxx new file mode 100644 index 0000000000..ed1e29aadd --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/forwarderconnection.cxx @@ -0,0 +1,52 @@ +// 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; + } + + // + // Since WinHttp will not emit WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING + // when closing WebSocket handle on Win8. Register callback at Connect level as a workaround + // + if (WinHttpSetStatusCallback(m_hConnection, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + +Finished: + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx new file mode 100644 index 0000000000..f02a318f41 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx @@ -0,0 +1,3073 @@ +// 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 + +// 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_dwHandlers(1), +m_pW3Context(pW3Context), +m_hRequest(NULL), +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_fFinishRequest(FALSE), +m_fClientDisconnected(FALSE), +m_fHasError(FALSE), +m_fServerResetConn(FALSE), +m_fDoneAsyncCompletion(FALSE), +m_fHttpHandleInClose(FALSE), +m_fWebSocketHandleInClose(FALSE) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER --%p\n", this); + + InitializeSRWLock(&m_RequestLock); +} + +FORWARDING_HANDLER::~FORWARDING_HANDLER( + VOID +) +{ + // + // Destructor has started. + // + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "~FORWARDING_HANDLER --%p\n", this); + + m_Signature = FORWARDING_HANDLER_SIGNATURE_FREE; + + // + // 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_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, // Callback Function + WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, + 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 = InterlockedDecrement(&m_cRefs); + if (cRefs == 0) + { + delete this; + } + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLog(sm_pTraceLog, + cRefs, + this); + } +} + +HRESULT +FORWARDING_HANDLER::SetStatusAndHeaders( + PCSTR pszHeaders, + DWORD +) +{ + HRESULT hr; + IHttpResponse * pResponse = m_pW3Context->GetResponse(); + IHttpRequest * pRequest = m_pW3Context->GetRequest(); + STACK_STRA(strHeaderName, 128); + STACK_STRA(strHeaderValue, 2048); + DWORD index = 0; + PSTR pchNewline; + PCSTR pchEndofHeaderValue; + BOOL fServerHeaderPresent = FALSE; + + _ASSERT(pszHeaders != NULL); + + // + // The first line is the status line + // + PSTR pchStatus = const_cast(strchr(pszHeaders, ' ')); + if (pchStatus == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + while (*pchStatus == ' ') + { + pchStatus++; + } + USHORT uStatus = static_cast(atoi(pchStatus)); + + if (m_fWebSocketEnabled && uStatus != 101) + { + // + // Expected 101 response. + // + + m_fWebSocketEnabled = FALSE; + } + + pchStatus = strchr(pchStatus, ' '); + if (pchStatus == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + while (*pchStatus == ' ') + { + pchStatus++; + } + if (*pchStatus == '\r' || *pchStatus == '\n') + { + pchStatus--; + } + + pchNewline = strchr(pchStatus, '\n'); + if (pchNewline == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + if (uStatus != 200) + { + // + // Skip over any spaces before the '\n' + // + for (pchEndofHeaderValue = pchNewline - 1; + (pchEndofHeaderValue > pchStatus) && + ((*pchEndofHeaderValue == ' ') || + (*pchEndofHeaderValue == '\r')); + pchEndofHeaderValue--) + {} + + // + // Copy the status description + // + if (FAILED(hr = strHeaderValue.Copy( + pchStatus, + (DWORD)(pchEndofHeaderValue - pchStatus) + 1)) || + FAILED(hr = pResponse->SetStatus(uStatus, + strHeaderValue.QueryStr(), + 0, + S_OK, + NULL, + TRUE))) + { + return hr; + } + } + + for (index = static_cast(pchNewline - pszHeaders) + 1; + pszHeaders[index] != '\r' && pszHeaders[index] != '\n' && pszHeaders[index] != '\0'; + index = static_cast(pchNewline - pszHeaders) + 1) + { + // + // Find the ':' in Header : Value\r\n + // + PCSTR pchColon = strchr(pszHeaders + index, ':'); + + // + // Find the '\n' in Header : Value\r\n + // + pchNewline = const_cast(strchr(pszHeaders + index, '\n')); + + if (pchNewline == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + // + // Take care of header continuation + // + while (pchNewline[1] == ' ' || + pchNewline[1] == '\t') + { + pchNewline = strchr(pchNewline + 1, '\n'); + } + + DBG_ASSERT( + (pchColon != NULL) && (pchColon < pchNewline)); + if ((pchColon == NULL) || (pchColon >= pchNewline)) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + // + // Skip over any spaces before the ':' + // + PCSTR pchEndofHeaderName; + for (pchEndofHeaderName = pchColon - 1; + (pchEndofHeaderName >= pszHeaders + index) && + (*pchEndofHeaderName == ' '); + pchEndofHeaderName--) + {} + + pchEndofHeaderName++; + + // + // Copy the header name + // + if (FAILED(hr = strHeaderName.Copy( + pszHeaders + index, + (DWORD)(pchEndofHeaderName - pszHeaders) - index))) + { + return hr; + } + + // + // Skip over the ':' and any trailing spaces + // + for (index = static_cast(pchColon - pszHeaders) + 1; + pszHeaders[index] == ' '; + index++) + {} + + + // + // Skip over any spaces before the '\n' + // + for (pchEndofHeaderValue = pchNewline - 1; + (pchEndofHeaderValue >= pszHeaders + index) && + ((*pchEndofHeaderValue == ' ') || + (*pchEndofHeaderValue == '\r')); + pchEndofHeaderValue--) + {} + + pchEndofHeaderValue++; + + // + // Copy the header value + // + if (pchEndofHeaderValue == pszHeaders + index) + { + strHeaderValue.Reset(); + } + else if (FAILED(hr = strHeaderValue.Copy( + pszHeaders + index, + (DWORD)(pchEndofHeaderValue - pszHeaders) - index))) + { + return hr; + } + + // + // Do not pass the transfer-encoding:chunked, Connection, Date or + // Server headers along + // + DWORD headerIndex = g_pResponseHeaderHash->GetIndex(strHeaderName.QueryStr()); + if (headerIndex == UNKNOWN_INDEX) + { + hr = pResponse->SetHeader(strHeaderName.QueryStr(), + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + FALSE); // fReplace + } + else + { + switch (headerIndex) + { + case HttpHeaderTransferEncoding: + if (!strHeaderValue.Equals("chunked", TRUE)) + { + break; + } + __fallthrough; + case HttpHeaderConnection: + case HttpHeaderDate: + continue; + + case HttpHeaderServer: + fServerHeaderPresent = TRUE; + break; + + case HttpHeaderContentLength: + if (pRequest->GetRawHttpRequest()->Verb != HttpVerbHEAD) + { + m_cContentLength = _atoi64(strHeaderValue.QueryStr()); + } + break; + } + + hr = pResponse->SetHeader(static_cast(headerIndex), + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + TRUE); // fReplace + } + if (FAILED(hr)) + { + return hr; + } + } + + // + // Explicitly remove the Server header if the back-end didn't set one. + // + + if (!fServerHeaderPresent) + { + pResponse->DeleteHeader("Server"); + } + + if (m_fDoReverseRewriteHeaders) + { + hr = DoReverseRewrite(pResponse); + if (FAILED(hr)) + { + return hr; + } + } + + m_fResponseHeadersReceivedAndSet = TRUE; + + return S_OK; +} + +HRESULT +FORWARDING_HANDLER::DoReverseRewrite( + __in IHttpResponse *pResponse +) +{ + DBG_ASSERT(pResponse == m_pW3Context->GetResponse()); + BOOL fSecure = (m_pW3Context->GetRequest()->GetRawHttpRequest()->pSslInfo != NULL); + STRA strTemp; + PCSTR pszHeader; + PCSTR pszStartHost; + PCSTR pszEndHost; + HTTP_RESPONSE_HEADERS *pHeaders; + HRESULT hr; + + // + // Content-Location and Location are easy, one known header in + // http[s]://host/url format + // + pszHeader = pResponse->GetHeader(HttpHeaderContentLocation); + if (pszHeader != NULL) + { + if (_strnicmp(pszHeader, "http://", 7) == 0) + { + pszStartHost = pszHeader + 7; + } + else if (_strnicmp(pszHeader, "https://", 8) == 0) + { + pszStartHost = pszHeader + 8; + } + else + { + goto Location; + } + + pszEndHost = strchr(pszStartHost, '/'); + + if (FAILED(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader))) + { + return hr; + } + if (pszEndHost != NULL && + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentLocation, + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) + { + return hr; + } + } + +Location: + + pszHeader = pResponse->GetHeader(HttpHeaderLocation); + if (pszHeader != NULL) + { + if (_strnicmp(pszHeader, "http://", 7) == 0) + { + pszStartHost = pszHeader + 7; + } + else if (_strnicmp(pszHeader, "https://", 8) == 0) + { + pszStartHost = pszHeader + 8; + } + else + { + goto SetCookie; + } + + pszEndHost = strchr(pszStartHost, '/'); + + if (FAILED(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader))) + { + return hr; + } + if (pszEndHost != NULL && + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + if (FAILED(hr = pResponse->SetHeader(HttpHeaderLocation, + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) + { + return hr; + } + } + +SetCookie: + + // + // Set-Cookie is different - possibly multiple unknown headers with + // syntax name=value ; ... ; Domain=.host ; ... + // + pHeaders = &pResponse->GetRawHttpResponse()->Headers; + for (DWORD i = 0; iUnknownHeaderCount; i++) + { + if (_stricmp(pHeaders->pUnknownHeaders[i].pName, "Set-Cookie") != 0) + { + continue; + } + + pszHeader = pHeaders->pUnknownHeaders[i].pRawValue; + pszStartHost = strchr(pszHeader, ';'); + while (pszStartHost != NULL) + { + pszStartHost++; + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + + if (_strnicmp(pszStartHost, "Domain", 6) != 0) + { + pszStartHost = strchr(pszStartHost, ';'); + continue; + } + pszStartHost += 6; + + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + if (*pszStartHost != '=') + { + break; + } + pszStartHost++; + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + if (*pszStartHost == '.') + { + pszStartHost++; + } + pszEndHost = pszStartHost; + while (!IsSpace(*pszEndHost) && + *pszEndHost != ';' && + *pszEndHost != '\0') + { + pszEndHost++; + } + + if (FAILED(hr = strTemp.Copy(pszHeader, static_cast(pszStartHost - pszHeader))) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader)) || + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + + pszHeader = (PCSTR)m_pW3Context->AllocateRequestMemory(strTemp.QueryCCH() + 1); + if (pszHeader == NULL) + { + return E_OUTOFMEMORY; + } + StringCchCopyA(const_cast(pszHeader), strTemp.QueryCCH() + 1, strTemp.QueryStr()); + pHeaders->pUnknownHeaders[i].pRawValue = pszHeader; + pHeaders->pUnknownHeaders[i].RawValueLength = static_cast(strTemp.QueryCCH()); + + break; + } + } + + return S_OK; +} + +HRESULT +FORWARDING_HANDLER::GetHeaders( + const PROTOCOL_CONFIG * pProtocol, + PCWSTR * ppszHeaders, + DWORD * pcchHeaders, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess +) +{ + + HRESULT hr = S_OK; + PCSTR pszCurrentHeader; + PCSTR ppHeadersToBeRemoved; + PCSTR pszFinalHeader; + USHORT cchCurrentHeader; + DWORD cchFinalHeader; + BOOL fSecure = FALSE; // dummy. Used in SplitUrl. Value will not be used + // as ANCM always use http protocol to communicate with backend + STRU struDestination; + STRU struUrl; + STACK_STRA(strTemp, 64); + HTTP_REQUEST_HEADERS *pHeaders; + IHttpRequest *pRequest = m_pW3Context->GetRequest(); + MULTISZA mszMsAspNetCoreHeaders; + + // + // We historically set the host section in request url to the new host header + // this is wrong but Kestrel has dependency on it. + // should change it in the future + // + if (!pProtocol->QueryPreserveHostHeader()) + { + if (FAILED(hr = 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 + // 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 + { + // Resize the buffer large enough to hold the encoded certificate info + if (FAILED(hr = strTemp.Resize( + 1 + (pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->CertEncodedSize + 2) / 3 * 4))) + { + return hr; + } + + Base64Encode( + pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->pCertEncoded, + pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->CertEncodedSize, + strTemp.QueryStr(), + strTemp.QuerySize(), + NULL); + strTemp.SyncWithBuffer(); + + if (FAILED(hr = pRequest->SetHeader( + pProtocol->QueryClientCertName()->QueryStr(), + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) // fReplace + { + return hr; + } + } + } + + // + // Remove the connection header + // + if (!m_fWebSocketEnabled) + { + pRequest->DeleteHeader(HttpHeaderConnection); + } + + // + // Get all the headers to send to the client + // + hr = m_pW3Context->GetServerVariable("ALL_RAW", + ppszHeaders, + pcchHeaders); + if (FAILED(hr)) + { + return hr; + } + + return S_OK; +} + +HRESULT +FORWARDING_HANDLER::CreateWinHttpRequest( + __in const IHttpRequest * pRequest, + __in const PROTOCOL_CONFIG * pProtocol, + __in HINTERNET hConnect, + __inout STRU * pstrUrl, + 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; + } + + if (WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + (WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | + WINHTTP_CALLBACK_FLAG_HANDLES | + WINHTTP_CALLBACK_STATUS_SENDING_REQUEST), + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hr = GetHeaders(pProtocol, + &m_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; + 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; + IHttpConnection *pClientConnection = 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(); + + m_cMinBufferLimit = pProtocol->QueryMinResponseBuffer(); + + pClientConnection = m_pW3Context->GetConnection(); + if (pClientConnection == NULL || + !pClientConnection->IsConnected()) + { + hr = HRESULT_FROM_WIN32(WSAECONNRESET); + goto Failure; + } + + // + // 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; + } + + 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(); + + pResponse->SetStatus(503, "Service Unavailable", 0, hr, NULL, TRUE); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); // no need to check return hresult + + pResponse->WriteEntityChunkByReference(&DataChunk); + 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, + pAspNetCoreConfig, + pServerProcess); + + if (FAILED(hr)) + { + goto Failure; + } + + // + // Register for connection disconnect notification with http.sys. + // + 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)) + { + m_pDisconnect->CleanupStoredContext(); + m_pDisconnect = NULL; + 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); + } + + 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; + + 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"); + 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; + m_fHasError = TRUE; + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + // + // Finish the request on failure. + // + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + + if (hr == HRESULT_FROM_WIN32(WSAECONNRESET)) + { + pResponse->SetStatus(400, "Bad Request", 0, hr); + goto Finished; + } + else if (fProcessStartFailure && !pAspNetCoreConfig->QueryDisableStartUpErrorPage()) + { + pResponse->SetStatus(502, "Bad Gateway", 5, hr, NULL, TRUE); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); + + if (SUCCEEDED(pApplicationManager->Get502ErrorPage(&pDataChunk))) + { + pResponse->WriteEntityChunkByReference(pDataChunk); + 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 (retVal != RQ_NOTIFICATION_PENDING) + { + RemoveRequest(); + } + + DereferenceForwardingHandler(); + // + // Do not use this object after dereferencing it, it may be gone. + // + + return retVal; +} + +VOID +FORWARDING_HANDLER::RemoveRequest() +{ + ASYNC_DISCONNECT_CONTEXT * pDisconnect; + pDisconnect = (ASYNC_DISCONNECT_CONTEXT *) InterlockedExchangePointer((PVOID*)&m_pDisconnect, NULL); + if (pDisconnect != NULL) + { + pDisconnect->ResetHandler(); + 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_PENDING; + BOOL fLocked = FALSE; + BOOL fClientError = FALSE; + BOOL fClosed = FALSE; + BOOL fWebsocketUpgraded = FALSE; + +#ifdef DEBUG + FORWARDING_REQUEST_STATUS dwLocalStatus = m_RequestStatus; +#endif // DEBUG + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnAsyncCompletion Enter", + reinterpret_cast(static_cast(cbCompletion)), + reinterpret_cast(static_cast(hrCompletionStatus))); + } + + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + + // + // Take a reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnAsyncCompletion"); + + // + // 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); + AcquireSRWLockExclusive(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + + fLocked = TRUE; + } + + if (m_fClientDisconnected && (m_RequestStatus != FORWARDER_DONE) ) + { + hr = ERROR_CONNECTION_ABORTED; + goto Failure; + } + + 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 Failure; + } + + hr = m_pWebSocket->ProcessRequest(this, m_pW3Context, m_hRequest, &fWebsocketUpgraded); + if (fWebsocketUpgraded) + { + // WinHttp WebSocket handle has been created, bump the counter so that remember to close it + // and prevent from premature postcomplation and unexpected callback from winhttp + InterlockedIncrement(&m_dwHandlers); + } + + if (FAILED(hr)) + { + // This failure could happen when client disconnect happens or backend server fails + // after websocket upgrade + goto Failure; + } + + // + // WebSocket upgrade is successful. Close the WinHttpRequest Handle + // + m_fHttpHandleInClose = TRUE; + fClosed = WinHttpCloseHandle(m_hRequest); + DBG_ASSERT(fClosed); + m_hRequest = NULL; + + if (!fClosed) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + 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); + if (m_hRequest == NULL && m_pWebSocket == NULL) + { + // Request must have been done + if (!m_fFinishRequest) + { + goto Failure; + } + + if (m_fHasError) + { + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + } + else + { + retVal = RQ_NOTIFICATION_CONTINUE; + } + } + 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; + if (!m_fHasError) + { + 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; + + if (fClientError || m_fClientDisconnected) + { + 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(); + +#ifdef DEBUG + // adding more info to error descrption to help us diagnose the issue + STACK_STRA(strMoreData, 128); + sprintf_s(strMoreData.QueryStr(), 100, "OnAsyncCompletion --%p--%p--%d--%d--%d--%d\n", this, m_pW3Context, GetCurrentThreadId(), (UINT)dwLocalStatus, m_fServerResetConn, m_fClientDisconnected); + strMoreData.SyncWithBuffer(); + strDescription.AppendA(strMoreData.QueryStr()); +#endif // DEBUG + + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + { + if (!m_fServerResetConn) + { + RemoveRequest(); + pResponse->ResetConnection(); + m_fServerResetConn = TRUE; + } + } + } + } + + if (m_pWebSocket != NULL && !m_fWebSocketHandleInClose) + { + m_fWebSocketHandleInClose = TRUE; + m_pWebSocket->TerminateRequest(); + } + + if (m_hRequest != NULL && !m_fHttpHandleInClose) + { + m_fHttpHandleInClose = TRUE; + WinHttpCloseHandle(m_hRequest); + m_hRequest = NULL; + } + +Finished: + + if (fLocked) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockExclusive(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + if (retVal != RQ_NOTIFICATION_PENDING) + { +#ifdef DEBUG + DWORD counter = InterlockedIncrement(&g_dwLogCounter) % ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; + g_strLogs[counter].Reset(); + sprintf_s(g_strLogs[counter].QueryStr(), ASPNETCORE_DEBUG_STRU_BUFFER_SIZE, + "OnAyncCompletion--%p--%d--%d--%d--%d\n", this, GetCurrentThreadId(), (UINT)dwLocalStatus, retVal, m_fDoneAsyncCompletion); + g_strLogs[counter].SyncWithBuffer(); +#endif // DEBUG + + DBG_ASSERT(m_dwHandlers == 0); + RemoveRequest(); + + // This is just a safety guard to prevent from returning non pending status no more once + // which should never happen + if (!m_fDoneAsyncCompletion) + { + m_fDoneAsyncCompletion = TRUE; + } + else + { + retVal = RQ_NOTIFICATION_PENDING; + } + } + + DereferenceForwardingHandler(); + // + // Do not use this object after dereferencing it, it may be gone. + // + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnAsyncCompletion Done %d", retVal); + 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. + // + if (!WinHttpWriteData(m_hRequest, + "0\r\n\r\n", + 5, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + 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. + // + if (!WinHttpReceiveResponse(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + } + else if (SUCCEEDED(hrCompletionStatus)) + { + DWORD cbOffset; + + if (m_BytesToReceive != INFINITE) + { + m_BytesToReceive -= cbCompletion; + cbOffset = 6; + } + else + { + // + // For chunk-encoded requests, need to re-chunk the + // entity body + // + // Add the CRLF just before and after the chunk data + // + m_pEntityBuffer[4] = '\r'; + m_pEntityBuffer[5] = '\n'; + + m_pEntityBuffer[cbCompletion + 6] = '\r'; + m_pEntityBuffer[cbCompletion + 7] = '\n'; + + if (cbCompletion < 0x10) + { + cbOffset = 3; + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion); + cbCompletion += 5; + } + else if (cbCompletion < 0x100) + { + cbOffset = 2; + m_pEntityBuffer[2] = HEX_TO_ASCII(cbCompletion >> 4); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 6; + } + else if (cbCompletion < 0x1000) + { + cbOffset = 1; + m_pEntityBuffer[1] = HEX_TO_ASCII(cbCompletion >> 8); + m_pEntityBuffer[2] = HEX_TO_ASCII((cbCompletion >> 4) & 0xf); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 7; + } + else + { + DBG_ASSERT(cbCompletion < 0x10000); + + cbOffset = 0; + m_pEntityBuffer[0] = HEX_TO_ASCII(cbCompletion >> 12); + m_pEntityBuffer[1] = HEX_TO_ASCII((cbCompletion >> 8) & 0xf); + m_pEntityBuffer[2] = HEX_TO_ASCII((cbCompletion >> 4) & 0xf); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 8; + } + } + m_cchLastSend = cbCompletion; + + // + // WinHttpWriteData can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + if (!WinHttpWriteData(m_hRequest, + m_pEntityBuffer + cbOffset, + cbCompletion, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + else + { + hr = hrCompletionStatus; + *pfClientError = TRUE; + goto Failure; + } + +Failure: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnReceivingResponse( +) +{ + HRESULT hr = S_OK; + + if (m_cBytesBuffered >= m_cMinBufferLimit) + { + FreeResponseBuffers(); + } + + if (m_BytesToSend == 0) + { + // + // If response buffering is enabled, try to read large chunks + // at a time - also treat very small buffering limit as no + // buffering + // + m_BytesToSend = min(m_cMinBufferLimit, BUFFER_SIZE); + if (m_BytesToSend < BUFFER_SIZE / 2) + { + // + // Disable buffering. + // + m_BytesToSend = 0; + } + } + + if (m_BytesToSend == 0) + { + // + // No buffering enabled. + // + // WinHttpQueryDataAvailable can operate asynchronously. + // + if (!WinHttpQueryDataAvailable(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + else + { + // + // Buffering enabled. + // + + if (m_pEntityBuffer == NULL) + { + m_pEntityBuffer = GetNewResponseBuffer(min(m_BytesToSend, BUFFER_SIZE)); + if (m_pEntityBuffer == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + } + + // + // WinHttpReadData can operate asynchronously. + // + if (!WinHttpReadData(m_hRequest, + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + 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 fExclusiveLock = FALSE; + BOOL fSharedLock = FALSE; + BOOL fDoPostCompletion = FALSE; + BOOL fClientError = FALSE; + BOOL fAnotherCompletionExpected = FALSE; + DWORD dwHandlers = 1; // defaullt for http handler + + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + IHttpResponse * pResponse = m_pW3Context->GetResponse(); + BOOL fEndRequest = FALSE; + + // Reference the request handler to prevent it from being released prematurely + ReferenceForwardingHandler(); + + UNREFERENCED_PARAMETER(dwStatusInformationLength); + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal Enter", + reinterpret_cast(static_cast(dwInternetStatus)), + NULL); + } + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal %x --%p", + dwInternetStatus, this); + + // + // Exclusive lock 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. + // + + fEndRequest = (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING); + + if (TlsGetValue(g_dwTlsIndex) != this) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + if (m_RequestStatus != FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + // Webscoket has already been guarded by critical section + // Only require exclisive lock for non-websocket scenario which has duplex channel + // Otherwise, there will be a deadlock + AcquireSRWLockExclusive(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + fExclusiveLock = TRUE; + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + } + else + { + AcquireSRWLockShared(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + fSharedLock = TRUE; + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + } + } + + if (fEndRequest) + { + dwHandlers = InterlockedDecrement(&m_dwHandlers); + } + + if (m_fFinishRequest) + { + // Request was done by another thread, skip + goto Finished; + } + + if(m_fClientDisconnected && (m_RequestStatus != FORWARDER_DONE)) + { + hr = ERROR_CONNECTION_ABORTED; + goto Failure; + } + + if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + fAnotherCompletionExpected = TRUE; + + if(m_pWebSocket == NULL) + { + goto Finished; + } + + switch (dwInternetStatus) + { + case WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE: + m_pWebSocket->OnWinHttpShutdownComplete(); + break; + + case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: + m_pWebSocket->OnWinHttpSendComplete( + (WINHTTP_WEB_SOCKET_STATUS*)lpvStatusInformation + ); + break; + + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + m_pWebSocket->OnWinHttpReceiveComplete( + (WINHTTP_WEB_SOCKET_STATUS*)lpvStatusInformation + ); + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + m_pWebSocket->OnWinHttpIoError( + (WINHTTP_WEB_SOCKET_ASYNC_RESULT*)lpvStatusInformation + ); + break; + } + goto Finished; + } + + switch (dwInternetStatus) + { + case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: + case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: + hr = OnWinHttpCompletionSendRequestOrWriteComplete(hRequest, + dwInternetStatus, + &fClientError, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: + hr = OnWinHttpCompletionStatusHeadersAvailable(hRequest, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: + hr = OnWinHttpCompletionStatusDataAvailable(hRequest, + *reinterpret_cast(lpvStatusInformation), // dwBytes + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + hr = OnWinHttpCompletionStatusReadComplete(pResponse, + dwStatusInformationLength, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + hr = HRESULT_FROM_WIN32(static_cast(lpvStatusInformation)->dwError); + break; + + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + // + // This is a notification, not a completion. This notifiation happens + // during the Send Request operation. + // + fAnotherCompletionExpected = TRUE; + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_SENT: + // + // Need to ignore this event. We get it as a side-effect of registering + // for WINHTTP_CALLBACK_STATUS_SENDING_REQUEST (which we actually need). + // + hr = S_OK; + fAnotherCompletionExpected = TRUE; + break; + + case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: + + fAnotherCompletionExpected = FALSE; + + if (m_RequestStatus != FORWARDER_DONE) + { + hr = ERROR_CONNECTION_ABORTED; + fClientError = m_fClientDisconnected; + goto Failure; + } + 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; + if(!m_fHasError) + { + m_fHasError = TRUE; + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + + if (fClientError || m_fClientDisconnected) + { + 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(); + +#ifdef DEBUG + // addingmore data to error description to help us diaganose the issue + STACK_STRA(strMoreData, 128); + sprintf_s(strMoreData.QueryStr(), 100, "OnWinHttpCompletionInternal --%p--%d--%d--%d\n", this, GetCurrentThreadId(), dwInternetStatus, m_fServerResetConn); + strMoreData.SyncWithBuffer(); + strDescription.AppendA(strMoreData.QueryStr()); +#endif // DEBUG + + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + { + if (!m_fServerResetConn) + { + RemoveRequest(); + pResponse->ResetConnection(); + m_fServerResetConn = TRUE; + } + } + } + } + +Finished: + // + // Since we use TLS to guard WinHttp operation, call PostCompletion instead of + // IndicateCompletion to allow cleaning up the TLS before thread reuse. + // Never post after the request has been finished for whatever reason + // + // Only postCompletion after all WinHttp handle got closed, + // i.e., received WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING callback for all handles + // So that no further WinHttp callback will be called + // Never post completion again after that + // Otherwise, there will be a AV as the request already passed IIS pipeline + // + if (fEndRequest && !m_fFinishRequest && dwHandlers ==0) + { + // + // Happy path + // +#ifdef DEBUG + DWORD counter = InterlockedIncrement(&g_dwLogCounter) % ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; + sprintf_s(g_strLogs[counter].QueryStr(), ASPNETCORE_DEBUG_STRU_BUFFER_SIZE, + "PostCompletion 0 --%p--%p--%d\n", this, m_pW3Context, GetCurrentThreadId()); + g_strLogs[counter].SyncWithBuffer(); +#endif // DEBUG + + // Marked the request is finished, no more PostCompletion is allowed + RemoveRequest(); + m_fFinishRequest = TRUE; + fDoPostCompletion = TRUE; + if (m_pWebSocket != NULL) + { + delete(m_pWebSocket); + m_pWebSocket = NULL; + } + } + + else if (m_RequestStatus == FORWARDER_DONE) + { + // + // Error path + // + RemoveRequest(); + if (m_hRequest != NULL && !m_fHttpHandleInClose) + { + m_fHttpHandleInClose = TRUE; + WinHttpCloseHandle(m_hRequest); + m_hRequest = NULL; + } + + if (m_pWebSocket != NULL && !m_fWebSocketHandleInClose) + { + m_fWebSocketHandleInClose = TRUE; + m_pWebSocket->TerminateRequest(); + } + + if (fEndRequest) + { + fDoPostCompletion = (dwHandlers == 0 && !m_fFinishRequest); + if (fDoPostCompletion) + { +#ifdef DEBUG + DWORD counter = InterlockedIncrement(&g_dwLogCounter) % ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; + sprintf_s(g_strLogs[counter].QueryStr(), ASPNETCORE_DEBUG_STRU_BUFFER_SIZE, + "PostCompletion 1 --%p--%p--%d\n", this, m_pW3Context, GetCurrentThreadId()); + g_strLogs[counter].SyncWithBuffer(); +#endif // DEBUG + // Marked the request is finished, no more PostCompletion is allowed + m_fFinishRequest = TRUE; + } + } + } + else if (!fAnotherCompletionExpected) + { + // + // Regular async IO operation + // + fDoPostCompletion = !m_fFinishRequest; +#ifdef DEBUG + if (fDoPostCompletion) + { + DWORD counter = InterlockedIncrement(&g_dwLogCounter) % ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; + sprintf_s(g_strLogs[counter].QueryStr(), ASPNETCORE_DEBUG_STRU_BUFFER_SIZE, + "PostCompletion 2 --%p--%p--%d\n", this, m_pW3Context, GetCurrentThreadId()); + g_strLogs[counter].SyncWithBuffer(); + } +#endif // DEBUG + } + + // + // No code should access IIS m_pW3Context after posting the completion. + // + if (fDoPostCompletion) + { + m_pW3Context->PostCompletion(0); + } + + if (fExclusiveLock) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockExclusive(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + else if (fSharedLock) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + DereferenceForwardingHandler(); + +} + +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; + + if (!WinHttpWriteData(m_hRequest, + "0\r\n\r\n", + 5, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + + goto Finished; + } + } + else if (FAILED(hr)) + { + *pfClientError = TRUE; + goto Finished; + } + else + { + // + // ReadEntityBody will post a completion to IIS. + // + *pfAnotherCompletionExpected = TRUE; + + goto Finished; + } + } + + m_RequestStatus = FORWARDER_RECEIVING_RESPONSE; + + if (!WinHttpReceiveResponse(hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + +Finished: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( + HINTERNET hRequest, + __out BOOL * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + STACK_BUFFER(bufHeaderBuffer, 2048); + STACK_STRA(strHeaders, 2048); + DWORD dwHeaderSize = bufHeaderBuffer.QuerySize(); + + UNREFERENCED_PARAMETER(pfAnotherCompletionExpected); + + // + // Headers are available, read the status line and headers and pass + // them on to the client + // + // WinHttpQueryHeaders operates synchronously, + // no need for taking reference. + // + dwHeaderSize = bufHeaderBuffer.QuerySize(); + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_RAW_HEADERS_CRLF, + WINHTTP_HEADER_NAME_BY_INDEX, + bufHeaderBuffer.QueryPtr(), + &dwHeaderSize, + WINHTTP_NO_HEADER_INDEX)) + { + if (!bufHeaderBuffer.Resize(dwHeaderSize)) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // WinHttpQueryHeaders operates synchronously, + // no need for taking reference. + // + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_RAW_HEADERS_CRLF, + WINHTTP_HEADER_NAME_BY_INDEX, + bufHeaderBuffer.QueryPtr(), + &dwHeaderSize, + WINHTTP_NO_HEADER_INDEX)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + + if (FAILED(hr = strHeaders.CopyW( + reinterpret_cast(bufHeaderBuffer.QueryPtr())))) + { + goto Finished; + } + + // Issue: The reason we add trailing \r\n is to eliminate issues that have been observed + // in some configurations where status and headers would not have final \r\n nor \r\n\r\n + // (last header was null terminated).That caused crash within header parsing code that expected valid + // format. Parsing code was fized to return ERROR_INVALID_PARAMETER, but we still should make + // Example of a status+header string that was causing problems (note the missing \r\n at the end) + // HTTP/1.1 302 Moved Permanently\r\n....\r\nLocation:http://site\0 + // + + if (!strHeaders.IsEmpty() && strHeaders.QueryStr()[strHeaders.QueryCCH() - 1] != '\n') + { + hr = strHeaders.Append("\r\n"); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (FAILED(hr = SetStatusAndHeaders( + strHeaders.QueryStr(), + strHeaders.QueryCCH()))) + { + goto Finished; + } + + FreeResponseBuffers(); + + // + // If the request was websocket, and response was 101, + // trigger a flush, so that IIS's websocket module + // can get a chance to initialize and complete the handshake. + // + + if (m_fWebSocketEnabled) + { + + hr = m_pW3Context->GetResponse()->Flush( + TRUE, + TRUE, + NULL, + NULL); + + if (FAILED(hr)) + { + *pfAnotherCompletionExpected = FALSE; + } + else + { + m_RequestStatus = FORWARDER_RECEIVED_WEBSOCKET_RESPONSE; + *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; + } + + if (!WinHttpReadData(hRequest, + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + 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), + 128); // 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 +) +{ + BOOL fAcquiredLock = FALSE; + if (TlsGetValue(g_dwTlsIndex) != this) + { + AcquireSRWLockExclusive(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + fAcquiredLock = TRUE; + } + + // Only set the disconnect flag as the disconnect happens, request most likely is in OnAsyncCompletion + // If we close the handle here, most likely WinHttp callback happens on the same threads. + // We will have two OnAyncCompletion calls and may have race on PostCompletion + // Need do more investigation + if (!m_fHttpHandleInClose) + { + m_fClientDisconnected = fClientInitiated; + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::TerminateRequest"); + RemoveRequest(); + + if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + // + // websocket client is gone, cannot finish closing handshake gracefully + // have to terminate the request + // + if (m_pWebSocket != NULL) + { + m_pWebSocket->TerminateRequest(); + } + } + } + if (fAcquiredLock) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockExclusive(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } +} + +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; i + +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"; + +#ifdef DEBUG +STRA g_strLogs[ASPNETCORE_DEBUG_STRU_ARRAY_SIZE]; +DWORD g_dwLogCounter = 0; +#endif // DEBUG + + +BOOL WINAPI DllMain( + HMODULE hModule, + DWORD dwReason, + LPVOID + ) +{ + switch (dwReason) + { + 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\\IIS AspNetCore Module\\Parameters", + 0, + KEY_READ, + &hKey) == NO_ERROR) + { + DWORD dwType; + DWORD dwData; + DWORD cbData; + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"OptionalWinHttpFlags", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + g_OptionalWinHttpFlags = dwData; + } + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"EnableReferenceCountTracing", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD) && (dwData == 1 || dwData == 0)) + { + g_fEnableReferenceCountTracing = !!dwData; + } + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"DebugFlags", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + g_dwAspNetCoreDebugFlags = dwData; + } + + RegCloseKey(hKey); + } + + DWORD dwSize = 0; + DWORD dwResult = GetExtendedTcpTable(NULL, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) + { + g_fNsiApiNotSupported = TRUE; + } +} + +HRESULT +__stdcall +RegisterModule( +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; + +#ifdef DEBUG + CREATE_DEBUG_PRINT_OBJECT("Asp.Net Core Module"); + g_dwDebugFlags = DEBUG_FLAGS_ANY; +#endif // DEBUG + + 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; + +#ifdef DEBUG + for (int i = 0; i < ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; i++) + { + g_strLogs[i].Resize(ASPNETCORE_DEBUG_STRU_BUFFER_SIZE + 1); + } +#endif // DEBUG + // + // 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/AspNetCoreModuleV1/AspNetCore/src/path.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/path.cxx new file mode 100644 index 0000000000..cde0dd5312 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/path.cxx @@ -0,0 +1,442 @@ +// 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" + +// static +HRESULT +PATH::SplitUrl( + PCWSTR pszDestinationUrl, + BOOL *pfSecure, + STRU *pstrDestination, + STRU *pstrUrl +) +/*++ + +Routine Description: + + Split the URL specified for forwarding into its specific components + The format of the URL looks like + http[s]://destination[:port]/path + when port is omitted, the default port for that specific protocol is used + when host is omitted, it gets the same value as the destination + +Arguments: + + pszDestinationUrl - the url to be split up + pfSecure - SSL to be used in forwarding? + pstrDestination - destination + pDestinationPort - port + pstrUrl - URL + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr; + + // + // First determine if the target is secure + // + if (_wcsnicmp(pszDestinationUrl, L"http://", 7) == 0) + { + *pfSecure = FALSE; + pszDestinationUrl += 7; + } + else if (_wcsnicmp(pszDestinationUrl, L"https://", 8) == 0) + { + *pfSecure = TRUE; + pszDestinationUrl += 8; + } + else + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + if (*pszDestinationUrl == L'\0') + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + // + // Find the 3rd slash corresponding to the url + // + LPCWSTR pszSlash = wcschr(pszDestinationUrl, L'/'); + if (pszSlash == NULL) + { + if (FAILED(hr = pstrUrl->Copy(L"/", 1)) || + FAILED(hr = pstrDestination->Copy(pszDestinationUrl))) + { + return hr; + } + } + else + { + if (FAILED(hr = pstrUrl->Copy(pszSlash)) || + FAILED(hr = pstrDestination->Copy(pszDestinationUrl, + (DWORD)(pszSlash - pszDestinationUrl)))) + { + return hr; + } + } + + return S_OK; +} + +// Change a hexadecimal digit to its numerical equivalent +#define TOHEX( ch ) \ + ((ch) > L'9' ? \ + (ch) >= L'a' ? \ + (ch) - L'a' + 10 : \ + (ch) - L'A' + 10 \ + : (ch) - L'0') + +// static +HRESULT +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; + STRU strTempPath; + + if ( pszPath == NULL || pfIsUnc == NULL ) + { + hr = E_INVALIDARG; + goto Finished; + } + + hr = MakePathCanonicalizationProof( (LPWSTR) pszPath, &strTempPath ); + if ( FAILED(hr) ) + { + goto Finished; + } + + // + // MakePathCanonicalizationProof will map \\?\UNC, \\.\UNC and \\ to \\?\UNC + // + (*pfIsUnc) = ( _wcsnicmp( strTempPath.QueryStr(), L"\\\\?\\UNC\\", 8 /* sizeof \\?\UNC\ */) == 0 ); + +Finished: + + return hr; +} + +HRESULT +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/AspNetCoreModuleV1/AspNetCore/src/precomp.hxx b/src/AspNetCoreModuleV1/AspNetCore/src/precomp.hxx new file mode 100644 index 0000000000..0aabd3b5f1 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/precomp.hxx @@ -0,0 +1,156 @@ +// Copyright(c).NET Foundation.All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#pragma warning( disable : 4091) + +// +// System related headers +// +#define _WINSOCKAPI_ + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + +#include +#include +#include + +//#include +#include +#include + +// This should remove our issue of compiling for win7 without header files. +// We force the Windows 8 version check logic in iiswebsocket.h to succeed even though we're compiling for Windows 7. +// Then, we set the version defines back to Windows 7 to for the remainder of the compilation. +#undef NTDDI_VERSION +#undef WINVER +#undef _WIN32_WINNT +#define NTDDI_VERSION 0x06020000 +#define WINVER 0x0602 +#define _WIN32_WINNT 0x0602 +#include +#undef NTDDI_VERSION +#undef WINVER +#undef _WIN32_WINNT + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + +#include +#include + +// +// 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 +#include +#include + +#include +#include "ahutil.h" +#include "multisz.h" +#include "multisza.h" +#include "sttimer.h" +#include +#include +#include +#include +#include +#include + +#include "filewatcher.h" +#include "environmentvariablehash.h" +#include "..\aspnetcore_msg.h" +#include "aspnetcoreconfig.h" +#include "serverprocess.h" +#include "processmanager.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 PVOID g_pModuleId; +extern BOOL g_fWebSocketSupported; +extern BOOL g_fEnableReferenceCountTracing; +extern DWORD g_dwActiveServerProcesses; diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/processmanager.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/processmanager.cxx new file mode 100644 index 0000000000..5029a52670 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/processmanager.cxx @@ -0,0 +1,294 @@ +// 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; + } + + hr = m_ppServerProcessList[dwProcessIndex]->Initialize( + this, + pConfig->QueryProcessPath(), + pConfig->QueryArguments(), + pConfig->QueryStartupTimeLimitInMS(), + pConfig->QueryShutdownTimeLimitInMS(), + pConfig->QueryWindowsAuthEnabled(), + pConfig->QueryBasicAuthEnabled(), + pConfig->QueryAnonymousAuthEnabled(), + pConfig->QueryEnvironmentVariables(), + pConfig->QueryStdoutLogEnabled(), + pConfig->QueryStdoutLogFile() + ); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = m_ppServerProcessList[dwProcessIndex]->StartProcess(context); + if( FAILED( hr ) ) + { + goto Finished; + } + } + + if( !m_ppServerProcessList[dwProcessIndex]->IsReady() ) + { + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + goto Finished; + } + + m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + } + +Finished: + + if( FAILED(hr) ) + { + if(m_ppServerProcessList[dwProcessIndex] != NULL ) + { + m_ppServerProcessList[dwProcessIndex]->DereferenceServerProcess(); + m_ppServerProcessList[dwProcessIndex] = NULL; + } + } + + if( fSharedLock ) + { + ReleaseSRWLockShared( &m_srwLock ); + fSharedLock = FALSE; + } + + if( fExclusiveLock ) + { + ReleaseSRWLockExclusive( &m_srwLock ); + fExclusiveLock = FALSE; + } + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/protocolconfig.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/protocolconfig.cxx new file mode 100644 index 0000000000..83e2648925 --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/src/proxymodule.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/proxymodule.cxx new file mode 100644 index 0000000000..4a4e8fb6bd --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/proxymodule.cxx @@ -0,0 +1,114 @@ +// 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) + { + // + // This will be called when the main notification is cleaned up + // i.e., the request is done with IIS pipeline + // + 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/AspNetCoreModuleV1/AspNetCore/src/responseheaderhash.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/responseheaderhash.cxx new file mode 100644 index 0000000000..161095042c --- /dev/null +++ b/src/AspNetCoreModuleV1/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/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx new file mode 100644 index 0000000000..a64b6cae28 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx @@ -0,0 +1,2350 @@ +// 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, + BOOL fWindowsAuthEnabled, + BOOL fBasicAuthEnabled, + BOOL fAnonymousAuthEnabled, + ENVIRONMENT_VAR_HASH *pEnvironmentVariables, + 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; + m_fWindowsAuthEnabled = fWindowsAuthEnabled; + m_fBasicAuthEnabled = fBasicAuthEnabled; + m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; + m_pProcessManager->ReferenceProcessManager(); + + if (FAILED (hr = m_ProcessPath.Copy(*pszProcessExePath)) || + FAILED (hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| + FAILED (hr = m_Arguments.Copy(*pszArguments))) + { + goto Finished; + } + + if (m_hJobObject == NULL) + { + m_hJobObject = CreateJobObject(NULL, // LPSECURITY_ATTRIBUTES + NULL); // LPCTSTR lpName +#pragma warning( disable : 4312) + // 0xdeadbeef is used by Antares + if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef) + { + m_hJobObject = NULL; + // ignore job object creation error. + } +#pragma warning( error : 4312) + if (m_hJobObject != NULL) + { + jobInfo.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + + if (!SetInformationJobObject(m_hJobObject, + JobObjectExtendedLimitInformation, + &jobInfo, + sizeof jobInfo)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + + m_pEnvironmentVarTable = pEnvironmentVariables; + } + +Finished: + return hr; +} + +HRESULT +SERVER_PROCESS::GetRandomPort +( + DWORD* pdwPickedPort, + DWORD dwExcludedPort = 0 +) +{ + HRESULT hr = S_OK; + BOOL fPortInUse = FALSE; + DWORD dwActualProcessId = 0; + + if (g_fNsiApiNotSupported) + { + // + // the default value for optional parameter dwExcludedPort is 0 which is reserved + // a random number between MIN_PORT and MAX_PORT + // + while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort); + } + else + { + DWORD cRetry = 0; + do + { + // + // ignore dwActualProcessId because here we are + // determing whether the randomly generated port is + // in use by any other process. + // + while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort); + hr = CheckIfServerIsUp(*pdwPickedPort, &dwActualProcessId, &fPortInUse); + } while (fPortInUse && ++cRetry < MAX_RETRY); + + if (cRetry >= MAX_RETRY) + { + hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); + } + } + + return hr; +} + +HRESULT +SERVER_PROCESS::SetupListenPort( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + ENVIRONMENT_VAR_ENTRY *pEntry = NULL; + pEnvironmentVarTable->FindKey(ASPNETCORE_PORT_ENV_STR, &pEntry); + if (pEntry != NULL) + { + pEntry->Dereference(); + if (pEntry->QueryValue() != NULL || pEntry->QueryValue()[0] != L'\0') + { + m_dwPort = (DWORD)_wtoi(pEntry->QueryValue()); + if(m_dwPort >MAX_PORT || m_dwPort < MIN_PORT) + { + hr = E_INVALIDARG; + goto Finished; + // need add log for this one + } + hr = m_struPort.Copy(pEntry->QueryValue()); + goto Finished; + } + else + { + // + // user set the env variable but did not give value, let's set it up + // + pEnvironmentVarTable->DeleteKey(ASPNETCORE_PORT_ENV_STR); + } + } + + WCHAR buffer[15]; + if (FAILED(hr = GetRandomPort(&m_dwPort))) + { + goto Finished; + } + + if (swprintf_s(buffer, 15, L"%d", m_dwPort) <= 0) + { + hr = E_INVALIDARG; + goto Finished; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry)) || + FAILED(hr = m_struPort.Copy(buffer))) + { + goto Finished; + } + +Finished: + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupAppPath( + 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//. + pszPath = m_struAppFullPath.QueryStr(); + while (pszPath[dwPosition] != NULL) + { + if (pszPath[dwPosition] == '/') + { + dwCounter++; + if (dwCounter == 4) + break; + } + dwPosition++; + } + + if (dwCounter == 4) + { + hr = m_struAppPath.Copy(pszPath + dwPosition); + } + else + { + hr = m_struAppPath.Copy(L"/"); + } + + if (FAILED(hr)) + { + goto Finished; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED (hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppPath.QueryStr())) || + FAILED (hr = pEnvironmentVarTable->InsertRecord(pEntry))) + { + goto Finished; + } + +Finished: + if (pEntry!= NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupAppToken( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + UUID logUuid; + PSTR pszLogUuid = NULL; + BOOL fRpcStringAllocd = FALSE; + RPC_STATUS rpcStatus; + STRU strAppToken; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + pEnvironmentVarTable->FindKey(ASPNETCORE_APP_TOKEN_ENV_STR, &pEntry); + if (pEntry != NULL) + { + // user sets the environment variable + m_straGuid.Reset(); + hr = m_straGuid.CopyW(pEntry->QueryValue()); + pEntry->Dereference(); + pEntry = NULL; + goto Finished; + } + else + { + if (m_straGuid.IsEmpty()) + { + // the GUID has not been set yet + rpcStatus = UuidCreate(&logUuid); + if (rpcStatus != RPC_S_OK) + { + hr = rpcStatus; + goto Finished; + } + + rpcStatus = UuidToStringA(&logUuid, (BYTE **)&pszLogUuid); + if (rpcStatus != RPC_S_OK) + { + hr = rpcStatus; + goto Finished; + } + + fRpcStringAllocd = TRUE; + + if (FAILED (hr = m_straGuid.Copy(pszLogUuid))) + { + goto Finished; + } + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(strAppToken.CopyA(m_straGuid.QueryStr())) || + FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_TOKEN_ENV_STR, strAppToken.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry))) + { + goto Finished; + } + } + +Finished: + + if (fRpcStringAllocd) + { + RpcStringFreeA((BYTE **)&pszLogUuid); + pszLogUuid = NULL; + } + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + + +HRESULT +SERVER_PROCESS::InitEnvironmentVariablesTable( + ENVIRONMENT_VAR_HASH** ppEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + BOOL fFound = FALSE; + DWORD dwResult, dwError; + STRU strIisAuthEnvValue; + STACK_STRU(strStartupAssemblyEnv, 1024); + ENVIRONMENT_VAR_ENTRY* pHostingEntry = NULL; + ENVIRONMENT_VAR_ENTRY* pIISAuthEntry = NULL; + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL; + + pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH(); + if (pEnvironmentVarTable == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // few environment variables expected, small bucket size for hash table + // + if (FAILED(hr = pEnvironmentVarTable->Initialize(37 /*prime*/))) + { + goto Finished; + } + + // copy the envirable hash table (from configuration) to a temp one as we may need to remove elements + m_pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToTable, pEnvironmentVarTable); + if (pEnvironmentVarTable->Count() != m_pEnvironmentVarTable->Count()) + { + // hash table copy failed + hr = E_UNEXPECTED; + goto Finished; + } + + pEnvironmentVarTable->FindKey(ASPNETCORE_IIS_AUTH_ENV_STR, &pIISAuthEntry); + if (pIISAuthEntry != NULL) + { + // user defined ASPNETCORE_IIS_HTTPAUTH in configuration, wipe it off + pIISAuthEntry->Dereference(); + pEnvironmentVarTable->DeleteKey(ASPNETCORE_IIS_AUTH_ENV_STR); + } + + if (m_fWindowsAuthEnabled) + { + strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_WINDOWS); + } + + if (m_fBasicAuthEnabled) + { + strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_BASIC); + } + + if (m_fAnonymousAuthEnabled) + { + strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_ANONYMOUS); + } + + if (strIisAuthEnvValue.IsEmpty()) + { + strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_NONE); + } + + pIISAuthEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pIISAuthEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pIISAuthEntry->Initialize(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISAuthEntry))) + { + goto Finished; + } + + + pEnvironmentVarTable->FindKey(HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry); + if (pHostingEntry !=NULL ) + { + // user defined ASPNETCORE_HOSTINGSTARTUPASSEMBLIES in configuration + // the value will be used in OutputEnvironmentVariables. Do nothing here + pHostingEntry->Dereference(); + pHostingEntry = NULL; + goto Skipped; + } + + //check whether ASPNETCORE_HOSTINGSTARTUPASSEMBLIES is defined in system + dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, + strStartupAssemblyEnv.QueryStr(), + strStartupAssemblyEnv.QuerySizeCCH()); + if (dwResult == 0) + { + dwError = GetLastError(); + + // Windows API (e.g., CreateProcess) allows variable with empty string value + // in such case dwResult will be 0 and dwError will also be 0 + // As UI and CMD does not allow empty value, ignore this environment var + if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + else if (dwResult > strStartupAssemblyEnv.QuerySizeCCH()) + { + // have to increase the buffer and try get environment var again + strStartupAssemblyEnv.Reset(); + strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) +1); + dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, + strStartupAssemblyEnv.QueryStr(), + strStartupAssemblyEnv.QuerySizeCCH()); + if (strStartupAssemblyEnv.IsEmpty()) + { + hr = E_UNEXPECTED; + goto Finished; + } + fFound = TRUE; + } + else + { + fFound = TRUE; + } + + strStartupAssemblyEnv.SyncWithBuffer(); + if (fFound) + { + strStartupAssemblyEnv.Append(L";"); + } + strStartupAssemblyEnv.Append(HOSTING_STARTUP_ASSEMBLIES_VALUE); + + // the environment variable was not defined, create it and add to hashtable + pHostingEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pHostingEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pHostingEntry->Initialize(HOSTING_STARTUP_ASSEMBLIES_NAME, strStartupAssemblyEnv.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pHostingEntry))) + { + goto Finished; + } + +Skipped: + *ppEnvironmentVarTable = pEnvironmentVarTable; + pEnvironmentVarTable = NULL; + +Finished: + if (pHostingEntry != NULL) + { + pHostingEntry->Dereference(); + pHostingEntry = NULL; + } + + if (pIISAuthEntry != NULL) + { + pIISAuthEntry->Dereference(); + pIISAuthEntry = NULL; + } + + if (pEnvironmentVarTable != NULL) + { + pEnvironmentVarTable->Clear(); + delete pEnvironmentVarTable; + pEnvironmentVarTable = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::OutputEnvironmentVariables +( + MULTISZ* pmszOutput, + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + LPWSTR pszEnvironmentVariables = NULL; + LPWSTR pszCurrentVariable = NULL; + LPWSTR pszNextVariable = NULL; + LPWSTR pszEqualChar = NULL; + STRU strEnvVar; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + DBG_ASSERT(pmszOutput); + DBG_ASSERT(pEnvironmentVarTable); // We added some startup variables + DBG_ASSERT(pEnvironmentVarTable->Count() >0); + + pszEnvironmentVariables = GetEnvironmentStringsW(); + if (pszEnvironmentVariables == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); + goto Finished; + } + pszCurrentVariable = pszEnvironmentVariables; + while (*pszCurrentVariable != L'\0') + { + pszNextVariable = pszCurrentVariable + wcslen(pszCurrentVariable) + 1; + pszEqualChar = wcschr(pszCurrentVariable, L'='); + if (pszEqualChar != NULL) + { + if (FAILED(hr = strEnvVar.Copy(pszCurrentVariable, (DWORD)(pszEqualChar - pszCurrentVariable) + 1))) + { + goto Finished; + } + pEnvironmentVarTable->FindKey(strEnvVar.QueryStr(), &pEntry); + if (pEntry != NULL) + { + // same env variable is defined in configuration, use it + if (FAILED(hr = strEnvVar.Append(pEntry->QueryValue()))) + { + goto Finished; + } + pmszOutput->Append(strEnvVar); //should we check the returned bool + // remove the record from hash table as we already output it + pEntry->Dereference(); + pEnvironmentVarTable->DeleteKey(pEntry->QueryName()); + strEnvVar.Reset(); + pEntry = NULL; + } + else + { + pmszOutput->Append(pszCurrentVariable); + } + } + else + { + // env varaible is not well formated + hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); + goto Finished; + } + // move to next env variable + pszCurrentVariable = pszNextVariable; + } + // append the remaining env variable in hash table + pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToMultiSz, pmszOutput); + +Finished: + if (pszEnvironmentVariables != NULL) + { + FreeEnvironmentStringsW(pszEnvironmentVariables); + pszEnvironmentVariables = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupCommandLine( + STRU* pstrCommandLine +) +{ + HRESULT hr = S_OK; + LPWSTR pszPath = NULL; + LPWSTR pszFullPath = NULL; + STRU strRelativePath; + DWORD dwBufferSize = 0; + FILE *file = NULL; + + DBG_ASSERT(pstrCommandLine); + + pszPath = m_ProcessPath.QueryStr(); + + if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL)) + { + // let's check whether it is a relative path + if (FAILED(hr = strRelativePath.Copy(m_pszRootApplicationPath.QueryStr())) || + FAILED(hr = strRelativePath.Append(L"\\")) || + FAILED(hr = strRelativePath.Append(pszPath))) + { + goto Finished; + } + + dwBufferSize = strRelativePath.QueryCCH() + 1; + pszFullPath = new WCHAR[dwBufferSize]; + if (pszFullPath == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (_wfullpath(pszFullPath, + strRelativePath.QueryStr(), + dwBufferSize) == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + goto Finished; + } + + if ((file = _wfsopen(pszFullPath, L"r", _SH_DENYNO)) != NULL) + { + fclose(file); + pszPath = pszFullPath; + } + } + if (FAILED(hr = pstrCommandLine->Copy(pszPath)) || + FAILED(hr = pstrCommandLine->Append(L" ")) || + FAILED(hr = pstrCommandLine->Append(m_Arguments.QueryStr()))) + { + goto Finished; + } + +Finished: + if (pszFullPath != NULL) + { + delete pszFullPath; + } + return hr; +} + + +HRESULT +SERVER_PROCESS::PostStartCheck( + const STRU* const pStruCommandline, + STRU* pStruErrorMessage) +{ + HRESULT hr = S_OK; + + BOOL fReady = FALSE; + BOOL fProcessMatch = FALSE; + BOOL fDebuggerAttached = FALSE; + DWORD dwTickCount = 0; + DWORD dwTimeDifference = 0; + DWORD dwActualProcessId = 0; + INT iChildProcessIndex = -1; + + if (CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + + dwTickCount = GetTickCount(); + do + { + DWORD processStatus; + if (GetExitCodeProcess(m_hProcessHandle, &processStatus)) + { + // make sure the process is still running + if (processStatus != STILL_ACTIVE) + { + hr = E_FAIL; + pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), + hr, + processStatus); + goto Finished; + } + } + // + // dwActualProcessId will be set only when NsiAPI(GetExtendedTcpTable) is supported + // + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); + fDebuggerAttached = IsDebuggerIsAttached(); + + if (!fReady) + { + Sleep(250); + } + + dwTimeDifference = (GetTickCount() - dwTickCount); + } while (fReady == FALSE && + ((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttached)); + + // register call back with the created process + if (FAILED(hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle))) + { + goto Finished; + } + + // + // check if debugger is attached after startupTimeout. + // + if (!fDebuggerAttached && + CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + if (!g_fNsiApiNotSupported) + { + // + // NsiAPI(GetExtendedTcpTable) is supported. we should check whether processIds matche + // + if (dwActualProcessId == m_dwProcessId) + { + m_dwListeningProcessId = m_dwProcessId; + fProcessMatch = TRUE; + } + + if (!fProcessMatch) + { + // could be the scenario that backend creates child process + if (FAILED(hr = GetChildProcessHandles())) + { + goto Finished; + } + + for (DWORD i = 0; i < m_cChildProcess; ++i) + { + // a child process listen on the assigned port + if (dwActualProcessId == m_dwChildProcessIds[i]) + { + m_dwListeningProcessId = m_dwChildProcessIds[i]; + fProcessMatch = TRUE; + + if (m_hChildProcessHandles[i] != NULL) + { + if (fDebuggerAttached == FALSE && + CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + + if (FAILED(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], + m_hChildProcessHandles[i]))) + { + goto Finished; + } + iChildProcessIndex = i; + } + break; + } + } + } + + if(!fProcessMatch) + { + // + // process that we created is not listening + // on the port we specified. + // + fReady = FALSE; + pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), + m_dwPort, + hr); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Finished; + } + } + + if (!fReady) + { + // + // hr is already set by CheckIfServerIsUp + // + if (dwTimeDifference >= m_dwStartupTimeLimitInMS) + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), + m_dwPort, + hr); + } + goto Finished; + } + + if (iChildProcessIndex >= 0) + { + // + // final check to make sure child process listening on HTTP is still UP + // This is needed because, the child process might have crashed/exited between + // the previous call to checkIfServerIsUp and RegisterProcessWait + // and we would not know about it. + // + + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); + + if ((FAILED(hr) || fReady == FALSE)) + { + pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), + m_dwPort, + hr); + goto Finished; + } + } + + // + // ready to mark the server process ready but before this, + // create and initialize the FORWARDER_CONNECTION + // + if (m_pForwarderConnection != NULL) + { + m_pForwarderConnection->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; + +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, + m_struAppFullPath.QueryStr(), + m_dwProcessId, + m_dwPort))) + { + apsz[0] = strEventMsg.QueryStr(); + + // + // not checking return code because if ReportEvent + // fails, we cannot do anything. + // + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_PROCESS_START_SUCCESS, + NULL, + 1, + 0, + apsz, + NULL); + } + } + +Finished: + if (processInformation.hThread != NULL) + { + CloseHandle(processInformation.hThread); + processInformation.hThread = NULL; + } + + if (pHashTable != NULL) + { + pHashTable->Clear(); + delete pHashTable; + pHashTable = NULL; + } + + if ( FAILED(hr) ) + { + if (strEventMsg.IsEmpty()) + { + if (!fDonePrepareCommandLine) + strEventMsg.SafeSnwprintf( + m_struAppFullPath.QueryStr(), + ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, + hr); + else + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + struCommandLine.QueryStr(), + hr); + } + + apsz[0] = strEventMsg.QueryStr(); + + // not checking return code because if ReportEvent + // fails, we cannot do anything. + // + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_ERROR_TYPE, + 0, + ASPNETCORE_EVENT_PROCESS_START_ERROR, + NULL, + 1, + 0, + apsz, + NULL); + } + } + + if ( FAILED( hr ) || m_fReady == FALSE) + { + if (m_hStdoutHandle != NULL) + { + if ( m_hStdoutHandle != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hStdoutHandle ); + } + m_hStdoutHandle = NULL; + } + + if ( m_fStdoutLogEnabled ) + { + m_Timer.CancelTimer(); + } + + if (m_hListeningProcessHandle != NULL) + { + if( m_hListeningProcessHandle != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hListeningProcessHandle ); + } + m_hListeningProcessHandle = NULL; + } + + if ( m_hProcessWaitHandle != NULL ) + { + UnregisterWait( m_hProcessWaitHandle ); + m_hProcessWaitHandle = NULL; + } + + StopProcess(); + + StopAllProcessesInJobObject(); + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetWindowsAuthToken( + HANDLE hToken, + LPHANDLE pTargetTokenHandle +) +{ + HRESULT hr = S_OK; + + if ( m_hListeningProcessHandle != NULL && m_hListeningProcessHandle != INVALID_HANDLE_VALUE ) + { + if (!DuplicateHandle( GetCurrentProcess(), + hToken, + m_hListeningProcessHandle, + pTargetTokenHandle, + 0, + FALSE, + DUPLICATE_SAME_ACCESS )) + { + hr = HRESULT_FROM_GETLASTERROR(); + goto Finished; + } + } + +Finished: + + return hr; +} + +HRESULT +SERVER_PROCESS::SetupStdHandles( + IHttpContext *context, + LPSTARTUPINFOW pStartupInfo +) +{ + SECURITY_ATTRIBUTES saAttr = {0}; + HRESULT hr = S_OK; + SYSTEMTIME systemTime; + STRU struLogFileName; + BOOL fStdoutLoggingFailed = FALSE; + STRU strEventMsg; + LPCWSTR apsz[1]; + STRU struAbsLogFilePath; + + DBG_ASSERT(pStartupInfo); + + if ( m_fStdoutLogEnabled ) + { + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (m_hStdoutHandle != NULL) + { + if(!CloseHandle( m_hStdoutHandle )) + { + hr = HRESULT_FROM_GETLASTERROR(); + goto Finished; + } + + m_hStdoutHandle = NULL; + } + + hr = PATH::ConvertPathToFullPath( m_struLogFile.QueryStr(), + context->GetApplication()->GetApplicationPhysicalPath(), + &struAbsLogFilePath ); + if (FAILED(hr)) + { + 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_ DWORD * pdwProcessId, + _Out_ BOOL * pfReady +) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + MIB_TCPTABLE_OWNER_PID *pTCPInfo = NULL; + MIB_TCPROW_OWNER_PID *pOwner = NULL; + DWORD dwSize = 0; + int iResult = 0; + SOCKADDR_IN sockAddr; + SOCKET socketCheck = INVALID_SOCKET; + + DBG_ASSERT(pfReady); + DBG_ASSERT(pdwProcessId); + + *pfReady = FALSE; + // + // it's OK for us to return processID 0 in case we cannot detect the real one + // + *pdwProcessId = 0; + + if (!g_fNsiApiNotSupported) + { + dwResult = GetExtendedTcpTable(NULL, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + + if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) + { + hr = HRESULT_FROM_WIN32(dwResult); + goto Finished; + } + + pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize); + if (pTCPInfo == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + dwResult = GetExtendedTcpTable(pTCPInfo, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + if (dwResult != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwResult); + goto Finished; + } + + // iterate pTcpInfo struct to find PID/PORT entry + for (DWORD dwLoop = 0; dwLoop < pTCPInfo->dwNumEntries; dwLoop++) + { + pOwner = &pTCPInfo->table[dwLoop]; + if (ntohs((USHORT)pOwner->dwLocalPort) == dwPort) + { + *pdwProcessId = pOwner->dwOwningPid; + *pfReady = TRUE; + break; + } + } + } + else + { + // + // We have to open socket to ping the service + // + socketCheck = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (socketCheck == INVALID_SOCKET) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + sockAddr.sin_family = AF_INET; + if (!inet_pton(AF_INET, LOCALHOST, &(sockAddr.sin_addr))) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + //sockAddr.sin_addr.s_addr = inet_addr( LOCALHOST ); + sockAddr.sin_port = htons((u_short)dwPort); + + // + // Connect to server. + // + iResult = connect(socketCheck, (SOCKADDR *)&sockAddr, sizeof(sockAddr)); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + *pfReady = TRUE; + } + +Finished: + + if (socketCheck != INVALID_SOCKET) + { + iResult = closesocket(socketCheck); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + } + socketCheck = INVALID_SOCKET; + } + + if( pTCPInfo != NULL ) + { + HeapFree( GetProcessHeap(), 0, pTCPInfo ); + pTCPInfo = NULL; + } + + return hr; +} + +// send signal to the process to let it gracefully shutdown +// if the process cannot shutdown within given time, terminate it +VOID +SERVER_PROCESS::SendSignal( + VOID +) +{ + HRESULT hr = S_OK; + HANDLE hThread = NULL; + + ReferenceServerProcess(); + + m_hShutdownHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); + + if (m_hShutdownHandle == NULL) + { + // since we cannot open the process. let's terminate the process + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hThread = CreateThread( + NULL, // default security attributes + 0, // default stack size + (LPTHREAD_START_ROUTINE)SendShutDownSignal, + this, // thread function arguments + 0, // default creation flags + NULL); // receive thread identifier + + if (hThread == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (WaitForSingleObject(m_hShutdownHandle, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + goto Finished; + } + + +Finished: + if (hThread != NULL) + { + // if the send shutdown message thread is still running, terminate it + DWORD dwThreadStatus = 0; + if (GetExitCodeThread(hThread, &dwThreadStatus)!= 0 && dwThreadStatus == STILL_ACTIVE) + { + TerminateThread(hThread, STATUS_CONTROL_C_EXIT); + } + CloseHandle(hThread); + hThread = NULL; + } + + if (FAILED(hr)) + { + TerminateBackendProcess(); + } + + if (m_hShutdownHandle != NULL && m_hShutdownHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hShutdownHandle); + m_hShutdownHandle = NULL; + } + + DereferenceServerProcess(); +} + +// +// StopProcess is only called if process crashes OR if the process +// creation failed and calling this counts towards RapidFailCounts. +// + +VOID +SERVER_PROCESS::StopProcess( + VOID +) +{ + m_fReady = FALSE; + + m_pProcessManager->IncrementRapidFailCount(); + + for(INT i=0;iNumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0 ) ); + + if( dwError == ERROR_MORE_DATA ) + { + hr = E_OUTOFMEMORY; + // some error + goto Finished; + } + + if( processList == NULL || + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0 ) ) + { + hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED); + // some error + goto Finished; + } + + if( processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES ) + { + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + goto Finished; + } + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + { + dwPid = (DWORD)processList->ProcessIdList[i]; + if( dwPid != dwWorkerProcessPid ) + { + HANDLE hProcess = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); + + BOOL returnValue = CheckRemoteDebuggerPresent( hProcess, &fDebuggerPresent ); + if (hProcess != NULL) + { + CloseHandle(hProcess); + hProcess = NULL; + } + + if( ! returnValue ) + { + goto Finished; + } + + if( fDebuggerPresent ) + { + break; + } + } + } + +Finished: + + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return fDebuggerPresent; +} + +HRESULT +SERVER_PROCESS::GetChildProcessHandles( + VOID +) +{ + HRESULT hr = S_OK; + PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL; + DWORD dwPid = 0; + DWORD dwWorkerProcessPid = 0; + DWORD cbNumBytes = 1024; + DWORD dwRetries = 0; + DWORD dwError = NO_ERROR; + + dwWorkerProcessPid = GetCurrentProcessId(); + + do + { + dwError = NO_ERROR; + + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + processList = NULL; + + // resize + cbNumBytes = cbNumBytes * 2; + } + + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if( processList == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + RtlZeroMemory( processList, cbNumBytes ); + + if( !QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL) ) + { + dwError = GetLastError(); + if( dwError != ERROR_MORE_DATA ) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + + } while( dwRetries++ < 5 && + processList != NULL && + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); + + if( dwError == ERROR_MORE_DATA ) + { + hr = E_OUTOFMEMORY; + // some error + goto Finished; + } + + if( processList == NULL || ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ) + { + hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED); + // some error + goto Finished; + } + + if( processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES ) + { + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + goto Finished; + } + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + { + dwPid = (DWORD)processList->ProcessIdList[i]; + if( dwPid != m_dwProcessId && + dwPid != dwWorkerProcessPid ) + { + m_hChildProcessHandles[m_cChildProcess] = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); + m_dwChildProcessIds[m_cChildProcess] = dwPid; + m_cChildProcess ++; + } + } + +Finished: + + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return hr; +} + +HRESULT +SERVER_PROCESS::StopAllProcessesInJobObject( + VOID +) +{ + HRESULT hr = S_OK; + PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL; + HANDLE hProcess = NULL; + DWORD dwWorkerProcessPid = 0; + DWORD cbNumBytes = 1024; + DWORD dwRetries = 0; + + dwWorkerProcessPid = GetCurrentProcessId(); + + do + { + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + processList = NULL; + + // resize + cbNumBytes = cbNumBytes * 2; + } + + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if( processList == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + RtlZeroMemory( processList, cbNumBytes ); + + if( !QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL) ) + { + DWORD dwError = GetLastError(); + if( dwError != ERROR_MORE_DATA ) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + + } while( dwRetries++ < 5 && + processList != NULL && + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); + + if( processList == NULL || ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + // some error + goto Finished; + } + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + { + if( dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i] ) + { + hProcess = OpenProcess( PROCESS_TERMINATE, + FALSE, + (DWORD)processList->ProcessIdList[i] ); + if( hProcess != NULL ) + { + if( !TerminateProcess(hProcess, 1) ) + { + hr = HRESULT_FROM_GETLASTERROR(); + } + else + { + WaitForSingleObject( hProcess, INFINITE ); + } + + if( hProcess != NULL ) + { + CloseHandle( hProcess ); + hProcess = NULL; + } + } + } + } + +Finished: + + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return hr; +} + +SERVER_PROCESS::SERVER_PROCESS() : + m_cRefs( 1 ), + m_hProcessHandle( NULL ), + m_hProcessWaitHandle( NULL ), + m_dwProcessId( 0 ), + m_cChildProcess( 0 ), + m_fReady( FALSE ), + m_lStopping( 0L ), + m_hStdoutHandle( NULL ), + m_fStdoutLogEnabled( FALSE ), + m_hJobObject( NULL ), + m_pForwarderConnection( NULL ), + m_dwListeningProcessId( 0 ), + m_hListeningProcessHandle( NULL ), + m_hShutdownHandle( NULL ) +{ + InterlockedIncrement(&g_dwActiveServerProcesses); + srand(GetTickCount()); + + for(INT i=0;iDereferenceProcessManager(); + m_pProcessManager = NULL; + } + + if(m_pForwarderConnection != NULL) + { + m_pForwarderConnection->DereferenceForwarderConnection(); + m_pForwarderConnection = NULL; + } + + m_pEnvironmentVarTable = NULL; + // no need to free m_pEnvironmentVarTable, as it references to + // the same hash table hold by configuration. + // the hashtable memory will be freed once onfiguration got recycled + + InterlockedDecrement(&g_dwActiveServerProcesses); +} + +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; + DWORD dwProcessId = 0; + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + CheckIfServerIsUp(m_dwPort, &dwProcessId, &fReady); + + if (!fReady) + { + m_pProcessManager->ShutdownProcess(this); + } + + DereferenceServerProcess(); + } + + return hr; +} + +HRESULT +SERVER_PROCESS::SendShutdownHttpMessage() +{ + HRESULT hr = S_OK; + HINTERNET hSession = NULL, + hConnect = NULL, + hRequest = NULL; + + STACK_STRU(strHeaders, 256); + STRU strAppToken; + STRU strUrl; + DWORD dwStatusCode = 0; + DWORD dwSize = sizeof(dwStatusCode); + + LPCWSTR apsz[1]; + STACK_STRU(strEventMsg, 256); + + hSession = WinHttpOpen(L"", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (hSession == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hConnect = WinHttpConnect(hSession, + L"127.0.0.1", + (USHORT)m_dwPort, + 0); + + if (hConnect == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + if (m_struAppPath.QueryCCH() > 1) + { + // app path size is 1 means site root, i.e., "/" + // we don't want to add duplicated '/' to the request url + // otherwise the request will fail + strUrl.Copy(m_struAppPath); + } + strUrl.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); + } + } + else + { + // terminate the backend process immediately instead of waiting for timeout + TerminateBackendProcess(); + } + } + + DereferenceServerProcess(); +} + +VOID +SERVER_PROCESS::TerminateBackendProcess( + VOID +) +{ + LPCWSTR apsz[1]; + STACK_STRU(strEventMsg, 256); + + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + // backend process will be terminated, remove the waitcallback + if (m_hProcessWaitHandle != NULL) + { + UnregisterWait(m_hProcessWaitHandle); + 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 diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/websockethandler.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/websockethandler.cxx new file mode 100644 index 0000000000..7adc14c915 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/websockethandler.cxx @@ -0,0 +1,1169 @@ +// 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 ), + _hWebSocketRequest( NULL ), + _pHandler ( NULL ), + _dwOutstandingIo ( 0 ), + _fCleanupInProgress ( FALSE ), + _fIndicateCompletionToIis ( FALSE ), + _fHandleClosed( FALSE ), + _fReceivedCloseMsg ( FALSE ) +{ + DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::WEBSOCKET_HANDLER"); + + InitializeCriticalSectionAndSpinCount(&_RequestLock, 1000); + + InsertRequest(); +} + +WEBSOCKET_HANDLER::~WEBSOCKET_HANDLER() +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::~WEBSOCKET_HANDLER"); + RemoveRequest(); + DeleteCriticalSection(&_RequestLock); +} + +VOID +WEBSOCKET_HANDLER::Terminate( + VOID + ) +{ + DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::Terminate"); + if (!_fHandleClosed) + { + EnterCriticalSection(&_RequestLock); + _fCleanupInProgress = TRUE; + if (_pHttpContext != NULL) + { + _pHttpContext->CancelIo(); + _pHttpContext = NULL; + } + + if (_hWebSocketRequest != NULL) + { + WinHttpCloseHandle(_hWebSocketRequest); + _hWebSocketRequest = NULL; + } + + _fHandleClosed = TRUE; + LeaveCriticalSection(&_RequestLock); + } +} + +//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 + ) +{ + LONG dwOutstandingIo = 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 called %d", _dwOutstandingIo); + // + // close Websocket handle. This will triger a WinHttp callback + // on handle close, then let IIS pipeline continue. + // Make sure no pending IO as there is no IIS websocket cancelation, + // any unexpected callback will lead to AV. Revisit it once CanelOutGoingIO works + // + if (_hWebSocketRequest != NULL && _dwOutstandingIo == 0) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::IndicateCompletionToIIS"); + + _pHandler->SetStatus(FORWARDER_DONE); + _fHandleClosed = TRUE; + WinHttpCloseHandle(_hWebSocketRequest); + _hWebSocketRequest = NULL; + } +} + +HRESULT +WEBSOCKET_HANDLER::ProcessRequest( + FORWARDING_HANDLER *pHandler, + IHttpContext *pHttpContext, + HINTERNET hRequest, + BOOL* fHandleCreated +) +/*++ + +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; + } + + *fHandleCreated = TRUE; + // + // 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 %d", eBufferType); + + if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) + { + // + // Query Close Status from WinHttp + // + + DWORD dwError = NO_ERROR; + USHORT uStatus; + DWORD dwReceived = 0; + STACK_STRU(strCloseReason, 128); + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketQueryCloseStatus( + _hWebSocketRequest, + &uStatus, + &_WinHttpReceiveBuffer, + RECEIVE_BUFFER_SIZE, + &dwReceived); + + if (dwError != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + + // + // Convert close reason to WCHAR + // + + hr = strCloseReason.CopyA((PCSTR)&_WinHttpReceiveBuffer, + dwReceived); + if (FAILED(hr)) + { + goto Finished; + } + + IncrementOutstandingIo(); + + // + // Backend end may start close hand shake first + // Need to inidcate no more receive should be called on WinHttp connection + // + _fReceivedCloseMsg = TRUE; + _fIndicateCompletionToIis = TRUE; + + // + // Send close to IIS. + // + + hr = _pWebSocketContext->SendConnectionClose( + TRUE, + uStatus, + uStatus == 1005 ? NULL : strCloseReason.QueryStr(), + OnWriteIoCompletion, + this, + NULL); + } + else + { + // + // Get equivalant flags for IIS API from buffer type. + // + + WINHTTP_HELPER::GetFlagsFromBufferType(eBufferType, + &fUtf8Encoded, + &fFinalFragment, + &fClose); + + IncrementOutstandingIo(); + + // + // Do the Send. + // + + hr = _pWebSocketContext->WriteFragment( + &_WinHttpReceiveBuffer, + &cbData, + TRUE, + fUtf8Encoded, + fFinalFragment, + OnWriteIoCompletion, + this, + NULL); + + } + + if (FAILED(hr)) + { + DecrementOutstandingIo(); + } + +Finished: + if (FAILED(hr)) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoWinHttpWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType +) +/*++ + +Routine Description: + + Initiates a websocket send on WinHttp + +--*/ +{ + DWORD dwError = NO_ERROR; + HRESULT hr = S_OK; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketSend, %d", eBufferType); + + if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) + { + USHORT uStatus; + LPCWSTR pszReason; + STACK_STRA(strCloseReason, 128); + + // + // Get Close status from IIS. + // + + hr = _pWebSocketContext->GetCloseStatus(&uStatus, + &pszReason); + + if (FAILED(hr)) + { + goto Finished; + } + + // + // Convert status to UTF8 + // + + hr = strCloseReason.CopyWToUTF8Unescaped(pszReason); + if (FAILED(hr)) + { + goto Finished; + } + + IncrementOutstandingIo(); + + // + // Send Close. + // + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketShutdown( + _hWebSocketRequest, + uStatus, + strCloseReason.QueryCCH() == 0 ? NULL : (PVOID) strCloseReason.QueryStr(), + strCloseReason.QueryCCH()); + + if (dwError == ERROR_IO_PENDING) + { + // + // Call will complete asynchronously, return. + // ignore error. + // + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend IO_PENDING"); + + dwError = NO_ERROR; + } + else + { + if (dwError == NO_ERROR) + { + // + // Call completed synchronously. + // + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend Shutdown successful."); + } + } + } + else + { + IncrementOutstandingIo(); + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketSend( + _hWebSocketRequest, + eBufferType, + cbData == 0 ? NULL : &_IisReceiveBuffer, + cbData + ); + } + + if (dwError != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwError); + DecrementOutstandingIo(); + goto Finished; + } + +Finished: + if (FAILED(hr)) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketSend failed with %08x", hr); + } + + return hr; +} + +//static +VOID +WINAPI +WEBSOCKET_HANDLER::OnReadIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ + + Routine Description: + + Completion routine for Read's from IIS pipeline. + +--*/ +{ + WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) + pvCompletionContext; + + pHandler->OnIisReceiveComplete( + hrError, + cbIO, + fUTF8Encoded, + fFinalFragment, + fClose + ); +} + +//static +VOID +WINAPI +WEBSOCKET_HANDLER::OnWriteIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ + Routine Description: + + Completion routine for Write's from IIS pipeline. + +--*/ +{ + WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) + pvCompletionContext; + + UNREFERENCED_PARAMETER(fUTF8Encoded); + UNREFERENCED_PARAMETER(fFinalFragment); + UNREFERENCED_PARAMETER(fClose); + + pHandler->OnIisSendComplete( + hrError, + cbIO + ); +} + + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpSendComplete( + WINHTTP_WEB_SOCKET_STATUS * + ) +/*++ + +Routine Description: + Completion callback executed when a send to backend + server completes. + + If the send was successful, issue the next read + on the client's endpoint. + +++*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpSendComplete"); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection (&_RequestLock); + + fLocked = TRUE; + + if (_fCleanupInProgress) + { + goto Finished; + } + // + // Data was successfully sent to backend. + // Initiate next receive from IIS. + // + + hr = DoIisWebSocketReceive(); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinsockSendComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpShutdownComplete( + VOID + ) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpShutdownComplete --%p", _pHandler); + + DecrementOutstandingIo(); + + return S_OK; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpIoError( + WINHTTP_WEB_SOCKET_ASYNC_RESULT *pCompletionStatus +) +{ + HRESULT hr = HRESULT_FROM_WIN32(pCompletionStatus->AsyncResult.dwError); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinHttpIoError HR = %08x, Operation = %d", + hr, pCompletionStatus->AsyncResult.dwResult); + + Cleanup(ServerDisconnect); + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpReceiveComplete( + WINHTTP_WEB_SOCKET_STATUS * pCompletionStatus + ) +/*++ + +Routine Description: + + Completion callback executed when a receive completes + on the backend server winhttp endpoint. + + Issue send on the Client(IIS) if the receive was + successful. + + If the receive completed with zero bytes, that + indicates that the server has disconnected the connection. + Issue cleanup for the websocket handler. +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpReceiveComplete, %d", _fCleanupInProgress); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } + hr = DoIisWebSocketSend( + pCompletionStatus->dwBytesTransferred, + pCompletionStatus->eBufferType + ); + + if (FAILED (hr)) + { + cleanupReason = ClientDisconnect; + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinsockReceiveComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnIisSendComplete( + HRESULT hrCompletion, + DWORD cbIo + ) +/*++ +Routine Description: + + Completion callback executed when a send + completes from the client. + + If send was successful,issue read on the + server endpoint, to continue the readloop. + +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + UNREFERENCED_PARAMETER(cbIo); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnIisSendComplete"); + + if (FAILED(hrCompletion)) + { + hr = hrCompletion; + cleanupReason = ClientDisconnect; + goto Finished; + } + + if (_fCleanupInProgress) + { + goto Finished; + } + EnterCriticalSection(&_RequestLock); + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } + + // + // Only call read if no close hand shake was received from backend + // + //if (!_fReceivedCloseMsg) + //{ + // + // Write Completed, initiate next read from backend server. + // + hr = DoWinHttpWebSocketReceive(); + if (FAILED(hr)) + { + cleanupReason = ServerDisconnect; + goto Finished; + } + //} + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnIisSendComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnIisReceiveComplete( + HRESULT hrCompletion, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ +Routine Description: + + Completion routine executed when a receive completes + from the client (IIS endpoint). + + If the receive was successful, initiate a send on + the backend server (winhttp) endpoint. + + If the receive failed, initiate cleanup. + +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnIisReceiveComplete"); + + if (FAILED(hrCompletion)) + { + cleanupReason = ClientDisconnect; + hr = hrCompletion; + goto Finished; + } + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + fLocked = TRUE; + + if (_fCleanupInProgress) + { + goto Finished; + } + // + // Get Buffer Type from flags. + // + + WINHTTP_HELPER::GetBufferTypeFromFlags(fUTF8Encoded, + fFinalFragment, + fClose, + &BufferType); + + // + // Initiate Send. + // + + hr = DoWinHttpWebSocketSend(cbIO, BufferType); + if (FAILED (hr)) + { + cleanupReason = ServerDisconnect; + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnIisReceiveComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +VOID +WEBSOCKET_HANDLER::Cleanup( + CleanupReason reason +) +/*++ + +Routine Description: + + Cleanup function for the websocket handler. + + Initiates cancelIo on the two IO endpoints: + IIS, WinHttp client. + +Arguments: + CleanupReason +--*/ +{ + BOOL fLocked = FALSE; + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::Cleanup Initiated with reason %d", reason); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + fLocked = TRUE; + + if (_fCleanupInProgress) + { + goto Finished; + } + + _fCleanupInProgress = TRUE; + + _fIndicateCompletionToIis = TRUE; + // + // We need cancel IO for fast error handling + // Reivist the code once CanelOutstandingIO api is available + // + /*if (_pWebSocketContext != NULL) + { + _pWebSocketContext->CancelOutstandingIO(); + }*/ + _pHttpContext->CancelIo(); + + // + // Don't close the handle here, + // as it trigger a WinHttp callback and let IIS pipeline continue + // Handle should be closed only in IndicateCompletionToIIS + IndicateCompletionToIIS(); +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + +} diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/winhttphelper.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/winhttphelper.cxx new file mode 100644 index 0000000000..8407c45830 --- /dev/null +++ b/src/AspNetCoreModuleV1/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/IISLib/IISLib.vcxproj b/src/AspNetCoreModuleV1/IISLib/IISLib.vcxproj similarity index 91% rename from src/IISLib/IISLib.vcxproj rename to src/AspNetCoreModuleV1/IISLib/IISLib.vcxproj index 7d51023d63..bb8795992b 100644 --- a/src/IISLib/IISLib.vcxproj +++ b/src/AspNetCoreModuleV1/IISLib/IISLib.vcxproj @@ -23,7 +23,7 @@ Win32Proj IISLib IISLib - 8.1 + 10.0.15063.0 @@ -68,7 +68,18 @@ - + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + diff --git a/src/IISLib/acache.cxx b/src/AspNetCoreModuleV1/IISLib/acache.cxx similarity index 100% rename from src/IISLib/acache.cxx rename to src/AspNetCoreModuleV1/IISLib/acache.cxx diff --git a/src/IISLib/acache.h b/src/AspNetCoreModuleV1/IISLib/acache.h similarity index 100% rename from src/IISLib/acache.h rename to src/AspNetCoreModuleV1/IISLib/acache.h diff --git a/src/IISLib/ahutil.cpp b/src/AspNetCoreModuleV1/IISLib/ahutil.cpp similarity index 100% rename from src/IISLib/ahutil.cpp rename to src/AspNetCoreModuleV1/IISLib/ahutil.cpp diff --git a/src/IISLib/ahutil.h b/src/AspNetCoreModuleV1/IISLib/ahutil.h similarity index 100% rename from src/IISLib/ahutil.h rename to src/AspNetCoreModuleV1/IISLib/ahutil.h diff --git a/src/IISLib/base64.cpp b/src/AspNetCoreModuleV1/IISLib/base64.cpp similarity index 100% rename from src/IISLib/base64.cpp rename to src/AspNetCoreModuleV1/IISLib/base64.cpp diff --git a/src/IISLib/base64.h b/src/AspNetCoreModuleV1/IISLib/base64.h similarity index 100% rename from src/IISLib/base64.h rename to src/AspNetCoreModuleV1/IISLib/base64.h diff --git a/src/IISLib/buffer.h b/src/AspNetCoreModuleV1/IISLib/buffer.h similarity index 100% rename from src/IISLib/buffer.h rename to src/AspNetCoreModuleV1/IISLib/buffer.h diff --git a/src/IISLib/datetime.h b/src/AspNetCoreModuleV1/IISLib/datetime.h similarity index 100% rename from src/IISLib/datetime.h rename to src/AspNetCoreModuleV1/IISLib/datetime.h diff --git a/src/IISLib/dbgutil.h b/src/AspNetCoreModuleV1/IISLib/dbgutil.h similarity index 100% rename from src/IISLib/dbgutil.h rename to src/AspNetCoreModuleV1/IISLib/dbgutil.h diff --git a/src/IISLib/hashfn.h b/src/AspNetCoreModuleV1/IISLib/hashfn.h similarity index 100% rename from src/IISLib/hashfn.h rename to src/AspNetCoreModuleV1/IISLib/hashfn.h diff --git a/src/IISLib/hashtable.h b/src/AspNetCoreModuleV1/IISLib/hashtable.h similarity index 100% rename from src/IISLib/hashtable.h rename to src/AspNetCoreModuleV1/IISLib/hashtable.h diff --git a/src/IISLib/listentry.h b/src/AspNetCoreModuleV1/IISLib/listentry.h similarity index 100% rename from src/IISLib/listentry.h rename to src/AspNetCoreModuleV1/IISLib/listentry.h diff --git a/src/IISLib/macros.h b/src/AspNetCoreModuleV1/IISLib/macros.h similarity index 100% rename from src/IISLib/macros.h rename to src/AspNetCoreModuleV1/IISLib/macros.h diff --git a/src/IISLib/multisz.cpp b/src/AspNetCoreModuleV1/IISLib/multisz.cpp similarity index 100% rename from src/IISLib/multisz.cpp rename to src/AspNetCoreModuleV1/IISLib/multisz.cpp diff --git a/src/IISLib/multisz.h b/src/AspNetCoreModuleV1/IISLib/multisz.h similarity index 100% rename from src/IISLib/multisz.h rename to src/AspNetCoreModuleV1/IISLib/multisz.h diff --git a/src/IISLib/multisza.cpp b/src/AspNetCoreModuleV1/IISLib/multisza.cpp similarity index 100% rename from src/IISLib/multisza.cpp rename to src/AspNetCoreModuleV1/IISLib/multisza.cpp diff --git a/src/IISLib/multisza.h b/src/AspNetCoreModuleV1/IISLib/multisza.h similarity index 100% rename from src/IISLib/multisza.h rename to src/AspNetCoreModuleV1/IISLib/multisza.h diff --git a/src/IISLib/ntassert.h b/src/AspNetCoreModuleV1/IISLib/ntassert.h similarity index 100% rename from src/IISLib/ntassert.h rename to src/AspNetCoreModuleV1/IISLib/ntassert.h diff --git a/src/IISLib/percpu.h b/src/AspNetCoreModuleV1/IISLib/percpu.h similarity index 100% rename from src/IISLib/percpu.h rename to src/AspNetCoreModuleV1/IISLib/percpu.h diff --git a/src/IISLib/precomp.h b/src/AspNetCoreModuleV1/IISLib/precomp.h similarity index 100% rename from src/IISLib/precomp.h rename to src/AspNetCoreModuleV1/IISLib/precomp.h diff --git a/src/IISLib/prime.h b/src/AspNetCoreModuleV1/IISLib/prime.h similarity index 100% rename from src/IISLib/prime.h rename to src/AspNetCoreModuleV1/IISLib/prime.h diff --git a/src/IISLib/pudebug.h b/src/AspNetCoreModuleV1/IISLib/pudebug.h similarity index 100% rename from src/IISLib/pudebug.h rename to src/AspNetCoreModuleV1/IISLib/pudebug.h diff --git a/src/IISLib/reftrace.c b/src/AspNetCoreModuleV1/IISLib/reftrace.c similarity index 100% rename from src/IISLib/reftrace.c rename to src/AspNetCoreModuleV1/IISLib/reftrace.c diff --git a/src/IISLib/reftrace.h b/src/AspNetCoreModuleV1/IISLib/reftrace.h similarity index 100% rename from src/IISLib/reftrace.h rename to src/AspNetCoreModuleV1/IISLib/reftrace.h diff --git a/src/IISLib/rwlock.h b/src/AspNetCoreModuleV1/IISLib/rwlock.h similarity index 100% rename from src/IISLib/rwlock.h rename to src/AspNetCoreModuleV1/IISLib/rwlock.h diff --git a/src/IISLib/stringa.cpp b/src/AspNetCoreModuleV1/IISLib/stringa.cpp similarity index 100% rename from src/IISLib/stringa.cpp rename to src/AspNetCoreModuleV1/IISLib/stringa.cpp diff --git a/src/IISLib/stringa.h b/src/AspNetCoreModuleV1/IISLib/stringa.h similarity index 100% rename from src/IISLib/stringa.h rename to src/AspNetCoreModuleV1/IISLib/stringa.h diff --git a/src/IISLib/stringu.cpp b/src/AspNetCoreModuleV1/IISLib/stringu.cpp similarity index 100% rename from src/IISLib/stringu.cpp rename to src/AspNetCoreModuleV1/IISLib/stringu.cpp diff --git a/src/IISLib/stringu.h b/src/AspNetCoreModuleV1/IISLib/stringu.h similarity index 100% rename from src/IISLib/stringu.h rename to src/AspNetCoreModuleV1/IISLib/stringu.h diff --git a/src/IISLib/tracelog.c b/src/AspNetCoreModuleV1/IISLib/tracelog.c similarity index 100% rename from src/IISLib/tracelog.c rename to src/AspNetCoreModuleV1/IISLib/tracelog.c diff --git a/src/IISLib/tracelog.h b/src/AspNetCoreModuleV1/IISLib/tracelog.h similarity index 100% rename from src/IISLib/tracelog.h rename to src/AspNetCoreModuleV1/IISLib/tracelog.h diff --git a/src/IISLib/treehash.h b/src/AspNetCoreModuleV1/IISLib/treehash.h similarity index 100% rename from src/IISLib/treehash.h rename to src/AspNetCoreModuleV1/IISLib/treehash.h diff --git a/src/IISLib/util.cxx b/src/AspNetCoreModuleV1/IISLib/util.cxx similarity index 100% rename from src/IISLib/util.cxx rename to src/AspNetCoreModuleV1/IISLib/util.cxx diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj similarity index 97% rename from src/AspNetCore/AspNetCore.vcxproj rename to src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj index 4eee0392c1..4660054b0b 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj @@ -1,6 +1,6 @@  - + Debug @@ -20,7 +20,7 @@ - {439824F9-1455-4CC4-BD79-B44FA0A16552} + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} Win32Proj AspNetCoreModule AspNetCore @@ -232,8 +232,7 @@ {55494e58-e061-4c4c-a0a8-837008e72f85} - {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} - false + {09d9d1d6-2951-4e14-bc35-76a23cf9391a} @@ -254,7 +253,7 @@ - + diff --git a/src/AspNetCore/Inc/applicationinfo.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h similarity index 100% rename from src/AspNetCore/Inc/applicationinfo.h rename to src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h similarity index 100% rename from src/AspNetCore/Inc/applicationmanager.h rename to src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h diff --git a/src/AspNetCore/Inc/appoffline.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/appoffline.h similarity index 100% rename from src/AspNetCore/Inc/appoffline.h rename to src/AspNetCoreModuleV2/AspNetCore/Inc/appoffline.h diff --git a/src/AspNetCore/Inc/filewatcher.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/filewatcher.h similarity index 100% rename from src/AspNetCore/Inc/filewatcher.h rename to src/AspNetCoreModuleV2/AspNetCore/Inc/filewatcher.h diff --git a/src/AspNetCore/Inc/fx_ver.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/fx_ver.h similarity index 100% rename from src/AspNetCore/Inc/fx_ver.h rename to src/AspNetCoreModuleV2/AspNetCore/Inc/fx_ver.h diff --git a/src/AspNetCore/Inc/globalmodule.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/globalmodule.h similarity index 100% rename from src/AspNetCore/Inc/globalmodule.h rename to src/AspNetCoreModuleV2/AspNetCore/Inc/globalmodule.h diff --git a/src/AspNetCore/Inc/proxymodule.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/proxymodule.h similarity index 100% rename from src/AspNetCore/Inc/proxymodule.h rename to src/AspNetCoreModuleV2/AspNetCore/Inc/proxymodule.h diff --git a/src/AspNetCoreModuleV2/AspNetCore/Source.def b/src/AspNetCoreModuleV2/AspNetCore/Source.def new file mode 100644 index 0000000000..9aae10ab5d --- /dev/null +++ b/src/AspNetCoreModuleV2/AspNetCore/Source.def @@ -0,0 +1,4 @@ +LIBRARY aspnetcore + +EXPORTS + RegisterModule diff --git a/src/AspNetCore/ancm.mof b/src/AspNetCoreModuleV2/AspNetCore/ancm.mof similarity index 100% rename from src/AspNetCore/ancm.mof rename to src/AspNetCoreModuleV2/AspNetCore/ancm.mof diff --git a/src/AspNetCore/aspnetcore_schema.xml b/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_schema.xml similarity index 100% rename from src/AspNetCore/aspnetcore_schema.xml rename to src/AspNetCoreModuleV2/AspNetCore/aspnetcore_schema.xml diff --git a/src/AspNetCore/aspnetcoremodule.rc b/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc similarity index 96% rename from src/AspNetCore/aspnetcoremodule.rc rename to src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc index 657dfe3712..9d0dfa7f83 100644 --- a/src/AspNetCore/aspnetcoremodule.rc +++ b/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc @@ -1,120 +1,120 @@ -// Microsoft Visual C++ generated resource script. -// -#include -#include "version.h" -#include "..\CommonLib\Aspnetcore_msg.rc" -#include "..\CommonLib\resources.h" -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#define FileDescription "IIS AspNetCore Module. Commit: " CommitHash - -///////////////////////////////////////////////////////////////////////////// -// -// 11 -// - -//1 11 -//BEGIN -// 0x0001, 0x0000, 0x03e8, 0x0000, 0x03ed, 0x0000, 0x0010, 0x0000, 0x0010, -// 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, -// 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, -// 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, -// 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, -// 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, -// 0x0000, 0x0000 -//END - - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "..\CommonLib\resources.h\0" -END - -2 TEXTINCLUDE -BEGIN - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION FileVersion - PRODUCTVERSION ProductVersion - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x2L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "CompanyName", "Microsoft Corporation" - VALUE "FileDescription", FileDescription - VALUE "FileVersion", FileVersionStr - VALUE "InternalName", "aspnetcore" - VALUE "LegalCopyright", "Copyright (C) Microsoft Corporation" - VALUE "OriginalFilename", "aspnetcore.dll" - VALUE "ProductName", "ASP.NET Core Module" - VALUE "ProductVersion", ProductVersionStr - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - - -///////////////////////////////////////////////////////////////////////////// -// -// String Table -// - -STRINGTABLE -BEGIN - IDS_INVALID_PROPERTY "Property name '%s' in system.webServer/aspNetCore section has invalid value '%s' which does not conform to the prescribed format" - IDS_SERVER_ERROR "There was a connection error while trying to route the request." -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED +// Microsoft Visual C++ generated resource script. +// +#include +#include "version.h" +#include "..\CommonLib\Aspnetcore_msg.rc" +#include "..\CommonLib\resources.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#define FileDescription "IIS AspNetCore Module. Commit: " CommitHash + +///////////////////////////////////////////////////////////////////////////// +// +// 11 +// + +//1 11 +//BEGIN +// 0x0001, 0x0000, 0x03e8, 0x0000, 0x03ed, 0x0000, 0x0010, 0x0000, 0x0010, +// 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, +// 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, +// 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, +// 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, +// 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, +// 0x0000, 0x0000 +//END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "..\CommonLib\resources.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION FileVersion + PRODUCTVERSION ProductVersion + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Microsoft Corporation" + VALUE "FileDescription", FileDescription + VALUE "FileVersion", FileVersionStr + VALUE "InternalName", "aspnetcore" + VALUE "LegalCopyright", "Copyright (C) Microsoft Corporation" + VALUE "OriginalFilename", "aspnetcore.dll" + VALUE "ProductName", "ASP.NET Core Module" + VALUE "ProductVersion", ProductVersionStr + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_INVALID_PROPERTY "Property name '%s' in system.webServer/aspNetCore section has invalid value '%s' which does not conform to the prescribed format" + IDS_SERVER_ERROR "There was a connection error while trying to route the request." +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/src/AspNetCore/Src/applicationinfo.cpp b/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp similarity index 92% rename from src/AspNetCore/Src/applicationinfo.cpp rename to src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp index 4e14e33a8a..a7b96c4ddb 100644 --- a/src/AspNetCore/Src/applicationinfo.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp @@ -15,7 +15,7 @@ APPLICATION_INFO::~APPLICATION_INFO() { // Mark the entry as invalid, // StopMonitor will close the file handle and trigger a FCN - // the entry will delete itself when processing this FCN + // the entry will delete itself when processing this FCN m_pFileWatcherEntry->MarkEntryInValid(); m_pFileWatcherEntry->StopMonitor(); m_pFileWatcherEntry = NULL; @@ -112,10 +112,15 @@ APPLICATION_INFO::UpdateAppOfflineFileHandle() // if it was, log that app_offline has been dropped. if (m_fAppOfflineFound) { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED_MSG); + STACK_STRU(strEventMsg, 256); + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED_MSG))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_INFORMATION_TYPE, + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED, + strEventMsg.QueryStr()); + } } m_fAppOfflineFound = FALSE; @@ -152,11 +157,16 @@ APPLICATION_INFO::UpdateAppOfflineFileHandle() // recycle the application if (m_pApplication != NULL) { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, + STACK_STRU(strEventMsg, 256); + if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, - m_pApplication->QueryConfig()->QueryApplicationPath()->QueryStr()); + m_pApplication->QueryConfig()->QueryApplicationPath()->QueryStr()))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_INFORMATION_TYPE, + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, + strEventMsg.QueryStr()); + } RecycleApplication(); } @@ -258,20 +268,32 @@ APPLICATION_INFO::FindRequestHandlerAssembly() { if (FAILED(hr = FindNativeAssemblyFromHostfxr(&struFileName))) { - UTILITY::LogEvent(g_hEventLog, + STACK_STRU(strEventMsg, 256); + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_INPROCESS_RH_MISSING_MSG))) + { + UTILITY::LogEvent(g_hEventLog, EVENTLOG_INFORMATION_TYPE, ASPNETCORE_EVENT_INPROCESS_RH_MISSING, - ASPNETCORE_EVENT_INPROCESS_RH_MISSING_MSG); + strEventMsg.QueryStr()); + } + + goto Finished; } } else { if (FAILED(hr = FindNativeAssemblyFromGlobalLocation(&struFileName))) { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING, - ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG); + STACK_STRU(strEventMsg, 256); + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_INFORMATION_TYPE, + ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING, + strEventMsg.QueryStr()); + } goto Finished; } @@ -305,7 +327,7 @@ APPLICATION_INFO::FindRequestHandlerAssembly() Finished: // // Question: we remember the load failure so that we will not try again. - // User needs to check whether the fuction pointer is NULL + // User needs to check whether the fuction pointer is NULL // m_pfnAspNetCoreCreateApplication = g_pfnAspNetCoreCreateApplication; m_pfnAspNetCoreCreateRequestHandler = g_pfnAspNetCoreCreateRequestHandler; @@ -378,11 +400,11 @@ Finished: return hr; } -// +// // Tries to find aspnetcorerh.dll from the application // Calls into hostfxr.dll to find it. // Will leave hostfxr.dll loaded as it will be used again to call hostfxr_main. -// +// HRESULT APPLICATION_INFO::FindNativeAssemblyFromHostfxr( STRU* struFilename @@ -417,7 +439,7 @@ APPLICATION_INFO::FindNativeAssemblyFromHostfxr( if (pFnHostFxrSearchDirectories == NULL) { // Host fxr version is incorrect (need a higher version). - // TODO log error + // TODO log error hr = E_FAIL; goto Finished; } diff --git a/src/AspNetCore/Src/applicationmanager.cxx b/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx similarity index 87% rename from src/AspNetCore/Src/applicationmanager.cxx rename to src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx index 5b3e2ad93a..fd83a19818 100644 --- a/src/AspNetCore/Src/applicationmanager.cxx +++ b/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx @@ -24,6 +24,7 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo( BOOL fMixedHostingModelError = FALSE; BOOL fDuplicatedInProcessApp = FALSE; PCWSTR pszApplicationId = NULL; + STACK_STRU ( strEventMsg, 256 ); DBG_ASSERT(pServer); DBG_ASSERT(pConfig); @@ -31,9 +32,9 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo( *ppApplicationInfo = NULL; - // The configuration path is unique for each application and is used for the + // The configuration path is unique for each application and is used for the // key in the applicationInfoHash. - pszApplicationId = pConfig->QueryConfigPath()->QueryStr(); + pszApplicationId = pConfig->QueryConfigPath()->QueryStr(); hr = key.Initialize(pszApplicationId); if (FAILED(hr)) { @@ -54,7 +55,7 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo( if (*ppApplicationInfo == NULL) { - // Check which hosting model we want to support + // Check which hosting model we want to support switch (pConfig->QueryHostingModel()) { case HOSTING_IN_PROCESS: @@ -159,29 +160,41 @@ Finished: { if (fDuplicatedInProcessApp) { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP, + if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG, - pszApplicationId); + pszApplicationId))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP, + strEventMsg.QueryStr()); + } } else if (fMixedHostingModelError) { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR, + if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG, pszApplicationId, - pConfig->QueryHostingModel()); + pConfig->QueryHostingModel()))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR, + strEventMsg.QueryStr()); + } } else { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, + if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, pszApplicationId, - hr); + hr))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, + strEventMsg.QueryStr()); + } } } @@ -202,7 +215,7 @@ APPLICATION_MANAGER::FindConfigChangedApplication( DBG_ASSERT(pvContext); // Config Change context contains the original config path that changed - // and a multiStr containing + // and a multiStr containing CONFIG_CHANGE_CONTEXT* pContext = static_cast(pvContext); STRU* pstruConfigPath = pEntry->QueryConfig()->QueryConfigPath(); @@ -237,7 +250,7 @@ APPLICATION_MANAGER::FindConfigChangedApplication( // This will cause a shutdown event to occur through the global stop listening event. // OutOfProcess: Removes all applications in the application manager and calls Recycle, which will call Shutdown, // on each application. -// +// HRESULT APPLICATION_MANAGER::RecycleApplicationFromManager( _In_ LPCWSTR pszApplicationId @@ -294,7 +307,7 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( // Don't call application shutdown inside the lock m_pApplicationInfoHash->Apply(APPLICATION_INFO_HASH::ReferenceCopyToTable, static_cast(table)); DBG_ASSERT(dwPreviousCounter == table->Count()); - + // Removed the applications which are impacted by the configurtion change m_pApplicationInfoHash->DeleteIf(FindConfigChangedApplication, (PVOID)&context); @@ -336,11 +349,16 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( APPLICATION_INFO* pRecord; // Application got recycled. Log an event - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_CONFIGURATION, + STACK_STRU(strEventMsg, 256); + if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG, - path); + path))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_INFORMATION_TYPE, + ASPNETCORE_EVENT_RECYCLE_CONFIGURATION, + strEventMsg.QueryStr()); + } hr = key.Initialize(path); if (FAILED(hr)) @@ -351,7 +369,7 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( table->FindKey(&key, &pRecord); DBG_ASSERT(pRecord != NULL); - // RecycleApplication is called on a separate thread. + // RecycleApplication is called on a separate thread. pRecord->RecycleApplication(); pRecord->DereferenceApplicationInfo(); path = context.MultiSz.Next(path); @@ -368,11 +386,16 @@ Finished: if (FAILED(hr)) { // Failed to recycle an application. Log an event - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_RECYCLE_APP_FAILURE, - ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG, - pszApplicationId); + STACK_STRU(strEventMsg, 256); + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG, + pszApplicationId))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_RECYCLE_APP_FAILURE, + strEventMsg.QueryStr()); + } // Need to recycle the process as we cannot recycle the application if (!g_fRecycleProcessCalled) { @@ -387,7 +410,7 @@ Finished: // // Shutsdown all applications in the application hashtable // Only called by OnGlobalStopListening. -// +// VOID APPLICATION_MANAGER::ShutDown() { @@ -404,7 +427,7 @@ APPLICATION_MANAGER::ShutDown() } DBG_ASSERT(m_pApplicationInfoHash); - // During shutdown we lock until we delete the application + // During shutdown we lock until we delete the application AcquireSRWLockExclusive(&m_srwLock); // Call shutdown on each application in the application manager diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp similarity index 93% rename from src/AspNetCore/Src/dllmain.cpp rename to src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp index 454dc193b2..2e4f87e542 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp @@ -49,7 +49,7 @@ BOOL WINAPI DllMain(HMODULE hModule, break; case DLL_PROCESS_DETACH: // IIS can cause dll detach to occur before we receive global notifications - // For example, when we switch the bitness of the worker process, + // For example, when we switch the bitness of the worker process, // this is a bug in IIS. To try to avoid AVs, we will set a global flag g_fInShutdown = TRUE; StaticCleanup(); @@ -160,10 +160,16 @@ HRESULT if (fDisableANCM) { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_WARNING_TYPE, - ASPNETCORE_EVENT_MODULE_DISABLED, - ASPNETCORE_EVENT_MODULE_DISABLED_MSG); + // Logging + STACK_STRU(strEventMsg, 256); + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_MODULE_DISABLED_MSG))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_WARNING_TYPE, + ASPNETCORE_EVENT_MODULE_DISABLED, + strEventMsg.QueryStr()); + } // this will return 500 error to client // as we did not register the module goto Finished; @@ -198,7 +204,7 @@ HRESULT hr = E_OUTOFMEMORY; goto Finished; } - + hr = pApplicationManager->Initialize(); if(FAILED(hr)) { diff --git a/src/AspNetCore/Src/filewatcher.cxx b/src/AspNetCoreModuleV2/AspNetCore/src/filewatcher.cxx similarity index 100% rename from src/AspNetCore/Src/filewatcher.cxx rename to src/AspNetCoreModuleV2/AspNetCore/src/filewatcher.cxx diff --git a/src/AspNetCore/Src/globalmodule.cpp b/src/AspNetCoreModuleV2/AspNetCore/src/globalmodule.cpp similarity index 100% rename from src/AspNetCore/Src/globalmodule.cpp rename to src/AspNetCoreModuleV2/AspNetCore/src/globalmodule.cpp diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCoreModuleV2/AspNetCore/src/precomp.hxx similarity index 100% rename from src/AspNetCore/Src/precomp.hxx rename to src/AspNetCoreModuleV2/AspNetCore/src/precomp.hxx diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx similarity index 100% rename from src/AspNetCore/Src/proxymodule.cxx rename to src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx diff --git a/src/CommonLib/CommonLib.vcxproj b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj similarity index 100% rename from src/CommonLib/CommonLib.vcxproj rename to src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj diff --git a/src/CommonLib/SRWLockWrapper.cpp b/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp similarity index 100% rename from src/CommonLib/SRWLockWrapper.cpp rename to src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp diff --git a/src/CommonLib/SRWLockWrapper.h b/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h similarity index 100% rename from src/CommonLib/SRWLockWrapper.h rename to src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h diff --git a/src/CommonLib/application.cpp b/src/AspNetCoreModuleV2/CommonLib/application.cpp similarity index 100% rename from src/CommonLib/application.cpp rename to src/AspNetCoreModuleV2/CommonLib/application.cpp diff --git a/src/CommonLib/application.h b/src/AspNetCoreModuleV2/CommonLib/application.h similarity index 100% rename from src/CommonLib/application.h rename to src/AspNetCoreModuleV2/CommonLib/application.h diff --git a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.h b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.h new file mode 100644 index 0000000000..ea975472ac --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.h @@ -0,0 +1,328 @@ +/*++ + + Copyright (c) .NET Foundation. All rights reserved. + Licensed under the MIT License. See License.txt in the project root for license information. + +Module Name: + + aspnetcore_msg.mc + +Abstract: + + Asp.Net Core Module localizable messages. + +--*/ + + +#ifndef _ASPNETCORE_MSG_H_ +#define _ASPNETCORE_MSG_H_ + +// +// Values are 32 bit values laid out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code +// +// +// Define the facility codes +// + + +// +// Define the severity codes +// + + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_START_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_START_ERROR ((DWORD)0x000003E8L) + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_START_SUCCESS +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_START_SUCCESS ((DWORD)0x000003E9L) + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_CRASH +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_CRASH ((DWORD)0x000003EAL) + +// +// MessageId: ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED ((DWORD)0x000003EBL) + +// +// MessageId: ASPNETCORE_EVENT_CONFIG_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_CONFIG_ERROR ((DWORD)0x000003ECL) + +// +// MessageId: ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE ((DWORD)0x000003EDL) + +// +// MessageId: ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST ((DWORD)0x000003EEL) + +// +// MessageId: ASPNETCORE_EVENT_LOAD_CLR_FALIURE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE ((DWORD)0x000003EFL) + +// +// MessageId: ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP ((DWORD)0x000003F0L) + +// +// MessageId: ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR ((DWORD)0x000003F1L) + +// +// MessageId: ASPNETCORE_EVENT_ADD_APPLICATION_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR ((DWORD)0x000003F2L) + +// +// MessageId: ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT ((DWORD)0x000003F3L) + +// +// MessageId: ASPNETCORE_EVENT_RECYCLE_APPOFFLINE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE ((DWORD)0x000003F4L) + +// +// MessageId: ASPNETCORE_EVENT_MODULE_DISABLED +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_MODULE_DISABLED ((DWORD)0x000003F5L) + +// +// MessageId: ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP ((DWORD)0x000003F6L) + +// +// MessageId: ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING ((DWORD)0x000003F7L) + +// +// MessageId: ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND ((DWORD)0x000003F8L) + +// +// MessageId: ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND ((DWORD)0x000003F9L) + +// +// MessageId: ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION ((DWORD)0x000003FAL) + +// +// MessageId: ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND ((DWORD)0x000003FBL) + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_START_FAILURE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_START_FAILURE ((DWORD)0x000003FCL) + +// +// MessageId: ASPNETCORE_EVENT_RECYCLE_CONFIGURATION +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION ((DWORD)0x000003FDL) + +// +// MessageId: ASPNETCORE_EVENT_RECYCLE_APP_FAILURE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_RECYCLE_APP_FAILURE ((DWORD)0x000003FEL) + +// +// MessageId: ASPNETCORE_EVENT_APP_IN_SHUTDOWN +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_APP_IN_SHUTDOWN ((DWORD)0x000003FFL) + +// +// MessageId: ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED ((DWORD)0x00000400L) + +// +// MessageId: ASPNETCORE_EVENT_GENERAL_INFO_MSG +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_GENERAL_INFO_MSG ((DWORD)0x00000401L) + +// +// MessageId: ASPNETCORE_EVENT_GENERAL_WARNING_MSG +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_GENERAL_WARNING_MSG ((DWORD)0x00000402L) + +// +// MessageId: ASPNETCORE_EVENT_GENERAL_ERROR_MSG +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_GENERAL_ERROR_MSG ((DWORD)0x00000403L) + +// +// MessageId: ASPNETCORE_EVENT_INPROCESS_RH_MISSING +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_INPROCESS_RH_MISSING ((DWORD)0x00000404L) + +// +// MessageId: ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING ((DWORD)0x00000405L) + + +#endif // _ASPNETCORE_MODULE_MSG_H_ + diff --git a/src/CommonLib/aspnetcore_msg.mc b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc similarity index 100% rename from src/CommonLib/aspnetcore_msg.mc rename to src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc diff --git a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.rc b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.rc new file mode 100644 index 0000000000..0abcb0fa2c --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.rc @@ -0,0 +1,2 @@ +LANGUAGE 0x9,0x1 +1 11 "MSG00001.bin" diff --git a/src/CommonLib/aspnetcoreconfig.cxx b/src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.cxx similarity index 100% rename from src/CommonLib/aspnetcoreconfig.cxx rename to src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.cxx diff --git a/src/CommonLib/aspnetcoreconfig.h b/src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.h similarity index 100% rename from src/CommonLib/aspnetcoreconfig.h rename to src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.h diff --git a/src/CommonLib/debugutil.h b/src/AspNetCoreModuleV2/CommonLib/debugutil.h similarity index 100% rename from src/CommonLib/debugutil.h rename to src/AspNetCoreModuleV2/CommonLib/debugutil.h diff --git a/src/CommonLib/environmentvariablehash.h b/src/AspNetCoreModuleV2/CommonLib/environmentvariablehash.h similarity index 100% rename from src/CommonLib/environmentvariablehash.h rename to src/AspNetCoreModuleV2/CommonLib/environmentvariablehash.h diff --git a/src/CommonLib/fx_ver.cxx b/src/AspNetCoreModuleV2/CommonLib/fx_ver.cxx similarity index 100% rename from src/CommonLib/fx_ver.cxx rename to src/AspNetCoreModuleV2/CommonLib/fx_ver.cxx diff --git a/src/CommonLib/fx_ver.h b/src/AspNetCoreModuleV2/CommonLib/fx_ver.h similarity index 100% rename from src/CommonLib/fx_ver.h rename to src/AspNetCoreModuleV2/CommonLib/fx_ver.h diff --git a/src/CommonLib/hostfxr_utility.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp similarity index 100% rename from src/CommonLib/hostfxr_utility.cpp rename to src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp diff --git a/src/CommonLib/hostfxr_utility.h b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h similarity index 100% rename from src/CommonLib/hostfxr_utility.h rename to src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h diff --git a/src/CommonLib/requesthandler.cxx b/src/AspNetCoreModuleV2/CommonLib/requesthandler.cxx similarity index 100% rename from src/CommonLib/requesthandler.cxx rename to src/AspNetCoreModuleV2/CommonLib/requesthandler.cxx diff --git a/src/CommonLib/requesthandler.h b/src/AspNetCoreModuleV2/CommonLib/requesthandler.h similarity index 100% rename from src/CommonLib/requesthandler.h rename to src/AspNetCoreModuleV2/CommonLib/requesthandler.h diff --git a/src/CommonLib/resources.h b/src/AspNetCoreModuleV2/CommonLib/resources.h similarity index 100% rename from src/CommonLib/resources.h rename to src/AspNetCoreModuleV2/CommonLib/resources.h diff --git a/src/CommonLib/stdafx.cpp b/src/AspNetCoreModuleV2/CommonLib/stdafx.cpp similarity index 97% rename from src/CommonLib/stdafx.cpp rename to src/AspNetCoreModuleV2/CommonLib/stdafx.cpp index 6850f4cb6e..0351feb240 100644 --- a/src/CommonLib/stdafx.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/stdafx.cpp @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "stdafx.h" +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" diff --git a/src/CommonLib/stdafx.h b/src/AspNetCoreModuleV2/CommonLib/stdafx.h similarity index 96% rename from src/CommonLib/stdafx.h rename to src/AspNetCoreModuleV2/CommonLib/stdafx.h index 2ef05addcc..69ad058f1f 100644 --- a/src/CommonLib/stdafx.h +++ b/src/AspNetCoreModuleV2/CommonLib/stdafx.h @@ -1,34 +1,34 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -#include "targetver.h" - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers - -#include -#include -#include -#include -#include -#include -#include "Shlwapi.h" -#include "..\IISLib\hashtable.h" -#include "..\IISLib\stringu.h" -#include "..\IISLib\stringa.h" -#include "..\IISLib\multisz.h" -#include "..\IISLib\dbgutil.h" -#include "..\IISLib\ahutil.h" -#include "..\IISLib\hashfn.h" -#include "SRWLockWrapper.h" -#include "environmentvariablehash.h" -#include "utility.h" -#include "aspnetcoreconfig.h" -#include "application.h" -#include "requesthandler.h" -#include "fx_ver.h" -#include "hostfxr_utility.h" -#include "resources.h" -#include "aspnetcore_msg.h" - +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include +#include +#include +#include +#include +#include +#include "Shlwapi.h" +#include "..\IISLib\hashtable.h" +#include "..\IISLib\stringu.h" +#include "..\IISLib\stringa.h" +#include "..\IISLib\multisz.h" +#include "..\IISLib\dbgutil.h" +#include "..\IISLib\ahutil.h" +#include "..\IISLib\hashfn.h" +#include "SRWLockWrapper.h" +#include "environmentvariablehash.h" +#include "utility.h" +#include "aspnetcoreconfig.h" +#include "application.h" +#include "requesthandler.h" +#include "fx_ver.h" +#include "hostfxr_utility.h" +#include "resources.h" +#include "aspnetcore_msg.h" + diff --git a/src/CommonLib/targetver.h b/src/AspNetCoreModuleV2/CommonLib/targetver.h similarity index 90% rename from src/CommonLib/targetver.h rename to src/AspNetCoreModuleV2/CommonLib/targetver.h index 617620b372..5b1f29cad0 100644 --- a/src/CommonLib/targetver.h +++ b/src/AspNetCoreModuleV2/CommonLib/targetver.h @@ -1,8 +1,8 @@ -#pragma once - -// Including SDKDDKVer.h defines the highest available Windows platform. - -// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and -// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. - +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + #include \ No newline at end of file diff --git a/src/CommonLib/utility.cxx b/src/AspNetCoreModuleV2/CommonLib/utility.cxx similarity index 100% rename from src/CommonLib/utility.cxx rename to src/AspNetCoreModuleV2/CommonLib/utility.cxx diff --git a/src/CommonLib/utility.h b/src/AspNetCoreModuleV2/CommonLib/utility.h similarity index 100% rename from src/CommonLib/utility.h rename to src/AspNetCoreModuleV2/CommonLib/utility.h diff --git a/src/AspNetCoreModuleV2/IISLib/IISLib.vcxproj b/src/AspNetCoreModuleV2/IISLib/IISLib.vcxproj new file mode 100644 index 0000000000..7c0cca6626 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/IISLib.vcxproj @@ -0,0 +1,201 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {09D9D1D6-2951-4E14-BC35-76A23CF9391A} + Win32Proj + IISLib + IISLib + 10.0.15063.0 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + MultiThreadedDebug + false + true + + + Windows + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + MultiThreadedDebug + false + true + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/IISLib/acache.cxx b/src/AspNetCoreModuleV2/IISLib/acache.cxx new file mode 100644 index 0000000000..d68813edbc --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/acache.cxx @@ -0,0 +1,443 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +LONG ALLOC_CACHE_HANDLER::sm_nFillPattern = 0xACA50000; +HANDLE ALLOC_CACHE_HANDLER::sm_hHeap; + +// +// This class is used to implement the free list. We cast the free'd +// memory block to a FREE_LIST_HEADER*. The signature is used to guard against +// double deletion. We also fill memory with a pattern. +// +class FREE_LIST_HEADER +{ +public: + SLIST_ENTRY ListEntry; + DWORD dwSignature; + + enum + { + FREE_SIGNATURE = (('A') | ('C' << 8) | ('a' << 16) | (('$' << 24) | 0x80)), + }; +}; + +ALLOC_CACHE_HANDLER::ALLOC_CACHE_HANDLER( + VOID +) : m_nThreshold(0), + m_cbSize(0), + m_pFreeLists(NULL), + m_nTotal(0) +{ +} + +ALLOC_CACHE_HANDLER::~ALLOC_CACHE_HANDLER( + VOID +) +{ + if (m_pFreeLists != NULL) + { + CleanupLookaside(); + m_pFreeLists->Dispose(); + m_pFreeLists = NULL; + } +} + +HRESULT +ALLOC_CACHE_HANDLER::Initialize( + DWORD cbSize, + LONG nThreshold +) +{ + HRESULT hr = S_OK; + + m_nThreshold = nThreshold; + if ( m_nThreshold > 0xffff) + { + // + // This will be compared against QueryDepthSList return value (USHORT). + // + m_nThreshold = 0xffff; + } + + if ( IsPageheapEnabled() ) + { + // + // Disable acache. + // + m_nThreshold = 0; + } + + // + // Make sure the block is big enough to hold a FREE_LIST_HEADER. + // + m_cbSize = cbSize; + m_cbSize = max(m_cbSize, sizeof(FREE_LIST_HEADER)); + + // + // Round up the block size to a multiple of the size of a LONG (for + // the fill pattern in Free()). + // + m_cbSize = (m_cbSize + sizeof(LONG) - 1) & ~(sizeof(LONG) - 1); + +#if defined(_MSC_VER) && _MSC_VER >= 1600 // VC10 + auto Init = [] (SLIST_HEADER* pHead) + { + InitializeSListHead(pHead); + }; +#else + class Functor + { + public: + void operator()(SLIST_HEADER* pHead) + { + InitializeSListHead(pHead); + } + } Init; +#endif + + hr = PER_CPU::Create(Init, + &m_pFreeLists ); + if (FAILED(hr)) + { + goto Finished; + } + + m_nFillPattern = InterlockedIncrement(&sm_nFillPattern); + +Finished: + + return hr; +} + +// static +HRESULT +ALLOC_CACHE_HANDLER::StaticInitialize( + VOID +) +{ + // + // Since the memory allocated is fixed size, + // a heap is not really needed, allocations can be done + // using VirtualAllocEx[Numa]. For now use Windows Heap. + // + // Be aware that creating one private heap consumes more + // virtual address space for the worker process. + // + sm_hHeap = GetProcessHeap(); + return S_OK; +} + + +// static +VOID +ALLOC_CACHE_HANDLER::StaticTerminate( + VOID +) +{ + sm_hHeap = NULL; +} + +VOID +ALLOC_CACHE_HANDLER::CleanupLookaside( + VOID +) +/*++ + Description: + This function cleans up the lookaside list by removing storage space. + + Arguments: + None. + + Returns: + None +--*/ +{ + // + // Free up all the entries in the list. + // Don't use InterlockedFlushSList, in order to work + // memory must be 16 bytes aligned and currently it is 64. + // + +#if defined(_MSC_VER) && _MSC_VER >= 1600 // VC10 + auto Predicate = [=] (SLIST_HEADER * pListHeader) + { + PSLIST_ENTRY pl; + LONG NodesToDelete = QueryDepthSList( pListHeader ); + + pl = InterlockedPopEntrySList( pListHeader ); + while ( pl != NULL && --NodesToDelete >= 0 ) + { + InterlockedDecrement( &m_nTotal); + + ::HeapFree( sm_hHeap, 0, pl ); + + pl = InterlockedPopEntrySList(pListHeader); + } + }; +#else + class Functor + { + public: + explicit Functor(ALLOC_CACHE_HANDLER * pThis) : _pThis(pThis) + { + } + void operator()(SLIST_HEADER * pListHeader) + { + PSLIST_ENTRY pl; + LONG NodesToDelete = QueryDepthSList( pListHeader ); + + pl = InterlockedPopEntrySList( pListHeader ); + while ( pl != NULL && --NodesToDelete >= 0 ) + { + InterlockedDecrement( &_pThis->m_nTotal); + + ::HeapFree( sm_hHeap, 0, pl ); + + pl = InterlockedPopEntrySList(pListHeader); + } + } + private: + ALLOC_CACHE_HANDLER * _pThis; + } Predicate(this); +#endif + + m_pFreeLists ->ForEach(Predicate); +} + +LPVOID +ALLOC_CACHE_HANDLER::Alloc( + VOID +) +{ + LPVOID pMemory = NULL; + + if ( m_nThreshold > 0 ) + { + SLIST_HEADER * pListHeader = m_pFreeLists ->GetLocal(); + pMemory = (LPVOID) InterlockedPopEntrySList(pListHeader); // get the real object + + if (pMemory != NULL) + { + FREE_LIST_HEADER* pfl = (FREE_LIST_HEADER*) pMemory; + // + // If the signature is wrong then somebody's been scribbling + // on memory that they've freed. + // + DBG_ASSERT(pfl->dwSignature == FREE_LIST_HEADER::FREE_SIGNATURE); + } + } + + if ( pMemory == NULL ) + { + // + // No free entry. Need to alloc a new object. + // + pMemory = (LPVOID) ::HeapAlloc( sm_hHeap, + 0, + m_cbSize ); + + if ( pMemory != NULL ) + { + // + // Update counters. + // + m_nTotal++; + } + } + + if ( pMemory == NULL ) + { + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + } + else + { + FREE_LIST_HEADER* pfl = (FREE_LIST_HEADER*) pMemory; + pfl->dwSignature = 0; // clear; just in case caller never overwrites + } + + return pMemory; +} + +VOID +ALLOC_CACHE_HANDLER::Free( + __in LPVOID pMemory +) +{ + // + // Assume that this is allocated using the Alloc() function. + // + DBG_ASSERT(NULL != pMemory); + + // + // Use a signature to check against double deletions. + // + FREE_LIST_HEADER* pfl = (FREE_LIST_HEADER*) pMemory; + DBG_ASSERT(pfl->dwSignature != FREE_LIST_HEADER::FREE_SIGNATURE); + + // + // Start filling the space beyond the portion overlaid by the initial + // FREE_LIST_HEADER. Fill at most 6 DWORDS. + // + LONG* pl = (LONG*) (pfl+1); + + for (LONG cb = (LONG)min(6 * sizeof(LONG),m_cbSize) - sizeof(FREE_LIST_HEADER); + cb > 0; + cb -= sizeof(LONG)) + { + *pl++ = m_nFillPattern; + } + + // + // Now, set the signature. + // + pfl->dwSignature = FREE_LIST_HEADER::FREE_SIGNATURE; + + // + // Store the items in the alloc cache. + // + SLIST_HEADER * pListHeader = m_pFreeLists ->GetLocal(); + + if ( QueryDepthSList(pListHeader) >= m_nThreshold ) + { + // + // Threshold for free entries is exceeded. Free the object to + // process pool. + // + ::HeapFree( sm_hHeap, 0, pMemory ); + } + else + { + // + // Store the given pointer in the single linear list + // + InterlockedPushEntrySList(pListHeader, &pfl->ListEntry); + } +} + +DWORD +ALLOC_CACHE_HANDLER::QueryDepthForAllSLists( + VOID +) +/*++ + +Description: + + Aggregates the total count of elements in all lists. + +Arguments: + + None. + +Return Value: + + Total count (snapshot). + +--*/ +{ + DWORD Count = 0; + + if (m_pFreeLists != NULL) + { +#if defined(_MSC_VER) && _MSC_VER >= 1600 // VC10 + auto Predicate = [&Count] (SLIST_HEADER * pListHeader) + { + Count += QueryDepthSList(pListHeader); + }; +#else + class Functor + { + public: + explicit Functor(DWORD& Count) : _Count(Count) + { + } + void operator()(SLIST_HEADER * pListHeader) + { + _Count += QueryDepthSList(pListHeader); + } + private: + DWORD& _Count; + } Predicate(Count); +#endif + // + // [&Count] means that the method can modify local variable Count. + // + m_pFreeLists ->ForEach(Predicate); + } + + return Count; +} + +// static +BOOL +ALLOC_CACHE_HANDLER::IsPageheapEnabled( + VOID +) +{ + BOOL fRet = FALSE; + BOOL fLockedHeap = FALSE; + HMODULE hModule = NULL; + HANDLE hHeap = NULL; + PROCESS_HEAP_ENTRY heapEntry = {0}; + + // + // If verifier.dll is loaded - we are running under app verifier == pageheap is enabled + // + hModule = GetModuleHandle( L"verifier.dll" ); + if ( hModule != NULL ) + { + hModule = NULL; + fRet = TRUE; + goto Finished; + } + + // + // Create a heap for calling heapwalk + // otherwise HeapWalk turns off lookasides for a useful heap + // + hHeap = ::HeapCreate( 0, 0, 0 ); + if ( hHeap == NULL ) + { + fRet = FALSE; + goto Finished; + } + + fRet = ::HeapLock( hHeap ); + if ( !fRet ) + { + goto Finished; + } + fLockedHeap = TRUE; + + // + // If HeapWalk is unsupported -> then running page heap + // + fRet = ::HeapWalk( hHeap, &heapEntry ); + if ( !fRet ) + { + if ( GetLastError() == ERROR_INVALID_FUNCTION ) + { + fRet = TRUE; + goto Finished; + } + } + + fRet = FALSE; + +Finished: + + if ( fLockedHeap ) + { + fLockedHeap = FALSE; + DBG_REQUIRE( ::HeapUnlock( hHeap ) ); + } + + if ( hHeap ) + { + DBG_REQUIRE( ::HeapDestroy( hHeap ) ); + hHeap = NULL; + } + + return fRet; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/IISLib/acache.h b/src/AspNetCoreModuleV2/IISLib/acache.h new file mode 100644 index 0000000000..048df2b507 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/acache.h @@ -0,0 +1,115 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "percpu.h" + +class ALLOC_CACHE_HANDLER +{ +public: + + ALLOC_CACHE_HANDLER( + VOID + ); + + ~ALLOC_CACHE_HANDLER( + VOID + ); + + HRESULT + Initialize( + DWORD cbSize, + LONG nThreshold + ); + + LPVOID + Alloc( + VOID + ); + + VOID + Free( + __in LPVOID pMemory + ); + + +private: + + VOID + CleanupLookaside( + VOID + ); + + DWORD + QueryDepthForAllSLists( + VOID + ); + + LONG m_nThreshold; + DWORD m_cbSize; + + PER_CPU * m_pFreeLists; + + // + // Total heap allocations done over the lifetime. + // Note that this is not interlocked, it is just a hint for debugging. + // + volatile LONG m_nTotal; + + LONG m_nFillPattern; + +public: + + static + HRESULT + StaticInitialize( + VOID + ); + + static + VOID + StaticTerminate( + VOID + ); + + static + BOOL + IsPageheapEnabled(); + +private: + + static LONG sm_nFillPattern; + static HANDLE sm_hHeap; +}; + + +// You can use ALLOC_CACHE_HANDLER as a per-class allocator +// in your C++ classes. Add the following to your class definition: +// +// protected: +// static ALLOC_CACHE_HANDLER* sm_palloc; +// public: +// static void* operator new(size_t s) +// { +// IRTLASSERT(s == sizeof(C)); +// IRTLASSERT(sm_palloc != NULL); +// return sm_palloc->Alloc(); +// } +// static void operator delete(void* pv) +// { +// IRTLASSERT(pv != NULL); +// if (sm_palloc != NULL) +// sm_palloc->Free(pv); +// } +// +// Obviously, you must initialize sm_palloc before you can allocate +// any objects of this class. +// +// Note that if you derive a class from this base class, the derived class +// must also provide its own operator new and operator delete. If not, the +// base class's allocator will be called, but the size of the derived +// object will almost certainly be larger than that of the base object. +// Furthermore, the allocator will not be used for arrays of objects +// (override operator new[] and operator delete[]), but this is a +// harder problem since the allocator works with one fixed size. diff --git a/src/AspNetCoreModuleV2/IISLib/ahutil.cpp b/src/AspNetCoreModuleV2/IISLib/ahutil.cpp new file mode 100644 index 0000000000..dae2027f33 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/ahutil.cpp @@ -0,0 +1,1671 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +HRESULT +SetElementProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + IN CONST VARIANT * varPropValue + ) +{ + HRESULT hr = NOERROR; + + CComPtr pPropElement; + + BSTR bstrPropName = SysAllocString( szPropName ); + + if( !bstrPropName ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR( hr ); + goto exit; + } + + hr = pElement->GetPropertyByName( bstrPropName, + &pPropElement ); + if( FAILED(hr) ) + { + DBGERROR_HR( hr ); + goto exit; + } + + hr = pPropElement->put_Value( *varPropValue ); + if( FAILED(hr) ) + { + DBGERROR_HR( hr ); + goto exit; + } + +exit: + + if( bstrPropName ) + { + SysFreeString( bstrPropName ); + bstrPropName = NULL; + } + + return hr; +} + +HRESULT +SetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + IN CONST WCHAR * szPropValue + ) +{ + HRESULT hr; + VARIANT varPropValue; + VariantInit(&varPropValue); + + hr = VariantAssign(&varPropValue, szPropValue); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = SetElementProperty(pElement, szPropName, &varPropValue); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + VariantClear(&varPropValue); + return hr; +} + +HRESULT +GetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + OUT BSTR * pbstrPropValue + ) +{ + HRESULT hr = S_OK; + BSTR bstrPropName = SysAllocString( szPropName ); + IAppHostProperty* pProperty = NULL; + + *pbstrPropValue = NULL; + + if (!bstrPropName) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR( hr ); + goto exit; + } + + hr = pElement->GetPropertyByName( bstrPropName, &pProperty ); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + + hr = pProperty->get_StringValue( pbstrPropValue ); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + +exit: + + if (pProperty) + { + pProperty->Release(); + } + + if (bstrPropName) + { + SysFreeString( bstrPropName ); + } + + return hr; +} + + +HRESULT +GetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + OUT STRU * pstrPropValue + ) +{ + HRESULT hr = S_OK; + BSTR bstrPropName = SysAllocString( szPropName ); + IAppHostProperty* pProperty = NULL; + BSTR bstrPropValue = NULL; + + if (!bstrPropName) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR( hr ); + goto exit; + } + + hr = pElement->GetPropertyByName( bstrPropName, &pProperty ); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + + hr = pProperty->get_StringValue( &bstrPropValue ); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + + hr = pstrPropValue->Copy(bstrPropValue); + if (FAILED(hr)) + { + DBGERROR_HR( hr ); + goto exit; + } + +exit: + + if (pProperty) + { + pProperty->Release(); + } + + if (bstrPropValue) + { + SysFreeString( bstrPropValue ); + } + + if (bstrPropName) + { + SysFreeString( bstrPropName ); + } + + return hr; +} + +HRESULT +GetElementChildByName( + IN IAppHostElement * pElement, + IN LPCWSTR pszElementName, + OUT IAppHostElement ** ppChildElement +) +{ + BSTR bstrElementName = SysAllocString(pszElementName); + if (bstrElementName == NULL) + { + return E_OUTOFMEMORY; + } + HRESULT hr = pElement->GetElementByName(bstrElementName, + ppChildElement); + SysFreeString(bstrElementName); + return hr; +} + +HRESULT +GetElementBoolProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT bool * pBool +) +{ + BOOL fValue; + HRESULT hr = GetElementBoolProperty(pElement, + pszPropertyName, + &fValue); + if (SUCCEEDED(hr)) + { + *pBool = !!fValue; + } + return hr; +} + +HRESULT +GetElementBoolProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT BOOL * pBool +) +{ + HRESULT hr = S_OK; + BSTR bstrPropertyName = NULL; + IAppHostProperty * pProperty = NULL; + VARIANT varValue; + + VariantInit( &varValue ); + + bstrPropertyName = SysAllocString( pszPropertyName ); + if ( bstrPropertyName == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + // Now ask for the property and if it succeeds it is returned directly back. + hr = pElement->GetPropertyByName( bstrPropertyName, &pProperty ); + if ( FAILED ( hr ) ) + { + goto exit; + } + + // Now let's get the property and then extract it from the Variant. + hr = pProperty->get_Value( &varValue ); + if ( FAILED ( hr ) ) + { + goto exit; + } + + hr = VariantChangeType( &varValue, &varValue, 0, VT_BOOL ); + if ( FAILED ( hr ) ) + { + goto exit; + } + + // extract the value + *pBool = ( V_BOOL( &varValue ) == VARIANT_TRUE ); + +exit: + + VariantClear( &varValue ); + + if ( bstrPropertyName != NULL ) + { + SysFreeString( bstrPropertyName ); + bstrPropertyName = NULL; + } + + if ( pProperty != NULL ) + { + pProperty->Release(); + pProperty = NULL; + } + + return hr; + +} + +HRESULT +GetElementDWORDProperty( + IN IAppHostElement * pSitesCollectionEntry, + IN LPCWSTR pwszName, + OUT DWORD * pdwValue +) +{ + HRESULT hr = S_OK; + IAppHostProperty * pProperty = NULL; + BSTR bstrName = NULL; + VARIANT varValue; + + VariantInit( &varValue ); + + bstrName = SysAllocString( pwszName ); + if ( bstrName == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto error; + } + + hr = pSitesCollectionEntry->GetPropertyByName( bstrName, + &pProperty ); + if ( FAILED ( hr ) ) + { + goto error; + } + + hr = pProperty->get_Value( &varValue ); + if ( FAILED ( hr ) ) + { + goto error; + } + + hr = VariantChangeType( &varValue, &varValue, 0, VT_UI4 ); + if ( FAILED ( hr ) ) + { + goto error; + } + + // extract the value + *pdwValue = varValue.ulVal; + +error: + + VariantClear( &varValue ); + + if ( pProperty != NULL ) + { + pProperty->Release(); + pProperty = NULL; + } + + if ( bstrName != NULL ) + { + SysFreeString( bstrName ); + bstrName = NULL; + } + + return hr; +} + +HRESULT +GetElementLONGLONGProperty( + IN IAppHostElement * pSitesCollectionEntry, + IN LPCWSTR pwszName, + OUT LONGLONG * pllValue +) +{ + HRESULT hr = S_OK; + IAppHostProperty * pProperty = NULL; + BSTR bstrName = NULL; + VARIANT varValue; + + VariantInit( &varValue ); + + bstrName = SysAllocString( pwszName ); + if ( bstrName == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto error; + } + + hr = pSitesCollectionEntry->GetPropertyByName( bstrName, + &pProperty ); + if ( FAILED ( hr ) ) + { + goto error; + } + + hr = pProperty->get_Value( &varValue ); + if ( FAILED ( hr ) ) + { + goto error; + } + + hr = VariantChangeType( &varValue, &varValue, 0, VT_I8 ); + if ( FAILED ( hr ) ) + { + goto error; + } + + // extract the value + *pllValue = varValue.ulVal; + +error: + + VariantClear( &varValue ); + + if ( pProperty != NULL ) + { + pProperty->Release(); + pProperty = NULL; + } + + if ( bstrName != NULL ) + { + SysFreeString( bstrName ); + bstrName = NULL; + } + + return hr; +} + +HRESULT +GetElementRawTimeSpanProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT ULONGLONG * pulonglong +) +{ + HRESULT hr = S_OK; + BSTR bstrPropertyName = NULL; + IAppHostProperty * pProperty = NULL; + VARIANT varValue; + + VariantInit( &varValue ); + + bstrPropertyName = SysAllocString( pszPropertyName ); + if ( bstrPropertyName == NULL ) + { + hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); + goto Finished; + } + + // Now ask for the property and if it succeeds it is returned directly back + hr = pElement->GetPropertyByName( bstrPropertyName, &pProperty ); + if ( FAILED ( hr ) ) + { + goto Finished; + } + + // Now let's get the property and then extract it from the Variant. + hr = pProperty->get_Value( &varValue ); + if ( FAILED ( hr ) ) + { + goto Finished; + } + + hr = VariantChangeType( &varValue, &varValue, 0, VT_UI8 ); + if ( FAILED ( hr ) ) + { + goto Finished; + } + + // extract the value + *pulonglong = varValue.ullVal; + + +Finished: + + VariantClear( &varValue ); + + if ( bstrPropertyName != NULL ) + { + SysFreeString( bstrPropertyName ); + bstrPropertyName = NULL; + } + + if ( pProperty != NULL ) + { + pProperty->Release(); + pProperty = NULL; + } + + return hr; + +} // end of Config_GetRawTimeSpanProperty + +HRESULT +DeleteElementFromCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + BOOL * pfDeleted + ) +{ + HRESULT hr = NOERROR; + ULONG index; + + VARIANT varIndex; + VariantInit( &varIndex ); + + *pfDeleted = FALSE; + + hr = FindElementInCollection( + pCollection, + szKeyName, + szKeyValue, + BehaviorFlags, + &index + ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if (hr == S_FALSE) + { + // + // Not found. + // + + goto exit; + } + + varIndex.vt = VT_UI4; + varIndex.ulVal = index; + + hr = pCollection->DeleteElement( varIndex ); + + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + *pfDeleted = TRUE; + +exit: + + return hr; +} + +HRESULT +DeleteAllElementsFromCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + UINT * pNumDeleted + ) +{ + HRESULT hr = S_OK; + UINT numDeleted = 0; + BOOL fDeleted = TRUE; + + while (fDeleted) + { + hr = DeleteElementFromCollection( + pCollection, + szKeyName, + szKeyValue, + BehaviorFlags, + &fDeleted + ); + + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + break; + } + + if (fDeleted) + { + numDeleted++; + } + } + + *pNumDeleted = numDeleted; + return hr; +} + +BOOL +FindCompareCaseSensitive( + CONST WCHAR * szLookupValue, + CONST WCHAR * szKeyValue + ) +{ + return !wcscmp(szLookupValue, szKeyValue); +} + +BOOL +FindCompareCaseInsensitive( + CONST WCHAR * szLookupValue, + CONST WCHAR * szKeyValue + ) +{ + return !_wcsicmp(szLookupValue, szKeyValue); +} + +typedef +BOOL +(*PFN_FIND_COMPARE_PROC)( + CONST WCHAR *szLookupValue, + CONST WCHAR *szKeyValue + ); + +HRESULT +FindElementInCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + OUT ULONG * pIndex + ) +{ + HRESULT hr = NOERROR; + + CComPtr pElement; + CComPtr pKeyProperty; + + VARIANT varIndex; + VariantInit( &varIndex ); + + VARIANT varKeyValue; + VariantInit( &varKeyValue ); + + DWORD count; + DWORD i; + + BSTR bstrKeyName = NULL; + PFN_FIND_COMPARE_PROC compareProc; + + compareProc = (BehaviorFlags & FIND_ELEMENT_CASE_INSENSITIVE) + ? &FindCompareCaseInsensitive + : &FindCompareCaseSensitive; + + bstrKeyName = SysAllocString( szKeyName ); + if( !bstrKeyName ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + hr = pCollection->get_Count( &count ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + for( i = 0; i < count; i++ ) + { + varIndex.vt = VT_UI4; + varIndex.ulVal = i; + + hr = pCollection->get_Item( varIndex, + &pElement ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto tryNext; + } + + hr = pElement->GetPropertyByName( bstrKeyName, + &pKeyProperty ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto tryNext; + } + + hr = pKeyProperty->get_Value( &varKeyValue ); + + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto tryNext; + } + + if ((compareProc)(szKeyValue, varKeyValue.bstrVal)) + { + *pIndex = i; + break; + } + +tryNext: + + pElement.Release(); + pKeyProperty.Release(); + + VariantClear( &varKeyValue ); + } + + if (i >= count) + { + hr = S_FALSE; + } + +exit: + + SysFreeString( bstrKeyName ); + VariantClear( &varKeyValue ); + + return hr; +} + +HRESULT +VariantAssign( + IN OUT VARIANT * pv, + IN CONST WCHAR * sz + ) +{ + if( !pv || !sz ) + { + return E_INVALIDARG; + } + + HRESULT hr = NOERROR; + + BSTR bstr = SysAllocString( sz ); + if( !bstr ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR( hr ); + goto exit; + } + + hr = VariantClear( pv ); + if( FAILED(hr) ) + { + DBGERROR_HR( hr ); + goto exit; + } + + pv->vt = VT_BSTR; + pv->bstrVal = bstr; + bstr = NULL; + +exit: + + SysFreeString( bstr ); + + return hr; +} + +HRESULT +GetLocationFromFile( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szLocationPath, + OUT IAppHostConfigLocation ** ppLocation, + OUT BOOL * pFound + ) +{ + HRESULT hr = NOERROR; + + CComPtr pLocationCollection; + CComPtr pLocation; + + BSTR bstrLocationPath = NULL; + + *ppLocation = NULL; + *pFound = FALSE; + + hr = GetLocationCollection( pAdminMgr, + szConfigPath, + &pLocationCollection ); + + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + DWORD count; + DWORD i; + VARIANT varIndex; + VariantInit( &varIndex ); + + hr = pLocationCollection->get_Count( &count ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + for( i = 0; i < count; i++ ) + { + varIndex.vt = VT_UI4; + varIndex.ulVal = i; + + hr = pLocationCollection->get_Item( varIndex, + &pLocation ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pLocation->get_Path( &bstrLocationPath ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + if( 0 == wcscmp ( szLocationPath, bstrLocationPath ) ) + { + *pFound = TRUE; + *ppLocation = pLocation.Detach(); + break; + } + + + pLocation.Release(); + + SysFreeString( bstrLocationPath ); + bstrLocationPath = NULL; + } + +exit: + + SysFreeString( bstrLocationPath ); + + return hr; +} + +HRESULT +GetSectionFromLocation( + IN IAppHostConfigLocation * pLocation, + IN CONST WCHAR * szSectionName, + OUT IAppHostElement ** ppSectionElement, + OUT BOOL * pFound + ) +{ + HRESULT hr = NOERROR; + + CComPtr pSectionElement; + + DWORD count; + DWORD i; + + VARIANT varIndex; + VariantInit( &varIndex ); + + BSTR bstrSectionName = NULL; + + *pFound = FALSE; + *ppSectionElement = NULL; + + hr = pLocation->get_Count( &count ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + for( i = 0; i < count; i++ ) + { + varIndex.vt = VT_UI4; + varIndex.ulVal = i; + + + hr = pLocation->get_Item( varIndex, + &pSectionElement ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pSectionElement->get_Name( &bstrSectionName ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + if( 0 == wcscmp ( szSectionName, bstrSectionName ) ) + { + *pFound = TRUE; + *ppSectionElement = pSectionElement.Detach(); + break; + } + + pSectionElement.Release(); + + SysFreeString( bstrSectionName ); + bstrSectionName = NULL; + } + +exit: + + SysFreeString( bstrSectionName ); + + return hr; +} + + +HRESULT +GetAdminElement( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName, + OUT IAppHostElement ** pElement +) +{ + HRESULT hr = S_OK; + BSTR bstrConfigPath = NULL; + BSTR bstrElementName = NULL; + + bstrConfigPath = SysAllocString(szConfigPath); + bstrElementName = SysAllocString(szElementName); + + if (bstrConfigPath == NULL || bstrElementName == NULL) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + hr = pAdminMgr->GetAdminSection( bstrElementName, + bstrConfigPath, + pElement ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + if ( bstrElementName != NULL ) + { + SysFreeString(bstrElementName); + bstrElementName = NULL; + } + if ( bstrConfigPath != NULL ) + { + SysFreeString(bstrConfigPath); + bstrConfigPath = NULL; + } + + return hr; +} + + +HRESULT +ClearAdminElement( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ) +{ + HRESULT hr; + CComPtr pElement; + + hr = GetAdminElement( + pAdminMgr, + szConfigPath, + szElementName, + &pElement + ); + + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) + { + hr = S_OK; + } + else + { + DBGERROR_HR(hr); + } + + goto exit; + } + + hr = pElement->Clear(); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + return hr; +} + + +HRESULT +ClearElementFromAllSites( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ) +{ + HRESULT hr; + CComPtr pSitesCollection; + CComPtr pSiteElement; + CComPtr pChildCollection; + ENUM_INDEX index; + BOOL found; + + // + // Enumerate the sites, remove the specified elements. + // + + hr = GetSitesCollection( + pAdminMgr, + szConfigPath, + &pSitesCollection + ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + for (hr = FindFirstElement(pSitesCollection, &index, &pSiteElement) ; + SUCCEEDED(hr) ; + hr = FindNextElement(pSitesCollection, &index, &pSiteElement)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = pSiteElement->get_ChildElements(&pChildCollection); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if (pChildCollection) + { + hr = ClearChildElementsByName( + pChildCollection, + szElementName, + &found + ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + } + + pSiteElement.Release(); + } + +exit: + + return hr; + +} + + +HRESULT +ClearElementFromAllLocations( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ) +{ + HRESULT hr; + CComPtr pLocationCollection; + CComPtr pLocation; + CComPtr pChildCollection; + ENUM_INDEX index; + + // + // Enum the tags, remove the specified elements. + // + + hr = GetLocationCollection( + pAdminMgr, + szConfigPath, + &pLocationCollection + ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + for (hr = FindFirstLocation(pLocationCollection, &index, &pLocation) ; + SUCCEEDED(hr) ; + hr = FindNextLocation(pLocationCollection, &index, &pLocation)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = ClearLocationElements(pLocation, szElementName); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + pLocation.Release(); + } + +exit: + + return hr; + +} + +HRESULT +ClearLocationElements( + IN IAppHostConfigLocation * pLocation, + IN CONST WCHAR * szElementName + ) +{ + HRESULT hr; + CComPtr pElement; + ENUM_INDEX index; + BOOL matched; + + for (hr = FindFirstLocationElement(pLocation, &index, &pElement) ; + SUCCEEDED(hr) ; + hr = FindNextLocationElement(pLocation, &index, &pElement)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = CompareElementName(pElement, szElementName, &matched); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if (matched) + { + pElement->Clear(); + } + + pElement.Release(); + } + +exit: + + return hr; +} + +HRESULT +CompareElementName( + IN IAppHostElement * pElement, + IN CONST WCHAR * szNameToMatch, + OUT BOOL * pMatched + ) +{ + HRESULT hr; + BSTR bstrElementName = NULL; + + *pMatched = FALSE; // until proven otherwise + + hr = pElement->get_Name(&bstrElementName); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if( 0 == wcscmp ( szNameToMatch, bstrElementName ) ) + { + *pMatched = TRUE; + } + +exit: + + SysFreeString(bstrElementName); + return hr; +} + + +HRESULT +ClearChildElementsByName( + IN IAppHostChildElementCollection * pCollection, + IN CONST WCHAR * szElementName, + OUT BOOL * pFound + ) +{ + HRESULT hr; + CComPtr pElement; + ENUM_INDEX index; + BOOL matched; + + *pFound = FALSE; + + for (hr = FindFirstChildElement(pCollection, &index, &pElement) ; + SUCCEEDED(hr) ; + hr = FindNextChildElement(pCollection, &index, &pElement)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = CompareElementName(pElement, szElementName, &matched); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + if (matched) + { + hr = pElement->Clear(); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + *pFound = TRUE; + } + + pElement.Release(); + } + +exit: + + return hr; +} + + +HRESULT +GetSitesCollection( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + OUT IAppHostElementCollection ** pSitesCollection + ) +{ + HRESULT hr; + CComPtr pSitesElement; + BSTR bstrConfigPath; + BSTR bstrSitesSectionName; + + bstrConfigPath = SysAllocString(szConfigPath); + bstrSitesSectionName = SysAllocString(L"system.applicationHost/sites"); + *pSitesCollection = NULL; + + if (bstrConfigPath == NULL || bstrSitesSectionName == NULL) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + // + // Chase down the sites collection. + // + + hr = pAdminMgr->GetAdminSection( bstrSitesSectionName, + bstrConfigPath, + &pSitesElement ); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pSitesElement->get_Collection(pSitesCollection); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + SysFreeString(bstrSitesSectionName); + SysFreeString(bstrConfigPath); + return hr; +} + + +HRESULT +GetLocationCollection( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + OUT IAppHostConfigLocationCollection ** pLocationCollection + ) +{ + HRESULT hr; + BSTR bstrConfigPath; + CComPtr pConfigMgr; + CComPtr pConfigFile; + + bstrConfigPath = SysAllocString(szConfigPath); + *pLocationCollection = NULL; + + if (bstrConfigPath == NULL) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + hr = pAdminMgr->get_ConfigManager(&pConfigMgr); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pConfigMgr->GetConfigFile(bstrConfigPath, &pConfigFile); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pConfigFile->get_Locations(pLocationCollection); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + goto exit; + } + +exit: + + SysFreeString(bstrConfigPath); + return hr; +} + + +HRESULT +FindFirstElement( + IN IAppHostElementCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + hr = pCollection->get_Count(&pIndex->Count); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + return hr; + } + + VariantInit(&pIndex->Index); + pIndex->Index.vt = VT_UI4; + pIndex->Index.ulVal = 0; + + return FindNextElement(pCollection, pIndex, pElement); +} + +HRESULT +FindNextElement( + IN IAppHostElementCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + *pElement = NULL; + + if (pIndex->Index.ulVal >= pIndex->Count) + { + return S_FALSE; + } + + hr = pCollection->get_Item(pIndex->Index, pElement); + + if (SUCCEEDED(hr)) + { + pIndex->Index.ulVal++; + } + + return hr; +} + +HRESULT +FindFirstChildElement( + IN IAppHostChildElementCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + hr = pCollection->get_Count(&pIndex->Count); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + return hr; + } + + VariantInit(&pIndex->Index); + pIndex->Index.vt = VT_UI4; + pIndex->Index.ulVal = 0; + + return FindNextChildElement(pCollection, pIndex, pElement); +} + +HRESULT +FindNextChildElement( + IN IAppHostChildElementCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + *pElement = NULL; + + if (pIndex->Index.ulVal >= pIndex->Count) + { + return S_FALSE; + } + + hr = pCollection->get_Item(pIndex->Index, pElement); + + if (SUCCEEDED(hr)) + { + pIndex->Index.ulVal++; + } + + return hr; +} + +HRESULT +FindFirstLocation( + IN IAppHostConfigLocationCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostConfigLocation ** pLocation + ) +{ + HRESULT hr; + + hr = pCollection->get_Count(&pIndex->Count); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + return hr; + } + + VariantInit(&pIndex->Index); + pIndex->Index.vt = VT_UI4; + pIndex->Index.ulVal = 0; + + return FindNextLocation(pCollection, pIndex, pLocation); +} + +HRESULT +FindNextLocation( + IN IAppHostConfigLocationCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostConfigLocation ** pLocation + ) +{ + HRESULT hr; + + *pLocation = NULL; + + if (pIndex->Index.ulVal >= pIndex->Count) + { + return S_FALSE; + } + + hr = pCollection->get_Item(pIndex->Index, pLocation); + + if (SUCCEEDED(hr)) + { + pIndex->Index.ulVal++; + } + + return hr; +} + +HRESULT +FindFirstLocationElement( + IN IAppHostConfigLocation * pLocation, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + hr = pLocation->get_Count(&pIndex->Count); + + if (FAILED(hr)) + { + DBGERROR_HR(hr); + return hr; + } + + VariantInit(&pIndex->Index); + pIndex->Index.vt = VT_UI4; + pIndex->Index.ulVal = 0; + + return FindNextLocationElement(pLocation, pIndex, pElement); +} + +HRESULT +FindNextLocationElement( + IN IAppHostConfigLocation * pLocation, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ) +{ + HRESULT hr; + + *pElement = NULL; + + if (pIndex->Index.ulVal >= pIndex->Count) + { + return S_FALSE; + } + + hr = pLocation->get_Item(pIndex->Index, pElement); + + if (SUCCEEDED(hr)) + { + pIndex->Index.ulVal++; + } + + return hr; +} + +HRESULT +GetSharedConfigEnabled( + BOOL * pfIsSharedConfig +) +/*++ + +Routine Description: + Search the configuration for the shared configuration property. + +Arguments: + + pfIsSharedConfig - true if shared configuration is enabled + +Return Value: + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + IAppHostAdminManager *pAdminManager = NULL; + + BSTR bstrSectionName = NULL; + BSTR bstrConfigPath = NULL; + + IAppHostElement * pConfigRedirSection = NULL; + + + bstrSectionName = SysAllocString( L"configurationRedirection" ); + + if ( bstrSectionName == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + bstrConfigPath = SysAllocString( L"MACHINE/REDIRECTION" ); + if ( bstrConfigPath == NULL ) + { + hr = E_OUTOFMEMORY; + DBGERROR_HR(hr); + goto exit; + } + + hr = CoCreateInstance( CLSID_AppHostAdminManager, + NULL, + CLSCTX_INPROC_SERVER, + IID_IAppHostAdminManager, + (VOID **)&pAdminManager ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = pAdminManager->GetAdminSection( bstrSectionName, + bstrConfigPath, + &pConfigRedirSection ); + if( FAILED(hr) ) + { + DBGERROR_HR(hr); + goto exit; + } + + hr = GetElementBoolProperty( pConfigRedirSection, + L"enabled", + pfIsSharedConfig ); + + if ( FAILED( hr ) ) + { + DBGERROR_HR(hr); + goto exit; + } + + pConfigRedirSection->Release(); + pConfigRedirSection = NULL; + + +exit: + + // + // dump config exception to setup log file (if available) + // + + if ( pConfigRedirSection != NULL ) + { + pConfigRedirSection->Release(); + } + + if ( pAdminManager != NULL ) + { + pAdminManager->Release(); + } + + if ( bstrConfigPath != NULL ) + { + SysFreeString( bstrConfigPath ); + } + + if ( bstrSectionName != NULL ) + { + SysFreeString( bstrSectionName ); + } + + return hr; +} diff --git a/src/AspNetCoreModuleV2/IISLib/ahutil.h b/src/AspNetCoreModuleV2/IISLib/ahutil.h new file mode 100644 index 0000000000..5694b21b7e --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/ahutil.h @@ -0,0 +1,258 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include + +HRESULT +SetElementProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + IN CONST VARIANT * varPropValue + ); + +HRESULT +SetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + IN CONST WCHAR * szPropValue + ); + +HRESULT +GetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + OUT BSTR * pbstrPropValue + ); + +HRESULT +GetElementStringProperty( + IN IAppHostElement * pElement, + IN CONST WCHAR * szPropName, + OUT STRU * pstrPropValue + ); + +HRESULT +GetElementBoolProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT BOOL * pBool + ); + +HRESULT +GetElementBoolProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT bool * pBool + ); + +HRESULT +GetElementChildByName( + IN IAppHostElement * pElement, + IN LPCWSTR pszElementName, + OUT IAppHostElement ** ppChildElement + ); + +HRESULT +GetElementDWORDProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT DWORD * pdwValue + ); + +HRESULT +GetElementLONGLONGProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT LONGLONG * pllValue +); + + +HRESULT +GetElementRawTimeSpanProperty( + IN IAppHostElement * pElement, + IN LPCWSTR pszPropertyName, + OUT ULONGLONG * pulonglong + ); + +#define FIND_ELEMENT_CASE_SENSITIVE 0x00000000 +#define FIND_ELEMENT_CASE_INSENSITIVE 0x00000001 + +HRESULT +DeleteElementFromCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + BOOL * pfDeleted + ); + +HRESULT +DeleteAllElementsFromCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + UINT * pNumDeleted + ); + +HRESULT +FindElementInCollection( + IAppHostElementCollection *pCollection, + CONST WCHAR * szKeyName, + CONST WCHAR * szKeyValue, + ULONG BehaviorFlags, + OUT ULONG * pIndex + ); + +HRESULT +VariantAssign( + IN OUT VARIANT * pv, + IN CONST WCHAR * sz + ); + +HRESULT +GetLocationFromFile( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szLocationPath, + OUT IAppHostConfigLocation ** ppLocation, + OUT BOOL * pFound + ); + +HRESULT +GetSectionFromLocation( + IN IAppHostConfigLocation * pLocation, + IN CONST WCHAR * szSectionName, + OUT IAppHostElement ** ppSectionElement, + OUT BOOL * pFound + ); + +HRESULT +GetAdminElement( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName, + OUT IAppHostElement ** pElement + ); + +HRESULT +ClearAdminElement( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ); + +HRESULT +ClearElementFromAllSites( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ); + +HRESULT +ClearElementFromAllLocations( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + IN CONST WCHAR * szElementName + ); + +HRESULT +ClearLocationElements( + IN IAppHostConfigLocation * pLocation, + IN CONST WCHAR * szElementName + ); + +HRESULT +CompareElementName( + IN IAppHostElement * pElement, + IN CONST WCHAR * szNameToMatch, + OUT BOOL * pMatched + ); + +HRESULT +ClearChildElementsByName( + IN IAppHostChildElementCollection * pCollection, + IN CONST WCHAR * szElementName, + OUT BOOL * pFound + ); + +HRESULT +GetSitesCollection( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + OUT IAppHostElementCollection ** pSitesCollection + ); + +HRESULT +GetLocationCollection( + IN IAppHostAdminManager * pAdminMgr, + IN CONST WCHAR * szConfigPath, + OUT IAppHostConfigLocationCollection ** pLocationCollection + ); + +struct ENUM_INDEX +{ + VARIANT Index; + ULONG Count; +}; + +HRESULT +FindFirstElement( + IN IAppHostElementCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindNextElement( + IN IAppHostElementCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindFirstChildElement( + IN IAppHostChildElementCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindNextChildElement( + IN IAppHostChildElementCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindFirstLocation( + IN IAppHostConfigLocationCollection * pCollection, + OUT ENUM_INDEX * pIndex, + OUT IAppHostConfigLocation ** pLocation + ); + +HRESULT +FindNextLocation( + IN IAppHostConfigLocationCollection * pCollection, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostConfigLocation ** pLocation + ); + +HRESULT +FindFirstLocationElement( + IN IAppHostConfigLocation * pLocation, + OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT +FindNextLocationElement( + IN IAppHostConfigLocation * pLocation, + IN OUT ENUM_INDEX * pIndex, + OUT IAppHostElement ** pElement + ); + +HRESULT GetSharedConfigEnabled( + BOOL * pfIsSharedConfig +); \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/IISLib/base64.cpp b/src/AspNetCoreModuleV2/IISLib/base64.cpp new file mode 100644 index 0000000000..b8b6a0bf74 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/base64.cpp @@ -0,0 +1,482 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +DWORD +Base64Encode( + __in_bcount(cbDecodedBufferSize) VOID * pDecodedBuffer, + IN DWORD cbDecodedBufferSize, + __out_ecount_opt(cchEncodedStringSize) PWSTR pszEncodedString, + IN DWORD cchEncodedStringSize, + __out_opt DWORD * pcchEncoded + ) +/*++ + +Routine Description: + + Decode a base64-encoded string. + +Arguments: + + pDecodedBuffer (IN) - buffer to encode. + cbDecodedBufferSize (IN) - size of buffer to encode. + cchEncodedStringSize (IN) - size of the buffer for the encoded string. + pszEncodedString (OUT) = the encoded string. + pcchEncoded (OUT) - size in characters of the encoded string. + +Return Values: + + 0 - success. + E_OUTOFMEMORY + +--*/ +{ + static WCHAR rgchEncodeTable[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + DWORD ib; + DWORD ich; + DWORD cchEncoded; + BYTE b0, b1, b2; + BYTE * pbDecodedBuffer = (BYTE *) pDecodedBuffer; + + // Calculate encoded string size. + cchEncoded = 1 + (cbDecodedBufferSize + 2) / 3 * 4; + + if (NULL != pcchEncoded) { + *pcchEncoded = cchEncoded; + } + + if (cchEncodedStringSize == 0 && pszEncodedString == NULL) { + return ERROR_SUCCESS; + } + + if (cchEncodedStringSize < cchEncoded) { + // Given buffer is too small to hold encoded string. + return ERROR_INSUFFICIENT_BUFFER; + } + + // Encode data byte triplets into four-byte clusters. + ib = ich = 0; + while (ib < cbDecodedBufferSize) { + b0 = pbDecodedBuffer[ib++]; + b1 = (ib < cbDecodedBufferSize) ? pbDecodedBuffer[ib++] : 0; + b2 = (ib < cbDecodedBufferSize) ? pbDecodedBuffer[ib++] : 0; + + // + // The checks below for buffer overflow seems redundant to me. + // But it's the only way I can find to keep OACR quiet so it + // will have to do. + // + + pszEncodedString[ich++] = rgchEncodeTable[b0 >> 2]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[((b0 << 4) & 0x30) | ((b1 >> 4) & 0x0f)]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[((b1 << 2) & 0x3c) | ((b2 >> 6) & 0x03)]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[b2 & 0x3f]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + } + + // Pad the last cluster as necessary to indicate the number of data bytes + // it represents. + switch (cbDecodedBufferSize % 3) { + case 0: + break; + case 1: + pszEncodedString[ich - 2] = '='; + __fallthrough; + case 2: + pszEncodedString[ich - 1] = '='; + break; + } + + // Null-terminate the encoded string. + pszEncodedString[ich++] = '\0'; + + DBG_ASSERT(ich == cchEncoded); + + return ERROR_SUCCESS; +} + + +DWORD +Base64Decode( + __in PCWSTR pszEncodedString, + __out_opt VOID * pDecodeBuffer, + __in DWORD cbDecodeBufferSize, + __out_opt DWORD * pcbDecoded + ) +/*++ + +Routine Description: + + Decode a base64-encoded string. + +Arguments: + + pszEncodedString (IN) - base64-encoded string to decode. + cbDecodeBufferSize (IN) - size in bytes of the decode buffer. + pbDecodeBuffer (OUT) - holds the decoded data. + pcbDecoded (OUT) - number of data bytes in the decoded data (if success or + STATUS_BUFFER_TOO_SMALL). + +Return Values: + + 0 - success. + E_OUTOFMEMORY + E_INVALIDARG + +--*/ +{ +#define NA (255) +#define DECODE(x) (((ULONG)(x) < sizeof(rgbDecodeTable)) ? rgbDecodeTable[x] : NA) + + static BYTE rgbDecodeTable[128] = { + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 0-15 + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 16-31 + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 62, NA, NA, NA, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NA, NA, NA, 0, NA, NA, // 48-63 + NA, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NA, NA, NA, NA, NA, // 80-95 + NA, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NA, NA, NA, NA, NA, // 112-127 + }; + + DWORD cbDecoded; + DWORD cchEncodedSize; + DWORD ich; + DWORD ib; + BYTE b0, b1, b2, b3; + BYTE * pbDecodeBuffer = (BYTE *) pDecodeBuffer; + + cchEncodedSize = (DWORD)wcslen(pszEncodedString); + if (NULL != pcbDecoded) { + *pcbDecoded = 0; + } + + if ((0 == cchEncodedSize) || (0 != (cchEncodedSize % 4))) { + // Input string is not sized correctly to be base64. + return ERROR_INVALID_PARAMETER; + } + + // Calculate decoded buffer size. + cbDecoded = (cchEncodedSize + 3) / 4 * 3; + if (pszEncodedString[cchEncodedSize-1] == '=') { + if (pszEncodedString[cchEncodedSize-2] == '=') { + // Only one data byte is encoded in the last cluster. + cbDecoded -= 2; + } + else { + // Only two data bytes are encoded in the last cluster. + cbDecoded -= 1; + } + } + + if (NULL != pcbDecoded) { + *pcbDecoded = cbDecoded; + } + + if (cbDecodeBufferSize == 0 && pDecodeBuffer == NULL) { + return ERROR_SUCCESS; + } + + if (cbDecoded > cbDecodeBufferSize) { + // Supplied buffer is too small. + return ERROR_INSUFFICIENT_BUFFER; + } + + // Decode each four-byte cluster into the corresponding three data bytes. + ich = ib = 0; + while (ich < cchEncodedSize) { + b0 = DECODE(pszEncodedString[ich]); ich++; + b1 = DECODE(pszEncodedString[ich]); ich++; + b2 = DECODE(pszEncodedString[ich]); ich++; + b3 = DECODE(pszEncodedString[ich]); ich++; + + if ((NA == b0) || (NA == b1) || (NA == b2) || (NA == b3)) { + // Contents of input string are not base64. + return ERROR_INVALID_PARAMETER; + } + + pbDecodeBuffer[ib++] = (b0 << 2) | (b1 >> 4); + + if (ib < cbDecoded) { + pbDecodeBuffer[ib++] = (b1 << 4) | (b2 >> 2); + + if (ib < cbDecoded) { + pbDecodeBuffer[ib++] = (b2 << 6) | b3; + } + } + } + + DBG_ASSERT(ib == cbDecoded); + + return ERROR_SUCCESS; +} + + +DWORD +Base64Encode( + __in_bcount(cbDecodedBufferSize) VOID * pDecodedBuffer, + IN DWORD cbDecodedBufferSize, + __out_ecount_opt(cchEncodedStringSize) PSTR pszEncodedString, + IN DWORD cchEncodedStringSize, + __out_opt DWORD * pcchEncoded + ) +/*++ + +Routine Description: + + Decode a base64-encoded string. + +Arguments: + + pDecodedBuffer (IN) - buffer to encode. + cbDecodedBufferSize (IN) - size of buffer to encode. + cchEncodedStringSize (IN) - size of the buffer for the encoded string. + pszEncodedString (OUT) = the encoded string. + pcchEncoded (OUT) - size in characters of the encoded string. + +Return Values: + + 0 - success. + E_OUTOFMEMORY + +--*/ +{ + static CHAR rgchEncodeTable[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + DWORD ib; + DWORD ich; + DWORD cchEncoded; + BYTE b0, b1, b2; + BYTE * pbDecodedBuffer = (BYTE *) pDecodedBuffer; + + // Calculate encoded string size. + cchEncoded = 1 + (cbDecodedBufferSize + 2) / 3 * 4; + + if (NULL != pcchEncoded) { + *pcchEncoded = cchEncoded; + } + + if (cchEncodedStringSize == 0 && pszEncodedString == NULL) { + return ERROR_SUCCESS; + } + + if (cchEncodedStringSize < cchEncoded) { + // Given buffer is too small to hold encoded string. + return ERROR_INSUFFICIENT_BUFFER; + } + + // Encode data byte triplets into four-byte clusters. + ib = ich = 0; + while (ib < cbDecodedBufferSize) { + b0 = pbDecodedBuffer[ib++]; + b1 = (ib < cbDecodedBufferSize) ? pbDecodedBuffer[ib++] : 0; + b2 = (ib < cbDecodedBufferSize) ? pbDecodedBuffer[ib++] : 0; + + // + // The checks below for buffer overflow seems redundant to me. + // But it's the only way I can find to keep OACR quiet so it + // will have to do. + // + + pszEncodedString[ich++] = rgchEncodeTable[b0 >> 2]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[((b0 << 4) & 0x30) | ((b1 >> 4) & 0x0f)]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[((b1 << 2) & 0x3c) | ((b2 >> 6) & 0x03)]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + + pszEncodedString[ich++] = rgchEncodeTable[b2 & 0x3f]; + if ( ich >= cchEncodedStringSize ) + { + DBG_ASSERT( FALSE ); + return ERROR_BUFFER_OVERFLOW; + } + } + + // Pad the last cluster as necessary to indicate the number of data bytes + // it represents. + switch (cbDecodedBufferSize % 3) { + case 0: + break; + case 1: + pszEncodedString[ich - 2] = '='; + __fallthrough; + case 2: + pszEncodedString[ich - 1] = '='; + break; + } + + // Null-terminate the encoded string. + pszEncodedString[ich++] = '\0'; + + DBG_ASSERT(ich == cchEncoded); + + return ERROR_SUCCESS; +} + + +DWORD +Base64Decode( + __in PCSTR pszEncodedString, + __out_opt VOID * pDecodeBuffer, + __in DWORD cbDecodeBufferSize, + __out_opt DWORD * pcbDecoded + ) +/*++ + +Routine Description: + + Decode a base64-encoded string. + +Arguments: + + pszEncodedString (IN) - base64-encoded string to decode. + cbDecodeBufferSize (IN) - size in bytes of the decode buffer. + pbDecodeBuffer (OUT) - holds the decoded data. + pcbDecoded (OUT) - number of data bytes in the decoded data (if success or + STATUS_BUFFER_TOO_SMALL). + +Return Values: + + 0 - success. + E_OUTOFMEMORY + E_INVALIDARG + +--*/ +{ +#define NA (255) +#define DECODE(x) (((ULONG)(x) < sizeof(rgbDecodeTable)) ? rgbDecodeTable[x] : NA) + + static BYTE rgbDecodeTable[128] = { + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 0-15 + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 16-31 + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 62, NA, NA, NA, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NA, NA, NA, 0, NA, NA, // 48-63 + NA, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NA, NA, NA, NA, NA, // 80-95 + NA, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NA, NA, NA, NA, NA, // 112-127 + }; + + DWORD cbDecoded; + DWORD cchEncodedSize; + DWORD ich; + DWORD ib; + BYTE b0, b1, b2, b3; + BYTE * pbDecodeBuffer = (BYTE *) pDecodeBuffer; + + cchEncodedSize = (DWORD)strlen(pszEncodedString); + if (NULL != pcbDecoded) { + *pcbDecoded = 0; + } + + if ((0 == cchEncodedSize) || (0 != (cchEncodedSize % 4))) { + // Input string is not sized correctly to be base64. + return ERROR_INVALID_PARAMETER; + } + + // Calculate decoded buffer size. + cbDecoded = (cchEncodedSize + 3) / 4 * 3; + if (pszEncodedString[cchEncodedSize-1] == '=') { + if (pszEncodedString[cchEncodedSize-2] == '=') { + // Only one data byte is encoded in the last cluster. + cbDecoded -= 2; + } + else { + // Only two data bytes are encoded in the last cluster. + cbDecoded -= 1; + } + } + + if (NULL != pcbDecoded) { + *pcbDecoded = cbDecoded; + } + + if (cbDecodeBufferSize == 0 && pDecodeBuffer == NULL) { + return ERROR_SUCCESS; + } + + if (cbDecoded > cbDecodeBufferSize) { + // Supplied buffer is too small. + return ERROR_INSUFFICIENT_BUFFER; + } + + // Decode each four-byte cluster into the corresponding three data bytes. + ich = ib = 0; + while (ich < cchEncodedSize) { + b0 = DECODE(pszEncodedString[ich]); ich++; + b1 = DECODE(pszEncodedString[ich]); ich++; + b2 = DECODE(pszEncodedString[ich]); ich++; + b3 = DECODE(pszEncodedString[ich]); ich++; + + if ((NA == b0) || (NA == b1) || (NA == b2) || (NA == b3)) { + // Contents of input string are not base64. + return ERROR_INVALID_PARAMETER; + } + + pbDecodeBuffer[ib++] = (b0 << 2) | (b1 >> 4); + + if (ib < cbDecoded) { + pbDecodeBuffer[ib++] = (b1 << 4) | (b2 >> 2); + + if (ib < cbDecoded) { + pbDecodeBuffer[ib++] = (b2 << 6) | b3; + } + } + } + + DBG_ASSERT(ib == cbDecoded); + + return ERROR_SUCCESS; +} + diff --git a/src/AspNetCoreModuleV2/IISLib/base64.h b/src/AspNetCoreModuleV2/IISLib/base64.h new file mode 100644 index 0000000000..469b074d73 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/base64.h @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _BASE64_H_ +#define _BASE64_H_ + +DWORD +Base64Encode( + __in_bcount( cbDecodedBufferSize ) VOID * pDecodedBuffer, + IN DWORD cbDecodedBufferSize, + __out_ecount_opt( cchEncodedStringSize ) PWSTR pszEncodedString, + IN DWORD cchEncodedStringSize, + __out_opt DWORD * pcchEncoded + ); + +DWORD +Base64Decode( + __in PCWSTR pszEncodedString, + __out_opt VOID * pDecodeBuffer, + __in DWORD cbDecodeBufferSize, + __out_opt DWORD * pcbDecoded + ); + +DWORD +Base64Encode( + __in_bcount( cbDecodedBufferSize ) VOID * pDecodedBuffer, + IN DWORD cbDecodedBufferSize, + __out_ecount_opt( cchEncodedStringSize ) PSTR pszEncodedString, + IN DWORD cchEncodedStringSize, + __out_opt DWORD * pcchEncoded + ); + +DWORD +Base64Decode( + __in PCSTR pszEncodedString, + __out_opt VOID * pDecodeBuffer, + __in DWORD cbDecodeBufferSize, + __out_opt DWORD * pcbDecoded + ); + +#endif // _BASE64_HXX_ + diff --git a/src/AspNetCoreModuleV2/IISLib/buffer.h b/src/AspNetCoreModuleV2/IISLib/buffer.h new file mode 100644 index 0000000000..1d68155387 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/buffer.h @@ -0,0 +1,271 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include + + +// +// BUFFER_T class shouldn't be used directly. Use BUFFER specialization class instead. +// The only BUFFER_T partners are STRU and STRA classes. +// BUFFER_T cannot hold other but primitive types since it doesn't call +// constructor and destructor. +// +// Note: Size is in bytes. +// +template +class BUFFER_T +{ +public: + + BUFFER_T() + : m_cbBuffer( sizeof(m_rgBuffer) ), + m_fHeapAllocated( false ), + m_pBuffer(m_rgBuffer) + /*++ + Description: + + Default constructor where the inline buffer is used. + + Arguments: + + None. + + Returns: + + None. + + --*/ + { + } + + BUFFER_T( + __inout_bcount(cbInit) T* pbInit, + __in DWORD cbInit + ) : m_pBuffer( pbInit ), + m_cbBuffer( cbInit ), + m_fHeapAllocated( false ) + /*++ + Description: + + Instantiate BUFFER, initially using pbInit as buffer + This is useful for stack-buffers and inline-buffer class members + (see STACK_BUFFER and INLINE_BUFFER_INIT below) + + BUFFER does not free pbInit. + + Arguments: + + pbInit - Initial buffer to use. + cbInit - Size of pbInit in bytes (not in elements). + + Returns: + + None. + + --*/ + { + _ASSERTE( NULL != pbInit ); + _ASSERTE( cbInit > 0 ); + } + + ~BUFFER_T() + { + if( IsHeapAllocated() ) + { + _ASSERTE( NULL != m_pBuffer ); + HeapFree( GetProcessHeap(), 0, m_pBuffer ); + m_pBuffer = NULL; + m_cbBuffer = 0; + m_fHeapAllocated = false; + } + } + + T* + QueryPtr( + VOID + ) const + { + // + // Return pointer to data buffer. + // + return m_pBuffer; + } + + DWORD + QuerySize( + VOID + ) const + { + // + // Return number of bytes. + // + return m_cbBuffer; + } + + __success(return == true) + bool + Resize( + const SIZE_T cbNewSize, + const bool fZeroMemoryBeyondOldSize = false + ) + /*++ + Description: + + Resizes the buffer. + + Arguments: + + cbNewSize - Size in bytes to grow to. + fZeroMemoryBeyondOldSize + - Whether to zero the region of memory of the + new buffer beyond the original size. + + Returns: + + TRUE on success, FALSE on failure. + + --*/ + { + PVOID pNewMem; + + if ( cbNewSize <= m_cbBuffer ) + { + return true; + } + + if ( cbNewSize > MAXDWORD ) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return false; + } + + DWORD dwHeapAllocFlags = fZeroMemoryBeyondOldSize ? HEAP_ZERO_MEMORY : 0; + + if( IsHeapAllocated() ) + { + pNewMem = HeapReAlloc( GetProcessHeap(), dwHeapAllocFlags, m_pBuffer, cbNewSize ); + } + else + { + pNewMem = HeapAlloc( GetProcessHeap(), dwHeapAllocFlags, cbNewSize ); + } + + if( pNewMem == NULL ) + { + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + return false; + } + + if( !IsHeapAllocated() ) + { + // + // First time this block is allocated. Copy over old contents. + // + memcpy_s( pNewMem, static_cast(cbNewSize), m_pBuffer, m_cbBuffer ); + m_fHeapAllocated = true; + } + + m_pBuffer = reinterpret_cast(pNewMem); + m_cbBuffer = static_cast(cbNewSize); + + _ASSERTE( m_pBuffer != NULL ); + + return true; + } + +private: + + bool + IsHeapAllocated( + VOID + ) const + { + return m_fHeapAllocated; + } + + // + // The default inline buffer. + // This member should be at the beginning for alignment purposes. + // + T m_rgBuffer[LENGTH]; + + // + // Is m_pBuffer dynamically allocated? + // + bool m_fHeapAllocated; + + // + // Size of the buffer as requested by client in bytes. + // + DWORD m_cbBuffer; + + // + // Pointer to buffer. + // + __field_bcount_full(m_cbBuffer) + T* m_pBuffer; +}; + +// +// Resizes the buffer by 2 if the ideal size is bigger +// than the buffer length. That give us lg(n) allocations. +// +// Use template inferring like: +// +// BUFFER buff; +// hr = ResizeBufferByTwo(buff, 100); +// +template +HRESULT +ResizeBufferByTwo( + BUFFER_T& Buffer, + SIZE_T cbIdealSize, + bool fZeroMemoryBeyondOldSize = false +) +{ + if (cbIdealSize > Buffer.QuerySize()) + { + if (!Buffer.Resize(max(cbIdealSize, static_cast(Buffer.QuerySize() * 2)), + fZeroMemoryBeyondOldSize)) + { + return E_OUTOFMEMORY; + } + } + return S_OK; +} + + +// +// +// Lots of code uses BUFFER class to store a bunch of different +// structures, so m_rgBuffer needs to be 8 byte aligned when it is used +// as an opaque buffer. +// +#define INLINED_BUFFER_LEN 32 +typedef BUFFER_T BUFFER; + +// +// Assumption of macros below for pointer alignment purposes +// +C_ASSERT( sizeof(VOID*) <= sizeof(ULONGLONG) ); + +// +// Declare a BUFFER that will use stack memory of +// bytes. If the buffer overflows then a heap buffer will be allocated. +// +#define STACK_BUFFER( _name, _size ) \ + ULONGLONG __aqw##_name[ ( ( (_size) + sizeof(ULONGLONG) - 1 ) / sizeof(ULONGLONG) ) ]; \ + BUFFER _name( (BYTE*)__aqw##_name, sizeof(__aqw##_name) ) + +// +// Macros for declaring and initializing a BUFFER that will use inline memory +// of bytes as a member of an object. +// +#define INLINE_BUFFER( _name, _size ) \ + ULONGLONG __aqw##_name[ ( ( (_size) + sizeof(ULONGLONG) - 1 ) / sizeof(ULONGLONG) ) ]; \ + BUFFER _name; + +#define INLINE_BUFFER_INIT( _name ) \ + _name( (BYTE*)__aqw##_name, sizeof( __aqw##_name ) ) diff --git a/src/AspNetCoreModuleV2/IISLib/datetime.h b/src/AspNetCoreModuleV2/IISLib/datetime.h new file mode 100644 index 0000000000..fd09b7a6a0 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/datetime.h @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _DATETIME_H_ +#define _DATETIME_H_ + +BOOL +StringTimeToFileTime( + PCSTR pszTime, + ULONGLONG * pulTime +); + +#endif + diff --git a/src/AspNetCoreModuleV2/IISLib/dbgutil.h b/src/AspNetCoreModuleV2/IISLib/dbgutil.h new file mode 100644 index 0000000000..45c26777a9 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/dbgutil.h @@ -0,0 +1,102 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _DBGUTIL_H_ +#define _DBGUTIL_H_ + +#include + +// +// TODO +// Using _CrtDbg implementation. If hooking is desired +// wrappers should be provided here so that we can reimplement +// if neecessary. +// +// IF_DEBUG/DEBUG FLAGS +// +// registry configuration +// + +// +// Debug error levels for DEBUG_FLAGS_VAR. +// + +#define DEBUG_FLAG_INFO 0x00000001 +#define DEBUG_FLAG_WARN 0x00000002 +#define DEBUG_FLAG_ERROR 0x00000004 + +// +// Predefined error level values. These are backwards from the +// windows definitions. +// + +#define DEBUG_FLAGS_INFO (DEBUG_FLAG_ERROR | DEBUG_FLAG_WARN | DEBUG_FLAG_INFO) +#define DEBUG_FLAGS_WARN (DEBUG_FLAG_ERROR | DEBUG_FLAG_WARN) +#define DEBUG_FLAGS_ERROR (DEBUG_FLAG_ERROR) +#define DEBUG_FLAGS_ANY (DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) + +// +// Global variables to control tracing. Generally per module +// + +#ifndef DEBUG_FLAGS_VAR +#define DEBUG_FLAGS_VAR g_dwDebugFlags +#endif + +#ifndef DEBUG_LABEL_VAR +#define DEBUG_LABEL_VAR g_szDebugLabel +#endif + +extern PCSTR DEBUG_LABEL_VAR; +extern DWORD DEBUG_FLAGS_VAR; + +// +// Module should make this declaration globally. +// + +#define DECLARE_DEBUG_PRINT_OBJECT( _pszLabel_ ) \ + PCSTR DEBUG_LABEL_VAR = _pszLabel_; \ + DWORD DEBUG_FLAGS_VAR = DEBUG_FLAGS_ANY; \ + +#define DECLARE_DEBUG_PRINT_OBJECT2( _pszLabel_, _dwLevel_ ) \ + PCSTR DEBUG_LABEL_VAR = _pszLabel_; \ + DWORD DEBUG_FLAGS_VAR = _dwLevel_; \ + +// +// This doesn't do anything now. Should be safe to call in dll main. +// + +#define CREATE_DEBUG_PRINT_OBJECT + +// +// Trace macros +// + +#define DBG_CONTEXT _CRT_WARN, __FILE__, __LINE__, DEBUG_LABEL_VAR + +#ifdef DEBUG +#define DBGINFO(args) \ +{if (DEBUG_FLAGS_VAR & DEBUG_FLAG_INFO) { _CrtDbgReport args; }} +#define DBGWARN(args) \ +{if (DEBUG_FLAGS_VAR & DEBUG_FLAG_WARN) { _CrtDbgReport args; }} +#define DBGERROR(args) \ +{if (DEBUG_FLAGS_VAR & DEBUG_FLAG_ERROR) { _CrtDbgReport args; }} +#else +#define DBGINFO +#define DBGWARN +#define DBGERROR +#endif + +#define DBGPRINTF DBGINFO + +// +// Simple error traces +// + +#define DBGERROR_HR( _hr_ ) \ + DBGERROR((DBG_CONTEXT, "hr=0x%x\n", _hr_)) + +#define DBGERROR_STATUS( _status_ ) \ + DBGERROR((DBG_CONTEXT, "status=%d\n", _status_)) + +#endif diff --git a/src/AspNetCoreModuleV2/IISLib/hashfn.h b/src/AspNetCoreModuleV2/IISLib/hashfn.h new file mode 100644 index 0000000000..a7bfeda2cf --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/hashfn.h @@ -0,0 +1,325 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef __HASHFN_H__ +#define __HASHFN_H__ + + +// Produce a scrambled, randomish number in the range 0 to RANDOM_PRIME-1. +// Applying this to the results of the other hash functions is likely to +// produce a much better distribution, especially for the identity hash +// functions such as Hash(char c), where records will tend to cluster at +// the low end of the hashtable otherwise. LKRhash applies this internally +// to all hash signatures for exactly this reason. + +inline DWORD +HashScramble(DWORD dwHash) +{ + // Here are 10 primes slightly greater than 10^9 + // 1000000007, 1000000009, 1000000021, 1000000033, 1000000087, + // 1000000093, 1000000097, 1000000103, 1000000123, 1000000181. + + // default value for "scrambling constant" + const DWORD RANDOM_CONSTANT = 314159269UL; + // large prime number, also used for scrambling + const DWORD RANDOM_PRIME = 1000000007UL; + + return (RANDOM_CONSTANT * dwHash) % RANDOM_PRIME ; +} + + +// Faster scrambling function suggested by Eric Jacobsen + +inline DWORD +HashRandomizeBits(DWORD dw) +{ + return (((dw * 1103515245 + 12345) >> 16) + | ((dw * 69069 + 1) & 0xffff0000)); +} + + +// Small prime number used as a multiplier in the supplied hash functions +const DWORD HASH_MULTIPLIER = 101; + +#undef HASH_SHIFT_MULTIPLY + +#ifdef HASH_SHIFT_MULTIPLY +# define HASH_MULTIPLY(dw) (((dw) << 7) - (dw)) +#else +# define HASH_MULTIPLY(dw) ((dw) * HASH_MULTIPLIER) +#endif + +// Fast, simple hash function that tends to give a good distribution. +// Apply HashScramble to the result if you're using this for something +// other than LKRhash. + +inline DWORD +HashString( + const char* psz, + DWORD dwHash = 0) +{ + // force compiler to use unsigned arithmetic + const unsigned char* upsz = (const unsigned char*) psz; + + for ( ; *upsz; ++upsz) + dwHash = HASH_MULTIPLY(dwHash) + *upsz; + + return dwHash; +} + +inline DWORD +HashString( + __in_ecount(cch) const char* psz, + __in DWORD cch, + __in DWORD dwHash +) +{ + // force compiler to use unsigned arithmetic + const unsigned char* upsz = (const unsigned char*) psz; + + for (DWORD Index = 0; + Index < cch; + ++Index, ++upsz) + { + dwHash = HASH_MULTIPLY(dwHash) + *upsz; + } + + return dwHash; +} + + +// Unicode version of above + +inline DWORD +HashString( + const wchar_t* pwsz, + DWORD dwHash = 0) +{ + for ( ; *pwsz; ++pwsz) + dwHash = HASH_MULTIPLY(dwHash) + *pwsz; + + return dwHash; +} + +// Based on length of the string instead of null-terminating character + +inline DWORD +HashString( + __in_ecount(cch) const wchar_t* pwsz, + __in DWORD cch, + __in DWORD dwHash +) +{ + for (DWORD Index = 0; + Index < cch; + ++Index, ++pwsz) + { + dwHash = HASH_MULTIPLY(dwHash) + *pwsz; + } + + return dwHash; +} + + +// Quick-'n'-dirty case-insensitive string hash function. +// Make sure that you follow up with _stricmp or _mbsicmp. You should +// also cache the length of strings and check those first. Caching +// an uppercase version of a string can help too. +// Again, apply HashScramble to the result if using with something other +// than LKRhash. +// Note: this is not really adequate for MBCS strings. + +inline DWORD +HashStringNoCase( + const char* psz, + DWORD dwHash = 0) +{ + const unsigned char* upsz = (const unsigned char*) psz; + + for ( ; *upsz; ++upsz) + dwHash = HASH_MULTIPLY(dwHash) + + (*upsz & 0xDF); // strip off lowercase bit + + return dwHash; +} + +inline DWORD +HashStringNoCase( + __in_ecount(cch) + const char* psz, + SIZE_T cch, + DWORD dwHash) +{ + const unsigned char* upsz = (const unsigned char*) psz; + + for (SIZE_T Index = 0; + Index < cch; + ++Index, ++upsz) + { + dwHash = HASH_MULTIPLY(dwHash) + + (*upsz & 0xDF); // strip off lowercase bit + } + return dwHash; +} + + +// Unicode version of above + +inline DWORD +HashStringNoCase( + const wchar_t* pwsz, + DWORD dwHash = 0) +{ + for ( ; *pwsz; ++pwsz) + dwHash = HASH_MULTIPLY(dwHash) + (*pwsz & 0xFFDF); + + return dwHash; +} + +// Unicode version of above with length + +inline DWORD +HashStringNoCase( + __in_ecount(cch) + const wchar_t* pwsz, + SIZE_T cch, + DWORD dwHash) +{ + for (SIZE_T Index = 0; + Index < cch; + ++Index, ++pwsz) + { + dwHash = HASH_MULTIPLY(dwHash) + (*pwsz & 0xFFDF); + } + return dwHash; +} + + +// HashBlob returns the hash of a blob of arbitrary binary data. +// +// Warning: HashBlob is generally not the right way to hash a class object. +// Consider: +// class CFoo { +// public: +// char m_ch; +// double m_d; +// char* m_psz; +// }; +// +// inline DWORD Hash(const CFoo& rFoo) +// { return HashBlob(&rFoo, sizeof(CFoo)); } +// +// This is the wrong way to hash a CFoo for two reasons: (a) there will be +// a 7-byte gap between m_ch and m_d imposed by the alignment restrictions +// of doubles, which will be filled with random data (usually non-zero for +// stack variables), and (b) it hashes the address (rather than the +// contents) of the string m_psz. Similarly, +// +// bool operator==(const CFoo& rFoo1, const CFoo& rFoo2) +// { return memcmp(&rFoo1, &rFoo2, sizeof(CFoo)) == 0; } +// +// does the wrong thing. Much better to do this: +// +// DWORD Hash(const CFoo& rFoo) +// { +// return HashString(rFoo.m_psz, +// HASH_MULTIPLIER * Hash(rFoo.m_ch) +// + Hash(rFoo.m_d)); +// } +// +// Again, apply HashScramble if using with something other than LKRhash. + +inline DWORD +HashBlob( + const void* pv, + size_t cb, + DWORD dwHash = 0) +{ + const BYTE * pb = static_cast(pv); + + while (cb-- > 0) + dwHash = HASH_MULTIPLY(dwHash) + *pb++; + + return dwHash; +} + + + +// +// Overloaded hash functions for all the major builtin types. +// Again, apply HashScramble to result if using with something other than +// LKRhash. +// + +inline DWORD Hash(const char* psz) +{ return HashString(psz); } + +inline DWORD Hash(const unsigned char* pusz) +{ return HashString(reinterpret_cast(pusz)); } + +inline DWORD Hash(const signed char* pssz) +{ return HashString(reinterpret_cast(pssz)); } + +inline DWORD Hash(const wchar_t* pwsz) +{ return HashString(pwsz); } + +inline DWORD +Hash( + const GUID* pguid, + DWORD dwHash = 0) +{ + + return * reinterpret_cast(const_cast(pguid)) + dwHash; +} + +// Identity hash functions: scalar values map to themselves +inline DWORD Hash(char c) +{ return c; } + +inline DWORD Hash(unsigned char uc) +{ return uc; } + +inline DWORD Hash(signed char sc) +{ return sc; } + +inline DWORD Hash(short sh) +{ return sh; } + +inline DWORD Hash(unsigned short ush) +{ return ush; } + +inline DWORD Hash(int i) +{ return i; } + +inline DWORD Hash(unsigned int u) +{ return u; } + +inline DWORD Hash(long l) +{ return l; } + +inline DWORD Hash(unsigned long ul) +{ return ul; } + +inline DWORD Hash(float f) +{ + // be careful of rounding errors when computing keys + union { + float f; + DWORD dw; + } u; + u.f = f; + return u.dw; +} + +inline DWORD Hash(double dbl) +{ + // be careful of rounding errors when computing keys + union { + double dbl; + DWORD dw[2]; + } u; + u.dbl = dbl; + return u.dw[0] * HASH_MULTIPLIER + u.dw[1]; +} + +#endif // __HASHFN_H__ diff --git a/src/AspNetCoreModuleV2/IISLib/hashtable.h b/src/AspNetCoreModuleV2/IISLib/hashtable.h new file mode 100644 index 0000000000..9319e5643d --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/hashtable.h @@ -0,0 +1,666 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "rwlock.h" +#include "prime.h" + +template +class HASH_NODE +{ + template + friend class HASH_TABLE; + + HASH_NODE( + _Record * pRecord, + DWORD dwHash + ) : _pNext (NULL), + _pRecord (pRecord), + _dwHash (dwHash) + {} + + ~HASH_NODE() + { + _ASSERTE(_pRecord == NULL); + } + + private: + // Next node in the hash table look-aside + HASH_NODE<_Record> *_pNext; + + // actual record + _Record * _pRecord; + + // hash value + DWORD _dwHash; +}; + +template +class HASH_TABLE +{ +protected: + typedef BOOL + (PFN_DELETE_IF)( + _Record * pRecord, + PVOID pvContext + ); + + typedef VOID + (PFN_APPLY)( + _Record * pRecord, + PVOID pvContext + ); + +public: + HASH_TABLE( + VOID + ) + : _ppBuckets( NULL ), + _nBuckets( 0 ), + _nItems( 0 ) + { + } + + virtual + ~HASH_TABLE(); + + virtual + VOID + ReferenceRecord( + _Record * pRecord + ) = 0; + + virtual + VOID + DereferenceRecord( + _Record * pRecord + ) = 0; + + virtual + _Key + ExtractKey( + _Record * pRecord + ) = 0; + + virtual + DWORD + CalcKeyHash( + _Key key + ) = 0; + + virtual + BOOL + EqualKeys( + _Key key1, + _Key key2 + ) = 0; + + DWORD + Count( + VOID + ) const; + + bool + IsInitialized( + VOID + ) const; + + virtual + VOID + Clear(); + + HRESULT + Initialize( + DWORD nBucketSize + ); + + virtual + VOID + FindKey( + _Key key, + _Record ** ppRecord + ); + + virtual + HRESULT + InsertRecord( + _Record * pRecord + ); + + virtual + VOID + DeleteKey( + _Key key + ); + + virtual + VOID + DeleteIf( + PFN_DELETE_IF pfnDeleteIf, + PVOID pvContext + ); + + VOID + Apply( + PFN_APPLY pfnApply, + PVOID pvContext + ); + +private: + + __success(*ppNode != NULL && return != FALSE) + BOOL + FindNodeInternal( + _Key key, + DWORD dwHash, + __deref_out + HASH_NODE<_Record> ** ppNode, + __deref_opt_out + HASH_NODE<_Record> *** pppPreviousNodeNextPointer = NULL + ); + + VOID + DeleteNode( + HASH_NODE<_Record> * pNode + ) + { + if (pNode->_pRecord != NULL) + { + DereferenceRecord(pNode->_pRecord); + pNode->_pRecord = NULL; + } + + delete pNode; + } + + VOID + RehashTableIfNeeded( + VOID + ); + + HASH_NODE<_Record> ** _ppBuckets; + DWORD _nBuckets; + DWORD _nItems; + // + // Allow to use lock object in const methods. + // + mutable + CWSDRWLock _tableLock; +}; + +template +HRESULT +HASH_TABLE<_Record,_Key>::Initialize( + DWORD nBuckets +) +{ + HRESULT hr = S_OK; + + if ( nBuckets == 0 ) + { + hr = E_INVALIDARG; + goto Failed; + } + + if (nBuckets >= MAXDWORD/sizeof(HASH_NODE<_Record> *)) + { + hr = E_INVALIDARG; + goto Failed; + } + + _ASSERTE(_ppBuckets == NULL ); + if ( _ppBuckets != NULL ) + { + hr = E_INVALIDARG; + goto Failed; + } + + hr = _tableLock.Init(); + if ( FAILED( hr ) ) + { + goto Failed; + } + + _ppBuckets = (HASH_NODE<_Record> **)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + nBuckets*sizeof(HASH_NODE<_Record> *)); + if (_ppBuckets == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Failed; + } + _nBuckets = nBuckets; + + return S_OK; + +Failed: + + if (_ppBuckets) + { + HeapFree(GetProcessHeap(), + 0, + _ppBuckets); + _ppBuckets = NULL; + } + + return hr; +} + + +template +HASH_TABLE<_Record,_Key>::~HASH_TABLE() +{ + if (_ppBuckets == NULL) + { + return; + } + + _ASSERTE(_nItems == 0); + + HeapFree(GetProcessHeap(), + 0, + _ppBuckets); + _ppBuckets = NULL; + _nBuckets = 0; +} + +template< class _Record, class _Key> +DWORD +HASH_TABLE<_Record,_Key>::Count() const +{ + return _nItems; +} + +template< class _Record, class _Key> +bool +HASH_TABLE<_Record,_Key>::IsInitialized( + VOID +) const +{ + return _ppBuckets != NULL; +} + + +template +VOID +HASH_TABLE<_Record,_Key>::Clear() +{ + HASH_NODE<_Record> *pCurrent; + HASH_NODE<_Record> *pNext; + + // This is here in the off cases where someone instantiates a hashtable + // and then does an automatic "clear" before its destruction WITHOUT + // ever initializing it. + if ( ! _tableLock.QueryInited() ) + { + return; + } + + _tableLock.ExclusiveAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + pCurrent = _ppBuckets[i]; + _ppBuckets[i] = NULL; + while (pCurrent != NULL) + { + pNext = pCurrent->_pNext; + DeleteNode(pCurrent); + pCurrent = pNext; + } + } + + _nItems = 0; + _tableLock.ExclusiveRelease(); +} + +template +__success(*ppNode != NULL && return != FALSE) +BOOL +HASH_TABLE<_Record,_Key>::FindNodeInternal( + _Key key, + DWORD dwHash, + __deref_out + HASH_NODE<_Record> ** ppNode, + __deref_opt_out + HASH_NODE<_Record> *** pppPreviousNodeNextPointer +) +/*++ + Return value indicates whether the item is found + key, dwHash - key and hash for the node to find + ppNode - on successful return, the node found, on failed return, the first + node with hash value greater than the node to be found + pppPreviousNodeNextPointer - the pointer to previous node's _pNext + + This routine may be called under either read or write lock +--*/ +{ + HASH_NODE<_Record> **ppPreviousNodeNextPointer; + HASH_NODE<_Record> *pNode; + BOOL fFound = FALSE; + + ppPreviousNodeNextPointer = _ppBuckets + (dwHash % _nBuckets); + pNode = *ppPreviousNodeNextPointer; + while (pNode != NULL) + { + if (pNode->_dwHash == dwHash) + { + if (EqualKeys(key, + ExtractKey(pNode->_pRecord))) + { + fFound = TRUE; + break; + } + } + else if (pNode->_dwHash > dwHash) + { + break; + } + + ppPreviousNodeNextPointer = &(pNode->_pNext); + pNode = *ppPreviousNodeNextPointer; + } + + __analysis_assume( (pNode == NULL && fFound == FALSE) || + (pNode != NULL && fFound == TRUE ) ); + *ppNode = pNode; + if (pppPreviousNodeNextPointer != NULL) + { + *pppPreviousNodeNextPointer = ppPreviousNodeNextPointer; + } + return fFound; +} + +template +VOID +HASH_TABLE<_Record,_Key>::FindKey( + _Key key, + _Record ** ppRecord +) +{ + HASH_NODE<_Record> *pNode; + + *ppRecord = NULL; + + DWORD dwHash = CalcKeyHash(key); + + _tableLock.SharedAcquire(); + + if (FindNodeInternal(key, dwHash, &pNode) && + pNode->_pRecord != NULL) + { + ReferenceRecord(pNode->_pRecord); + *ppRecord = pNode->_pRecord; + } + + _tableLock.SharedRelease(); +} + +template +HRESULT +HASH_TABLE<_Record,_Key>::InsertRecord( + _Record * pRecord +) +/*++ + This method inserts a node for this record and also empty nodes for paths + in the heirarchy leading upto this path + + The insert is done under only a read-lock - this is possible by keeping + the hashes in a bucket in increasing order and using interlocked operations + to actually insert the item in the hash-bucket lookaside list and the parent + children list + + Returns HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) if the record already exists. + Never leak this error to the end user because "*file* already exists" may be confusing. +--*/ +{ + BOOL fLocked = FALSE; + _Key key = ExtractKey(pRecord); + DWORD dwHash = CalcKeyHash(key); + HRESULT hr = S_OK; + HASH_NODE<_Record> * pNewNode; + HASH_NODE<_Record> * pNextNode; + HASH_NODE<_Record> ** ppPreviousNodeNextPointer; + + // + // Ownership of pRecord is not transferred to pNewNode yet, so remember + // to either set it to null before deleting pNewNode or add an extra + // reference later - this is to make sure we do not do an extra ref/deref + // which users may view as getting flushed out of the hash-table + // + pNewNode = new HASH_NODE<_Record>(pRecord, dwHash); + if (pNewNode == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Finished; + } + + _tableLock.SharedAcquire(); + fLocked = TRUE; + + do + { + // + // Find the right place to add this node + // + if (FindNodeInternal(key, dwHash, &pNextNode, &ppPreviousNodeNextPointer)) + { + // + // If node already there, return error + // + pNewNode->_pRecord = NULL; + DeleteNode(pNewNode); + + // + // We should never leak this error to the end user + // because "file already exists" may be confusing. + // + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + goto Finished; + } + + // + // If another node got inserted in between, we will have to retry + // + pNewNode->_pNext = pNextNode; + } while (InterlockedCompareExchangePointer((PVOID *)ppPreviousNodeNextPointer, + pNewNode, + pNextNode) != pNextNode); + // pass ownership of pRecord now + if (pRecord != NULL) + { + ReferenceRecord(pRecord); + pRecord = NULL; + } + InterlockedIncrement((LONG *)&_nItems); + +Finished: + + if (fLocked) + { + _tableLock.SharedRelease(); + } + + if (SUCCEEDED(hr)) + { + RehashTableIfNeeded(); + } + + return hr; +} + +template +VOID +HASH_TABLE<_Record,_Key>::DeleteKey( + _Key key +) +{ + HASH_NODE<_Record> *pNode; + HASH_NODE<_Record> **ppPreviousNodeNextPointer; + + DWORD dwHash = CalcKeyHash(key); + + _tableLock.ExclusiveAcquire(); + + if (FindNodeInternal(key, dwHash, &pNode, &ppPreviousNodeNextPointer)) + { + *ppPreviousNodeNextPointer = pNode->_pNext; + DeleteNode(pNode); + _nItems--; + } + + _tableLock.ExclusiveRelease(); +} + +template +VOID +HASH_TABLE<_Record,_Key>::DeleteIf( + PFN_DELETE_IF pfnDeleteIf, + PVOID pvContext +) +{ + HASH_NODE<_Record> *pNode; + HASH_NODE<_Record> **ppPreviousNodeNextPointer; + + _tableLock.ExclusiveAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + ppPreviousNodeNextPointer = _ppBuckets + i; + pNode = *ppPreviousNodeNextPointer; + while (pNode != NULL) + { + // + // Non empty nodes deleted based on DeleteIf, empty nodes deleted + // if they have no children + // + if (pfnDeleteIf(pNode->_pRecord, pvContext)) + { + *ppPreviousNodeNextPointer = pNode->_pNext; + DeleteNode(pNode); + _nItems--; + } + else + { + ppPreviousNodeNextPointer = &pNode->_pNext; + } + + pNode = *ppPreviousNodeNextPointer; + } + } + + _tableLock.ExclusiveRelease(); +} + +template +VOID +HASH_TABLE<_Record,_Key>::Apply( + PFN_APPLY pfnApply, + PVOID pvContext +) +{ + HASH_NODE<_Record> *pNode; + + _tableLock.SharedAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + pNode = _ppBuckets[i]; + while (pNode != NULL) + { + if (pNode->_pRecord != NULL) + { + pfnApply(pNode->_pRecord, pvContext); + } + + pNode = pNode->_pNext; + } + } + + _tableLock.SharedRelease(); +} + +template +VOID +HASH_TABLE<_Record,_Key>::RehashTableIfNeeded( + VOID +) +{ + HASH_NODE<_Record> **ppBuckets; + DWORD nBuckets; + HASH_NODE<_Record> *pNode; + HASH_NODE<_Record> *pNextNode; + HASH_NODE<_Record> **ppNextPointer; + HASH_NODE<_Record> *pNewNextNode; + DWORD nNewBuckets; + + // + // If number of items has become too many, we will double the hash table + // size (we never reduce it however) + // + if (_nItems <= PRIME::GetPrime(2*_nBuckets)) + { + return; + } + + _tableLock.ExclusiveAcquire(); + + nNewBuckets = PRIME::GetPrime(2*_nBuckets); + + if (_nItems <= nNewBuckets) + { + goto Finished; + } + + nBuckets = nNewBuckets; + if (nBuckets >= 0xffffffff/sizeof(HASH_NODE<_Record> *)) + { + goto Finished; + } + ppBuckets = (HASH_NODE<_Record> **)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + nBuckets*sizeof(HASH_NODE<_Record> *)); + if (ppBuckets == NULL) + { + goto Finished; + } + + // + // Take out nodes from the old hash table and insert in the new one, make + // sure to keep the hashes in increasing order + // + for (DWORD i=0; i<_nBuckets; i++) + { + pNode = _ppBuckets[i]; + while (pNode != NULL) + { + pNextNode = pNode->_pNext; + + ppNextPointer = ppBuckets + (pNode->_dwHash % nBuckets); + pNewNextNode = *ppNextPointer; + while (pNewNextNode != NULL && + pNewNextNode->_dwHash <= pNode->_dwHash) + { + ppNextPointer = &pNewNextNode->_pNext; + pNewNextNode = pNewNextNode->_pNext; + } + pNode->_pNext = pNewNextNode; + *ppNextPointer = pNode; + + pNode = pNextNode; + } + } + + HeapFree(GetProcessHeap(), 0, _ppBuckets); + _ppBuckets = ppBuckets; + _nBuckets = nBuckets; + ppBuckets = NULL; + +Finished: + + _tableLock.ExclusiveRelease(); +} diff --git a/src/AspNetCoreModuleV2/IISLib/listentry.h b/src/AspNetCoreModuleV2/IISLib/listentry.h new file mode 100644 index 0000000000..80b70e97a9 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/listentry.h @@ -0,0 +1,163 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#ifndef _LIST_ENTRY_H +#define _LIST_ENTRY_H + +// +// Doubly-linked list manipulation routines. +// + + +#define InitializeListHead32(ListHead) (\ + (ListHead)->Flink = (ListHead)->Blink = PtrToUlong((ListHead))) + + +FORCEINLINE +VOID +InitializeListHead( + IN PLIST_ENTRY ListHead + ) +{ + ListHead->Flink = ListHead->Blink = ListHead; +} + +FORCEINLINE +BOOLEAN +IsListEmpty( + IN const LIST_ENTRY * ListHead + ) +{ + return (BOOLEAN)(ListHead->Flink == ListHead); +} + +FORCEINLINE +BOOLEAN +RemoveEntryList( + IN PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Blink; + PLIST_ENTRY Flink; + + Flink = Entry->Flink; + Blink = Entry->Blink; + Blink->Flink = Flink; + Flink->Blink = Blink; + return (BOOLEAN)(Flink == Blink); +} + +FORCEINLINE +PLIST_ENTRY +RemoveHeadList( + IN PLIST_ENTRY ListHead + ) +{ + PLIST_ENTRY Flink; + PLIST_ENTRY Entry; + + Entry = ListHead->Flink; + Flink = Entry->Flink; + ListHead->Flink = Flink; + Flink->Blink = ListHead; + return Entry; +} + + + +FORCEINLINE +PLIST_ENTRY +RemoveTailList( + IN PLIST_ENTRY ListHead + ) +{ + PLIST_ENTRY Blink; + PLIST_ENTRY Entry; + + Entry = ListHead->Blink; + Blink = Entry->Blink; + ListHead->Blink = Blink; + Blink->Flink = ListHead; + return Entry; +} + + +FORCEINLINE +VOID +InsertTailList( + IN PLIST_ENTRY ListHead, + IN PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Blink; + + Blink = ListHead->Blink; + Entry->Flink = ListHead; + Entry->Blink = Blink; + Blink->Flink = Entry; + ListHead->Blink = Entry; +} + + +FORCEINLINE +VOID +InsertHeadList( + IN PLIST_ENTRY ListHead, + IN PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Flink; + + Flink = ListHead->Flink; + Entry->Flink = Flink; + Entry->Blink = ListHead; + Flink->Blink = Entry; + ListHead->Flink = Entry; +} + +FORCEINLINE +VOID +AppendTailList( + IN PLIST_ENTRY ListHead, + IN PLIST_ENTRY ListToAppend + ) +{ + PLIST_ENTRY ListEnd = ListHead->Blink; + + ListHead->Blink->Flink = ListToAppend; + ListHead->Blink = ListToAppend->Blink; + ListToAppend->Blink->Flink = ListHead; + ListToAppend->Blink = ListEnd; +} + +FORCEINLINE +PSINGLE_LIST_ENTRY +PopEntryList( + PSINGLE_LIST_ENTRY ListHead + ) +{ + PSINGLE_LIST_ENTRY FirstEntry; + FirstEntry = ListHead->Next; + if (FirstEntry != NULL) { + ListHead->Next = FirstEntry->Next; + } + + return FirstEntry; +} + + +FORCEINLINE +VOID +PushEntryList( + PSINGLE_LIST_ENTRY ListHead, + PSINGLE_LIST_ENTRY Entry + ) +{ + Entry->Next = ListHead->Next; + ListHead->Next = Entry; +} + + +#endif diff --git a/src/AspNetCoreModuleV2/IISLib/macros.h b/src/AspNetCoreModuleV2/IISLib/macros.h new file mode 100644 index 0000000000..960f663a98 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/macros.h @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _MACROS_H +#define _MACROS_H + +// +// The DIFF macro should be used around an expression involving pointer +// subtraction. The expression passed to DIFF is cast to a size_t type, +// allowing the result to be easily assigned to any 32-bit variable or +// passed to a function expecting a 32-bit argument. +// + +#define DIFF(x) ((size_t)(x)) + +// Change a hexadecimal digit to its numerical equivalent +#define TOHEX( ch ) \ + ((ch) > L'9' ? \ + (ch) >= L'a' ? \ + (ch) - L'a' + 10 : \ + (ch) - L'A' + 10 \ + : (ch) - L'0') + + +// Change a number to its Hexadecimal equivalent + +#define TODIGIT( nDigit ) \ + (CHAR)((nDigit) > 9 ? \ + (nDigit) - 10 + 'A' \ + : (nDigit) + '0') + + +inline int +SAFEIsSpace(UCHAR c) +{ + return isspace( c ); +} + +inline int +SAFEIsAlNum(UCHAR c) +{ + return isalnum( c ); +} + +inline int +SAFEIsAlpha(UCHAR c) +{ + return isalpha( c ); +} + +inline int +SAFEIsXDigit(UCHAR c) +{ + return isxdigit( c ); +} + +inline int +SAFEIsDigit(UCHAR c) +{ + return isdigit( c ); +} + +#endif // _MACROS_H diff --git a/src/AspNetCoreModuleV2/IISLib/multisz.cpp b/src/AspNetCoreModuleV2/IISLib/multisz.cpp new file mode 100644 index 0000000000..775ec4cd0c --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/multisz.cpp @@ -0,0 +1,474 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + + +#pragma warning (disable : 4267) + +#include "precomp.h" +#include "multisz.h" +#include + +// +// Private Definitions +// + +#define MAXULONG 4294967295 +#define ISWHITE( ch ) ((ch) == L' ' || (ch) == L'\t' || (ch) == L'\r') + +// +// When appending data, this is the extra amount we request to avoid +// reallocations +// +#define STR_SLOP 128 + + +DWORD +MULTISZ::CalcLength( const WCHAR * str, + LPDWORD pcStrings ) +{ + DWORD count = 0; + DWORD total = 1; + DWORD len; + + while( *str ) { + len = ::wcslen( str ) + 1; + total += len; + str += len; + count++; + } + + if( pcStrings != NULL ) { + *pcStrings = count; + } + + return total; + +} // MULTISZ::CalcLength + + +BOOL +MULTISZ::FindString( const WCHAR * str ) +{ + + WCHAR * multisz; + + // + // Sanity check. + // + + DBG_ASSERT( QueryStr() != NULL ); + DBG_ASSERT( str != NULL ); + DBG_ASSERT( *str != '\0' ); + + // + // Scan it. + // + + multisz = QueryStr(); + + while( *multisz != '\0' ) { + + if( !::wcscmp( multisz, str ) ) { + + return TRUE; + + } + + multisz += ::wcslen( multisz ) + 1; + + } + + return FALSE; + +} // MULTISZ::FindString + + +BOOL +MULTISZ::FindStringNoCase( const WCHAR * str ) +{ + + WCHAR * multisz; + + // + // Sanity check. + // + + DBG_ASSERT( QueryStr() != NULL ); + DBG_ASSERT( str != NULL ); + DBG_ASSERT( *str != '\0' ); + + // + // Scan it. + // + + multisz = QueryStr(); + + while( *multisz != '\0' ) { + + if( !_wcsicmp( multisz, str ) ) { + + return TRUE; + + } + + multisz += wcslen( multisz ) + 1; + + } + + return FALSE; + +} // MULTISZ::FindStringNoCase + + +VOID +MULTISZ::AuxInit( const WCHAR * pInit ) +{ + BOOL fRet; + + if ( pInit ) + { + DWORD cStrings; + int cbCopy = CalcLength( pInit, &cStrings ) * sizeof(WCHAR); + fRet = Resize( cbCopy ); + + if ( fRet ) { + CopyMemory( QueryPtr(), pInit, cbCopy ); + m_cchLen = (cbCopy)/sizeof(WCHAR); + m_cStrings = cStrings; + } else { +// BUFFER::SetValid( FALSE); + } + + } else { + + Reset(); + + } + +} // MULTISZ::AuxInit() + + +/******************************************************************* + + NAME: MULTISZ::AuxAppend + + SYNOPSIS: Appends the string onto the multisz. + + ENTRY: Object to append +********************************************************************/ + +BOOL MULTISZ::AuxAppend( const WCHAR * pStr, UINT cbStr, BOOL fAddSlop ) +{ + DBG_ASSERT( pStr != NULL ); + + UINT cbThis = QueryCB(); + + DBG_ASSERT( cbThis >= 2 ); + + if( cbThis == 4 ) { + + // + // It's empty, so start at the beginning. + // + + cbThis = 0; + + } else { + + // + // It's not empty, so back up over the final terminating NULL. + // + + cbThis -= sizeof(WCHAR); + + } + + // + // Only resize when we have to. When we do resize, we tack on + // some extra space to avoid extra reallocations. + // + // Note: QuerySize returns the requested size of the string buffer, + // *not* the strlen of the buffer + // + + //AcIncrement( CacMultiszAppend); + + // + // Check for the arithmetic overflow + // + // ( 2 * sizeof( WCHAR ) ) is for the double terminator + // + ULONGLONG cb64Required = (ULONGLONG)cbThis + cbStr + 2 * sizeof(WCHAR); + if ( cb64Required > MAXULONG ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return FALSE; + } + if ( QuerySize() < (DWORD) cb64Required ) + { + ULONGLONG cb64AllocSize = cb64Required + (fAddSlop ? STR_SLOP : 0 ); + // + // Check for the arithmetic overflow + // + if ( cb64AllocSize > MAXULONG ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return FALSE; + } + if ( !Resize( (DWORD) cb64AllocSize ) ) + return FALSE; + } + + // copy the exact string and tack on the double terminator + memcpy( (BYTE *) QueryPtr() + cbThis, + pStr, + cbStr); + *(WCHAR *)((BYTE *)QueryPtr() + cbThis + cbStr) = L'\0'; + *(WCHAR *)((BYTE *)QueryPtr() + cbThis + cbStr + sizeof(WCHAR) ) = L'\0'; + + m_cchLen = CalcLength( (const WCHAR *)QueryPtr(), &m_cStrings ); + return TRUE; + +} // MULTISZ::AuxAppend() + + +#if 0 + +BOOL +MULTISZ::CopyToBuffer( WCHAR * lpszBuffer, LPDWORD lpcch) const +/*++ + Description: + Copies the string into the WCHAR buffer passed in if the buffer + is sufficient to hold the translated string. + If the buffer is small, the function returns small and sets *lpcch + to contain the required number of characters. + + Arguments: + lpszBuffer pointer to WCHAR buffer which on return contains + the UNICODE version of string on success. + lpcch pointer to DWORD containing the length of the buffer. + If *lpcch == 0 then the function returns TRUE with + the count of characters required stored in *lpcch. + Also in this case lpszBuffer is not affected. + Returns: + TRUE on success. + FALSE on failure. Use GetLastError() for further details. +--*/ +{ + BOOL fReturn = TRUE; + + if ( lpcch == NULL) { + SetLastError( ERROR_INVALID_PARAMETER); + return ( FALSE); + } + + if ( *lpcch == 0) { + + // + // Inquiring the size of buffer alone + // + *lpcch = QueryCCH() + 1; // add one character for terminating null + } else { + + // + // Copy after conversion from ANSI to Unicode + // + int iRet; + iRet = MultiByteToWideChar( CP_ACP, + MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, + QueryStrA(), QueryCCH() + 1, + lpszBuffer, (int )*lpcch); + + if ( iRet == 0 || iRet != (int ) *lpcch) { + + // + // Error in conversion. + // + fReturn = FALSE; + } + } + + return ( fReturn); +} // MULTISZ::CopyToBuffer() +#endif + +BOOL +MULTISZ::CopyToBuffer( __out_ecount_opt(*lpcch) WCHAR * lpszBuffer, LPDWORD lpcch) const +/*++ + Description: + Copies the string into the WCHAR buffer passed in if the buffer + is sufficient to hold the translated string. + If the buffer is small, the function returns small and sets *lpcch + to contain the required number of characters. + + Arguments: + lpszBuffer pointer to WCHAR buffer which on return contains + the string on success. + lpcch pointer to DWORD containing the length of the buffer. + If *lpcch == 0 then the function returns TRUE with + the count of characters required stored in lpcch. + Also in this case lpszBuffer is not affected. + Returns: + TRUE on success. + FALSE on failure. Use GetLastError() for further details. +--*/ +{ + BOOL fReturn = TRUE; + + if ( lpcch == NULL) { + SetLastError( ERROR_INVALID_PARAMETER); + return ( FALSE); + } + + register DWORD cch = QueryCCH(); + + if ( *lpcch >= cch) { + + DBG_ASSERT( lpszBuffer); + memcpy( lpszBuffer, QueryStr(), cch * sizeof(WCHAR)); + } else { + DBG_ASSERT( *lpcch < cch); + SetLastError( ERROR_INSUFFICIENT_BUFFER); + fReturn = FALSE; + } + + *lpcch = cch; + + return ( fReturn); +} // MULTISZ::CopyToBuffer() + +BOOL +MULTISZ::Equals( + MULTISZ* pmszRhs +) +// +// Compares this to pmszRhs, returns TRUE if equal +// +{ + DBG_ASSERT( NULL != pmszRhs ); + + PCWSTR pszLhs = First( ); + PCWSTR pszRhs = pmszRhs->First( ); + + if( m_cStrings != pmszRhs->m_cStrings ) + { + return FALSE; + } + + while( NULL != pszLhs ) + { + DBG_ASSERT( NULL != pszRhs ); + + if( 0 != wcscmp( pszLhs, pszRhs ) ) + { + return FALSE; + } + + pszLhs = Next( pszLhs ); + pszRhs = pmszRhs->Next( pszRhs ); + } + + return TRUE; +} + +HRESULT +SplitCommaDelimitedString( + PCWSTR pszList, + BOOL fTrimEntries, + BOOL fRemoveEmptyEntries, + MULTISZ * pmszList +) +/*++ + +Routine Description: + + Split comma delimited string into a multisz. Additional leading empty + entries after the first are discarded. + +Arguments: + + pszList - List to split up + fTrimEntries - Whether each entry should be trimmed before added to multisz + fRemoveEmptyEntries - Whether empty entires should be discarded + pmszList - Filled with MULTISZ list + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + + if ( pszList == NULL || + pmszList == NULL ) + { + DBG_ASSERT( FALSE ); + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + pmszList->Reset(); + + /* + pszCurrent: start of the current entry which may be the comma that + precedes the next entry if the entry is empty + + pszNext: the comma that precedes the next entry. If + pszCurrent == pszNext, then the entry is empty + + pszEnd: just past the end of the current entry + */ + + for ( PCWSTR pszCurrent = pszList, + pszNext = wcschr( pszCurrent, L',' ) + ; + ; + pszCurrent = pszNext + 1, + pszNext = wcschr( pszCurrent, L',' ) ) + { + PCWSTR pszEnd = NULL; + + if ( pszNext != NULL ) + { + pszEnd = pszNext; + } + else + { + pszEnd = pszCurrent + wcslen( pszCurrent ); + } + + if ( fTrimEntries ) + { + while ( pszCurrent < pszEnd && ISWHITE( pszCurrent[ 0 ] ) ) + { + pszCurrent++; + } + + while ( pszEnd > pszCurrent && ISWHITE( pszEnd[ -1 ] ) ) + { + pszEnd--; + } + } + + if ( pszCurrent != pszEnd || !fRemoveEmptyEntries ) + { + if ( !pmszList->Append( pszCurrent, (DWORD) ( pszEnd - pszCurrent ) ) ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + } + + if ( pszNext == NULL ) + { + break; + } + } + +Finished: + + return hr; +} + +#pragma warning(default:4267) \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/IISLib/multisz.h b/src/AspNetCoreModuleV2/IISLib/multisz.h new file mode 100644 index 0000000000..f65c151d4f --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/multisz.h @@ -0,0 +1,225 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _MULTISZ_H_ +#define _MULTISZ_H_ + +#include "stringu.h" +#include "ntassert.h" + +/*++ + class MULTISZ: + + Intention: + A light-weight multi-string class supporting encapsulated string class. + + This object is derived from BUFFER class. + It maintains following state: + + m_fValid - whether this object is valid - + used only by MULTISZ() init functions + * NYI: I need to kill this someday * + m_cchLen - string length cached when we update the string. + m_cStrings - number of strings. + + Member Functions: + There are two categories of functions: + 1) Safe Functions - which do integrity checking of state + 2) UnSafe Functions - which do not do integrity checking, but + enable writing to the data stream freely. + (someday this will be enabled as Safe versions without + problem for users) + +--*/ +class MULTISZ : public BUFFER +{ +public: + + MULTISZ() + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { Reset(); } + + // creates a stack version of the MULTISZ object - uses passed in stack buffer + // MULTISZ does not free this pbInit on its own. + MULTISZ( __in_bcount(cbInit) WCHAR * pbInit, DWORD cbInit) + : BUFFER( (BYTE *) pbInit, cbInit), + m_cchLen (0), + m_cStrings(0) + {} + + MULTISZ( const WCHAR * pchInit ) + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { AuxInit(pchInit); } + + MULTISZ( const MULTISZ & str ) + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { AuxInit( str.QueryStr()); } + +// BOOL IsValid(VOID) const { return ( BUFFER::IsValid()) ; } + // + // Checks and returns TRUE if this string has no valid data else FALSE + // + BOOL IsEmpty( VOID) const { return ( *QueryStr() == L'\0'); } + + BOOL Append( const WCHAR * pchInit ) { + return ((pchInit != NULL) ? (AuxAppend( pchInit, + (DWORD) (::wcslen(pchInit)) * sizeof(WCHAR) + )) : + TRUE); + } + + + BOOL Append( const WCHAR * pchInit, DWORD cchLen ) { + return ((pchInit != NULL) ? (AuxAppend( pchInit, + cchLen * sizeof(WCHAR))) : + TRUE); + } + + BOOL Append( STRU & str ) + { return AuxAppend( str.QueryStr(), + (str.QueryCCH()) * sizeof(WCHAR)); } + + // Resets the internal string to be NULL string. Buffer remains cached. + VOID Reset( VOID) + { DBG_ASSERT( QueryPtr() != NULL); + QueryStr()[0] = L'\0'; + QueryStr()[1] = L'\0'; + m_cchLen = 2; + m_cStrings = 0; + } + + BOOL Copy( const WCHAR * pchInit, IN DWORD cbLen ) { + if ( QueryPtr() ) { Reset(); } + return ( (pchInit != NULL) ? + AuxAppend( pchInit, cbLen, FALSE ): + TRUE); + } + + BOOL Copy( const MULTISZ & str ) + { return ( Copy(str.QueryStr(), str.QueryCB())); } + + // + // Returns the number of bytes in the string including the terminating + // NULLs + // + UINT QueryCB( VOID ) const + { return ( m_cchLen * sizeof(WCHAR)); } + + // + // Returns # of characters in the string including the terminating NULLs + // + UINT QueryCCH( VOID ) const { return (m_cchLen); } + + // + // Returns # of strings in the multisz. + // + + DWORD QueryStringCount( VOID ) const { return m_cStrings; } + + // + // Makes a copy of the stored string in given buffer + // + BOOL CopyToBuffer( __out_ecount_opt(*lpcch) WCHAR * lpszBuffer, LPDWORD lpcch) const; + + // + // Return the string buffer + // + WCHAR * QueryStrA( VOID ) const { return ( QueryStr()); } + WCHAR * QueryStr( VOID ) const { return ((WCHAR *) QueryPtr()); } + + // + // Makes a clone of the current string in the string pointer passed in. + // + BOOL + Clone( OUT MULTISZ * pstrClone) const + { + return ((pstrClone == NULL) ? + (SetLastError(ERROR_INVALID_PARAMETER), FALSE) : + (pstrClone->Copy( *this)) + ); + } // MULTISZ::Clone() + + // + // Recalculates the length of *this because we've modified the buffers + // directly + // + + VOID RecalcLen( VOID ) + { m_cchLen = MULTISZ::CalcLength( QueryStr(), &m_cStrings ); } + + // + // Calculate total character length of a MULTI_SZ, including the + // terminating NULLs. + // + + static DWORD CalcLength( const WCHAR * str, + LPDWORD pcStrings = NULL ); + + // + // Determine if the MULTISZ contains a specific string. + // + + BOOL FindString( const WCHAR * str ); + + BOOL FindString( STRU & str ) + { return FindString( str.QueryStr() ); } + + // + // Determine if the MULTISZ contains a specific string - case-insensitive + // + + BOOL FindStringNoCase( const WCHAR * str ); + + BOOL FindStringNoCase( STRU & str ) + { return FindStringNoCase( str.QueryStr() ); } + + // + // Used for scanning a multisz. + // + + const WCHAR * First( VOID ) const + { return *QueryStr() == L'\0' ? NULL : QueryStr(); } + + const WCHAR * Next( const WCHAR * Current ) const + { Current += ::wcslen( Current ) + 1; + return *Current == L'\0' ? NULL : Current; } + + BOOL + Equals( + MULTISZ* pmszRhs + ); + +private: + + DWORD m_cchLen; + DWORD m_cStrings; + VOID AuxInit( const WCHAR * pInit ); + BOOL AuxAppend( const WCHAR * pInit, + UINT cbStr, BOOL fAddSlop = TRUE ); + +}; + +// +// Quick macro for declaring a MULTISZ that will use stack memory of +// bytes. If the buffer overflows then a heap buffer will be allocated +// + +#define STACK_MULTISZ( name, size ) WCHAR __ach##name[size]; \ + MULTISZ name( __ach##name, sizeof( __ach##name )) + +HRESULT +SplitCommaDelimitedString( + PCWSTR pszList, + BOOL fTrimEntries, + BOOL fRemoveEmptyEntries, + MULTISZ * pmszList +); + +#endif // !_MULTISZ_HXX_ + diff --git a/src/AspNetCoreModuleV2/IISLib/multisza.cpp b/src/AspNetCoreModuleV2/IISLib/multisza.cpp new file mode 100644 index 0000000000..54717edf05 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/multisza.cpp @@ -0,0 +1,408 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma warning (disable : 4267) +#include "precomp.h" +#include "multisza.h" +#include + +// +// Private Definitions +// + +#define MAXULONG 4294967295 +#define ISWHITE( ch ) ((ch) == L' ' || (ch) == L'\t' || (ch) == L'\r') + +// +// When appending data, this is the extra amount we request to avoid +// reallocations +// +#define STR_SLOP 128 + + +DWORD +MULTISZA::CalcLength( const CHAR * str, + LPDWORD pcStrings ) +{ + DWORD count = 0; + DWORD total = 1; + DWORD len; + + while( *str ) { + len = ::strlen( str ) + 1; + total += len; + str += len; + count++; + } + + if( pcStrings != NULL ) { + *pcStrings = count; + } + + return total; + +} // MULTISZA::CalcLength + + +BOOL +MULTISZA::FindString( const CHAR * str ) +{ + + CHAR * multisz; + + // + // Sanity check. + // + + DBG_ASSERT( QueryStr() != NULL ); + DBG_ASSERT( str != NULL ); + DBG_ASSERT( *str != '\0' ); + + // + // Scan it. + // + + multisz = QueryStr(); + + while( *multisz != '\0' ) { + + if( !::strcmp( multisz, str ) ) { + + return TRUE; + + } + + multisz += ::strlen( multisz ) + 1; + + } + + return FALSE; + +} // MULTISZA::FindString + + +BOOL +MULTISZA::FindStringNoCase( const CHAR * str ) +{ + + CHAR * multisz; + + // + // Sanity check. + // + + DBG_ASSERT( QueryStr() != NULL ); + DBG_ASSERT( str != NULL ); + DBG_ASSERT( *str != '\0' ); + + // + // Scan it. + // + + multisz = QueryStr(); + + while( *multisz != '\0' ) { + + if( !_stricmp( multisz, str ) ) { + + return TRUE; + + } + + multisz += strlen( multisz ) + 1; + + } + + return FALSE; + +} // MULTISZA::FindStringNoCase + + +VOID +MULTISZA::AuxInit( const CHAR * pInit ) +{ + BOOL fRet; + + if ( pInit ) + { + DWORD cStrings; + int cbCopy = CalcLength( pInit, &cStrings ) * sizeof(CHAR); + fRet = Resize( cbCopy ); + + if ( fRet ) { + CopyMemory( QueryPtr(), pInit, cbCopy ); + m_cchLen = (cbCopy)/sizeof(CHAR); + m_cStrings = cStrings; + } else { +// BUFFER::SetValid( FALSE); + } + + } else { + + Reset(); + + } + +} // MULTISZA::AuxInit() + + +/******************************************************************* + + NAME: MULTISZA::AuxAppend + + SYNOPSIS: Appends the string onto the MULTISZA. + + ENTRY: Object to append +********************************************************************/ + +BOOL MULTISZA::AuxAppend( const CHAR * pStr, UINT cbStr, BOOL fAddSlop ) +{ + DBG_ASSERT( pStr != NULL ); + + UINT cbThis = QueryCB(); + + if( cbThis == 2 ) { + + // + // It's empty, so start at the beginning. + // + + cbThis = 0; + + } else { + + // + // It's not empty, so back up over the final terminating NULL. + // + + cbThis -= sizeof(CHAR); + + } + + // + // Only resize when we have to. When we do resize, we tack on + // some extra space to avoid extra reallocations. + // + // Note: QuerySize returns the requested size of the string buffer, + // *not* the strlen of the buffer + // + + //AcIncrement( CacMultiszAppend); + + // + // Check for the arithmetic overflow + // + // ( 2 * sizeof( CHAR ) ) is for the double terminator + // + ULONGLONG cb64Required = (ULONGLONG)cbThis + cbStr + 2 * sizeof(CHAR); + if ( cb64Required > MAXULONG ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return FALSE; + } + if ( QuerySize() < (DWORD) cb64Required ) + { + ULONGLONG cb64AllocSize = cb64Required + (fAddSlop ? STR_SLOP : 0 ); + // + // Check for the arithmetic overflow + // + if ( cb64AllocSize > MAXULONG ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return FALSE; + } + if ( !Resize( (DWORD) cb64AllocSize ) ) + return FALSE; + } + + // copy the exact string and tack on the double terminator + memcpy( (BYTE *) QueryPtr() + cbThis, + pStr, + cbStr); + *(CHAR *)((BYTE *)QueryPtr() + cbThis + cbStr) = L'\0'; + *(CHAR *)((BYTE *)QueryPtr() + cbThis + cbStr + sizeof(CHAR) ) = L'\0'; + + m_cchLen = CalcLength( (const CHAR *)QueryPtr(), &m_cStrings ); + return TRUE; + +} // MULTISZA::AuxAppend() + +BOOL +MULTISZA::CopyToBuffer( __out_ecount_opt(*lpcch) CHAR * lpszBuffer, LPDWORD lpcch) const +/*++ + Description: + Copies the string into the CHAR buffer passed in if the buffer + is sufficient to hold the translated string. + If the buffer is small, the function returns small and sets *lpcch + to contain the required number of characters. + + Arguments: + lpszBuffer pointer to CHAR buffer which on return contains + the string on success. + lpcch pointer to DWORD containing the length of the buffer. + If *lpcch == 0 then the function returns TRUE with + the count of characters required stored in lpcch. + Also in this case lpszBuffer is not affected. + Returns: + TRUE on success. + FALSE on failure. Use GetLastError() for further details. +--*/ +{ + BOOL fReturn = TRUE; + + if ( lpcch == NULL) { + SetLastError( ERROR_INVALID_PARAMETER); + return ( FALSE); + } + + register DWORD cch = QueryCCH(); + + if ( *lpcch >= cch) { + + DBG_ASSERT( lpszBuffer); + memcpy( lpszBuffer, QueryStr(), cch * sizeof(CHAR)); + } else { + DBG_ASSERT( *lpcch < cch); + SetLastError( ERROR_INSUFFICIENT_BUFFER); + fReturn = FALSE; + } + + *lpcch = cch; + + return ( fReturn); +} // MULTISZA::CopyToBuffer() + +BOOL +MULTISZA::Equals( + MULTISZA* pmszRhs +) +// +// Compares this to pmszRhs, returns TRUE if equal +// +{ + DBG_ASSERT( NULL != pmszRhs ); + + PCSTR pszLhs = First( ); + PCSTR pszRhs = pmszRhs->First( ); + + if( m_cStrings != pmszRhs->m_cStrings ) + { + return FALSE; + } + + while( NULL != pszLhs ) + { + DBG_ASSERT( NULL != pszRhs ); + + if( 0 != strcmp( pszLhs, pszRhs ) ) + { + return FALSE; + } + + pszLhs = Next( pszLhs ); + pszRhs = pmszRhs->Next( pszRhs ); + } + + return TRUE; +} + +HRESULT +SplitCommaDelimitedString( + PCSTR pszList, + BOOL fTrimEntries, + BOOL fRemoveEmptyEntries, + MULTISZA * pmszList +) +/*++ + +Routine Description: + + Split comma delimited string into a MULTISZA. Additional leading empty + entries after the first are discarded. + +Arguments: + + pszList - List to split up + fTrimEntries - Whether each entry should be trimmed before added to MULTISZA + fRemoveEmptyEntries - Whether empty entires should be discarded + pmszList - Filled with MULTISZA list + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + + if ( pszList == NULL || + pmszList == NULL ) + { + DBG_ASSERT( FALSE ); + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + pmszList->Reset(); + + /* + pszCurrent: start of the current entry which may be the comma that + precedes the next entry if the entry is empty + + pszNext: the comma that precedes the next entry. If + pszCurrent == pszNext, then the entry is empty + + pszEnd: just past the end of the current entry + */ + + for ( PCSTR pszCurrent = pszList, + pszNext = strchr( pszCurrent, L',' ) + ; + ; + pszCurrent = pszNext + 1, + pszNext = strchr( pszCurrent, L',' ) ) + { + PCSTR pszEnd = NULL; + + if ( pszNext != NULL ) + { + pszEnd = pszNext; + } + else + { + pszEnd = pszCurrent + strlen( pszCurrent ); + } + + if ( fTrimEntries ) + { + while ( pszCurrent < pszEnd && ISWHITE( pszCurrent[ 0 ] ) ) + { + pszCurrent++; + } + + while ( pszEnd > pszCurrent && ISWHITE( pszEnd[ -1 ] ) ) + { + pszEnd--; + } + } + + if ( pszCurrent != pszEnd || !fRemoveEmptyEntries ) + { + if ( !pmszList->Append( pszCurrent, (DWORD) ( pszEnd - pszCurrent ) ) ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + } + + if ( pszNext == NULL ) + { + break; + } + } + +Finished: + + return hr; +} +#pragma warning(default:4267) \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/IISLib/multisza.h b/src/AspNetCoreModuleV2/IISLib/multisza.h new file mode 100644 index 0000000000..d575ec9423 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/multisza.h @@ -0,0 +1,226 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _MULTISZA_H_ +#define _MULTISZA_H_ + +#include +#include "stringa.h" + + +/*++ + class MULTISZ: + + Intention: + A light-weight multi-string class supporting encapsulated string class. + + This object is derived from BUFFER class. + It maintains following state: + + m_fValid - whether this object is valid - + used only by MULTISZ() init functions + * NYI: I need to kill this someday * + m_cchLen - string length cached when we update the string. + m_cStrings - number of strings. + + Member Functions: + There are two categories of functions: + 1) Safe Functions - which do integrity checking of state + 2) UnSafe Functions - which do not do integrity checking, but + enable writing to the data stream freely. + (someday this will be enabled as Safe versions without + problem for users) + +--*/ +class MULTISZA : public BUFFER +{ +public: + + MULTISZA() + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { Reset(); } + + // creates a stack version of the MULTISZA object - uses passed in stack buffer + // MULTISZA does not free this pbInit on its own. + MULTISZA( __in_bcount(cbInit) CHAR * pbInit, DWORD cbInit) + : BUFFER( (BYTE *) pbInit, cbInit), + m_cchLen (0), + m_cStrings(0) + {} + + MULTISZA( const CHAR * pchInit ) + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { AuxInit(pchInit); } + + MULTISZA( const MULTISZA & str ) + : BUFFER (), + m_cchLen ( 0), + m_cStrings(0) + { AuxInit( str.QueryStr()); } + +// BOOL IsValid(VOID) const { return ( BUFFER::IsValid()) ; } + // + // Checks and returns TRUE if this string has no valid data else FALSE + // + BOOL IsEmpty( VOID) const { return ( *QueryStr() == L'\0'); } + + BOOL Append( const CHAR * pchInit ) { + return ((pchInit != NULL) ? (AuxAppend( pchInit, + (DWORD) (::strlen(pchInit)) * sizeof(CHAR) + )) : + TRUE); + } + + + BOOL Append( const CHAR * pchInit, DWORD cchLen ) { + return ((pchInit != NULL) ? (AuxAppend( pchInit, + cchLen * sizeof(CHAR))) : + TRUE); + } + + BOOL Append( STRA & str ) + { return AuxAppend( str.QueryStr(), + (str.QueryCCH()) * sizeof(CHAR)); } + + // Resets the internal string to be NULL string. Buffer remains cached. + VOID Reset( VOID) + { DBG_ASSERT( QueryPtr() != NULL); + QueryStr()[0] = L'\0'; + QueryStr()[1] = L'\0'; + m_cchLen = 2; + m_cStrings = 0; + } + + BOOL Copy( const CHAR * pchInit, IN DWORD cbLen ) { + if ( QueryPtr() ) { Reset(); } + return ( (pchInit != NULL) ? + AuxAppend( pchInit, cbLen, FALSE ): + TRUE); + } + + BOOL Copy( const MULTISZA & str ) + { return ( Copy(str.QueryStr(), str.QueryCB())); } + + // + // Returns the number of bytes in the string including the terminating + // NULLs + // + UINT QueryCB( VOID ) const + { return ( m_cchLen * sizeof(CHAR)); } + + // + // Returns # of characters in the string including the terminating NULLs + // + UINT QueryCCH( VOID ) const { return (m_cchLen); } + + // + // Returns # of strings in the MULTISZA. + // + + DWORD QueryStringCount( VOID ) const { return m_cStrings; } + + // + // Makes a copy of the stored string in given buffer + // + BOOL CopyToBuffer( __out_ecount_opt(*lpcch) CHAR * lpszBuffer, LPDWORD lpcch) const; + + // + // Return the string buffer + // + CHAR * QueryStrA( VOID ) const { return ( QueryStr()); } + CHAR * QueryStr( VOID ) const { return ((CHAR *) QueryPtr()); } + + // + // Makes a clone of the current string in the string pointer passed in. + // + BOOL + Clone( OUT MULTISZA * pstrClone) const + { + return ((pstrClone == NULL) ? + (SetLastError(ERROR_INVALID_PARAMETER), FALSE) : + (pstrClone->Copy( *this)) + ); + } // MULTISZA::Clone() + + // + // Recalculates the length of *this because we've modified the buffers + // directly + // + + VOID RecalcLen( VOID ) + { m_cchLen = MULTISZA::CalcLength( QueryStr(), &m_cStrings ); } + + // + // Calculate total character length of a MULTI_SZ, including the + // terminating NULLs. + // + + static DWORD CalcLength( const CHAR * str, + LPDWORD pcStrings = NULL ); + + // + // Determine if the MULTISZA contains a specific string. + // + + BOOL FindString( const CHAR * str ); + + BOOL FindString( STRA & str ) + { return FindString( str.QueryStr() ); } + + // + // Determine if the MULTISZA contains a specific string - case-insensitive + // + + BOOL FindStringNoCase( const CHAR * str ); + + BOOL FindStringNoCase( STRA & str ) + { return FindStringNoCase( str.QueryStr() ); } + + // + // Used for scanning a MULTISZA. + // + + const CHAR * First( VOID ) const + { return *QueryStr() == L'\0' ? NULL : QueryStr(); } + + const CHAR * Next( const CHAR * Current ) const + { Current += ::strlen( Current ) + 1; + return *Current == L'\0' ? NULL : Current; } + + BOOL + Equals( + MULTISZA* pmszRhs + ); + +private: + + DWORD m_cchLen; + DWORD m_cStrings; + VOID AuxInit( const CHAR * pInit ); + BOOL AuxAppend( const CHAR * pInit, + UINT cbStr, BOOL fAddSlop = TRUE ); + +}; + +// +// Quick macro for declaring a MULTISZA that will use stack memory of +// bytes. If the buffer overflows then a heap buffer will be allocated +// + +#define STACK_MULTISZA( name, size ) CHAR __ach##name[size]; \ + MULTISZA name( __ach##name, sizeof( __ach##name )) + +HRESULT +SplitCommaDelimitedString( + PCSTR pszList, + BOOL fTrimEntries, + BOOL fRemoveEmptyEntries, + MULTISZA * pmszList +); + +#endif // !_MULTISZA_HXX_ + diff --git a/src/AspNetCoreModuleV2/IISLib/ntassert.h b/src/AspNetCoreModuleV2/IISLib/ntassert.h new file mode 100644 index 0000000000..6d2f3b9a30 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/ntassert.h @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#ifdef _ASSERTE + #undef _ASSERTE +#endif + +#ifdef ASSERT + #undef ASSERT +#endif + +#if defined( DBG ) && DBG + #define SX_ASSERT( _x ) ( (VOID)( ( ( _x ) ) ? TRUE : ( __annotation( L"Debug", L"AssertFail", L#_x ), DbgRaiseAssertionFailure(), FALSE ) ) ) + #define SX_ASSERTMSG( _m, _x ) ( (VOID)( ( ( _x ) ) ? TRUE : ( __annotation( L"Debug", L"AssertFail", L##_m ), DbgRaiseAssertionFailure(), FALSE ) ) ) + #define SX_VERIFY( _x ) SX_ASSERT( _x ) + #define _ASSERTE( _x ) SX_ASSERT( _x ) + #define ASSERT( _x ) SX_ASSERT( _x ) + #define assert( _x ) SX_ASSERT( _x ) + #define DBG_ASSERT( _x ) SX_ASSERT( _x ) + #define DBG_REQUIRE( _x ) SX_ASSERT( _x ) +#else + #define SX_ASSERT( _x ) ( (VOID)0 ) + #define SX_ASSERTMSG( _m, _x ) ( (VOID)0 ) + #define SX_VERIFY( _x ) ( (VOID)( ( _x ) ? TRUE : FALSE ) ) + #define _ASSERTE( _x ) ( (VOID)0 ) + #define assert( _x ) ( (VOID)0 ) + #define DBG_ASSERT( _x ) ( (VOID)0 ) + #define DBG_REQUIRE( _x ) ((VOID)(_x)) +#endif + diff --git a/src/AspNetCoreModuleV2/IISLib/percpu.h b/src/AspNetCoreModuleV2/IISLib/percpu.h new file mode 100644 index 0000000000..5d3c563935 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/percpu.h @@ -0,0 +1,305 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +template +class PER_CPU +{ +public: + + template + inline + static + HRESULT + Create( + FunctionInitializer Initializer, + __deref_out PER_CPU ** ppInstance + ); + + inline + T * + GetLocal( + VOID + ); + + template + inline + VOID + ForEach( + FunctionForEach Function + ); + + inline + VOID + Dispose( + VOID + ); + +private: + + PER_CPU( + VOID + ) + { + // + // Don't perform any operation during constructor. + // Constructor will never be called. + // + } + + ~PER_CPU( + VOID + ) + { + // + // Don't perform any operation during destructor. + // Constructor will never be called. + // + } + + template + HRESULT + Initialize( + FunctionInitializer Initializer, + DWORD NumberOfVariables, + DWORD Alignment + ); + + T * + GetObject( + DWORD Index + ); + + static + HRESULT + GetProcessorInformation( + __out DWORD * pCacheLineSize, + __out DWORD * pNumberOfProcessors + ); + + // + // Pointer to the begining of the inlined array. + // + PVOID m_pVariables; + SIZE_T m_Alignment; + SIZE_T m_VariablesCount; +}; + +template +template +inline +// static +HRESULT +PER_CPU::Create( + FunctionInitializer Initializer, + __deref_out PER_CPU ** ppInstance +) +{ + HRESULT hr = S_OK; + DWORD CacheLineSize = 0; + DWORD ObjectCacheLineSize = 0; + DWORD NumberOfProcessors = 0; + PER_CPU * pInstance = NULL; + + hr = GetProcessorInformation(&CacheLineSize, + &NumberOfProcessors); + if (FAILED(hr)) + { + goto Finished; + } + + if (sizeof(T) > CacheLineSize) + { + // + // Round to the next multiple of the cache line size. + // + ObjectCacheLineSize = (sizeof(T) + CacheLineSize-1) & (CacheLineSize-1); + } + else + { + ObjectCacheLineSize = CacheLineSize; + } + + // + // Calculate the size of the PER_CPU object, including the array. + // The first cache line is for the member variables and the array + // starts in the next cache line. + // + SIZE_T Size = CacheLineSize + NumberOfProcessors * ObjectCacheLineSize; + + pInstance = (PER_CPU*) _aligned_malloc(Size, CacheLineSize); + if (pInstance == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + ZeroMemory(pInstance, Size); + + // + // The array start in the 2nd cache line. + // + pInstance->m_pVariables = reinterpret_cast(pInstance) + CacheLineSize; + + // + // Pass a disposer for disposing initialized items in case of failure. + // + hr = pInstance->Initialize(Initializer, + NumberOfProcessors, + ObjectCacheLineSize); + if (FAILED(hr)) + { + goto Finished; + } + + *ppInstance = pInstance; + pInstance = NULL; + +Finished: + + if (pInstance != NULL) + { + // + // Free the instance without disposing it. + // + pInstance->Dispose(); + pInstance = NULL; + } + + return hr; +} + +template +inline +T * +PER_CPU::GetLocal( + VOID +) +{ + // Use GetCurrentProcessorNumber (up to 64 logical processors) instead of + // GetCurrentProcessorNumberEx (more than 64 logical processors) because + // the number of processors are not densely packed per group. + // The idea of distributing variables per CPU is to have + // a scalability multiplier (could be NUMA node instead). + // + // Make sure the index don't go beyond the array size, if that happens, + // there won't be even distribution, but still better + // than one single variable. + // + return GetObject(GetCurrentProcessorNumber()); +} + +template +inline +T * +PER_CPU::GetObject( + DWORD Index +) +{ + return reinterpret_cast(static_cast(m_pVariables) + Index * m_Alignment); +} + +template +template +inline +VOID +PER_CPU::ForEach( + FunctionForEach Function +) +{ + for(DWORD Index = 0; Index < m_VariablesCount; ++Index) + { + T * pObject = GetObject(Index); + Function(pObject); + } +} + +template +VOID +PER_CPU::Dispose( + VOID +) +{ + _aligned_free(this); +} + +template +template +inline +HRESULT +PER_CPU::Initialize( + FunctionInitializer Initializer, + DWORD NumberOfVariables, + DWORD Alignment +) +/*++ + +Routine Description: + + Initialize each object using the initializer function. + If initialization for any object fails, it dispose the + objects that were successfully initialized. + +Arguments: + + Initializer - Function for initialize one object. + Signature: HRESULT Func(T*) + Dispose - Function for disposing initialized objects in case of failure. + Signature: void Func(T*) + NumberOfVariables - The length of the array of variables. + Alignment - Alignment to use for avoiding false sharing. + +Return: + + HRESULT - E_OUTOFMEMORY + +--*/ +{ + HRESULT hr = S_OK; + DWORD Index = 0; + + m_VariablesCount = NumberOfVariables; + m_Alignment = Alignment; + + for (; Index < m_VariablesCount; ++Index) + { + T * pObject = GetObject(Index); + Initializer(pObject); + } + + return hr; +} + +template +// static +HRESULT +PER_CPU::GetProcessorInformation( + __out DWORD * pCacheLineSize, + __out DWORD * pNumberOfProcessors +) +/*++ + +Routine Description: + + Gets the CPU cache-line size for the current system. + This information is used for avoiding CPU false sharing. + +Arguments: + + pCacheLineSize - The processor cache-line size. + pNumberOfProcessors - Maximum number of processors per group. + +Return: + + HRESULT - E_OUTOFMEMORY + +--*/ +{ + SYSTEM_INFO SystemInfo = { }; + + GetSystemInfo(&SystemInfo); + *pNumberOfProcessors = SystemInfo.dwNumberOfProcessors; + *pCacheLineSize = SYSTEM_CACHE_ALIGNMENT_SIZE; + + return S_OK; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/IISLib/precomp.h b/src/AspNetCoreModuleV2/IISLib/precomp.h new file mode 100644 index 0000000000..9cccea4045 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/precomp.h @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include +#include +#pragma warning( disable:4127 ) +#include +#include +#include +#include +#include +#include + +#include "macros.h" +#include "stringu.h" +#include "stringa.h" +#include "dbgutil.h" +#include "ntassert.h" +#include "ahutil.h" +#include "acache.h" +//#include "base64.hxx" + diff --git a/src/AspNetCoreModuleV2/IISLib/prime.h b/src/AspNetCoreModuleV2/IISLib/prime.h new file mode 100644 index 0000000000..6a6a88ed78 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/prime.h @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include + +// +// Pre-calculated prime numbers (up to 10,049,369). +// +extern __declspec(selectany) const DWORD g_Primes [] = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, + 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, + 12143, 14591, 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, + 130363, 156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, + 968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, + 5999471, 7199369, 7849369, 8649369, 9249369, 10049369 +}; + +class PRIME +{ +public: + + static + DWORD + GetPrime( + DWORD dwMinimum + ) + { + // + // Try to use the precalculated numbers. + // + for ( DWORD Index = 0; Index < _countof( g_Primes ); Index++ ) + { + DWORD dwCandidate = g_Primes[Index]; + if ( dwCandidate >= dwMinimum ) + { + return dwCandidate; + } + } + + // + // Do calculation. + // + for ( DWORD dwCandidate = dwMinimum | 1; + dwCandidate < MAXDWORD; + dwCandidate += 2 ) + { + if ( IsPrime( dwCandidate ) ) + { + return dwCandidate; + } + } + return dwMinimum; + } + +private: + + static + BOOL + IsPrime( + DWORD dwCandidate + ) + { + if ((dwCandidate & 1) == 0) + { + return ( dwCandidate == 2 ); + } + + DWORD dwMax = static_cast(sqrt(static_cast(dwCandidate))); + + for ( DWORD Index = 3; Index <= dwMax; Index += 2 ) + { + if ( (dwCandidate % Index) == 0 ) + { + return FALSE; + } + } + return TRUE; + } + + PRIME() {} + ~PRIME() {} +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/IISLib/pudebug.h b/src/AspNetCoreModuleV2/IISLib/pudebug.h new file mode 100644 index 0000000000..7b0e35da0f --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/pudebug.h @@ -0,0 +1,736 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +# ifndef _PUDEBUG_H_ +# define _PUDEBUG_H_ + +#ifndef _NO_TRACING_ +# define _NO_TRACING_ +#endif // _NO_TRACING_ + +/************************************************************ + * Include Headers + ************************************************************/ + +# ifdef __cplusplus +extern "C" { +# endif // __cplusplus + +# include + +# ifndef dllexp +# define dllexp __declspec( dllexport) +# endif // dllexp + +#include + +#ifndef IN_OUT +#define IN_OUT __inout +#endif + +/*********************************************************** + * Macros + ************************************************************/ + +enum PRINT_REASONS { + PrintNone = 0x0, // Nothing to be printed + PrintError = 0x1, // An error message + PrintWarning = 0x2, // A warning message + PrintLog = 0x3, // Just logging. Indicates a trace of where ... + PrintMsg = 0x4, // Echo input message + PrintCritical = 0x5, // Print and Exit + PrintAssertion= 0x6 // Printing for an assertion failure + }; + + +enum DEBUG_OUTPUT_FLAGS { + DbgOutputNone = 0x0, // None + DbgOutputKdb = 0x1, // Output to Kernel Debugger + DbgOutputLogFile = 0x2, // Output to LogFile + DbgOutputTruncate = 0x4, // Truncate Log File if necessary + DbgOutputStderr = 0x8, // Send output to std error + DbgOutputBackup = 0x10, // Make backup of debug file ? + DbgOutputMemory = 0x20, // Dump to memory buffer + DbgOutputAll = 0xFFFFFFFF // All the bits set. + }; + + +# define MAX_LABEL_LENGTH ( 100) + + +// The following flags are used internally to track what level of tracing we +// are currently using. Bitmapped for extensibility. +#define DEBUG_FLAG_ODS 0x00000001 +//#define DEBUG_FLAG_INFO 0x00000002 +//#define DEBUG_FLAG_WARN 0x00000004 +//#define DEBUG_FLAG_ERROR 0x00000008 +// The following are used internally to determine whether to log or not based +// on what the current state is +//#define DEBUG_FLAGS_INFO (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO) +//#define DEBUG_FLAGS_WARN (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO | DEBUG_FLAG_WARN) +//#define DEBUG_FLAGS_ERROR (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) + +#define DEBUG_FLAGS_ANY (DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) + +// +// user of DEBUG infrastructure may choose unique variable name for DEBUG_FLAGS +// that's specially useful for cases where DEBUG infrastructure is used within +// static library (static library may prefer to maintain it's own DebugFlags independent +// on the main program it links to +// +#ifndef DEBUG_FLAGS_VAR +#define DEBUG_FLAGS_VAR g_dwDebugFlags +#endif + +extern +#ifdef __cplusplus +"C" +# endif // _cplusplus + DWORD DEBUG_FLAGS_VAR ; // Debugging Flags + +# define DECLARE_DEBUG_VARIABLE() + +# define SET_DEBUG_FLAGS( dwFlags) DEBUG_FLAGS_VAR = dwFlags +# define GET_DEBUG_FLAGS() ( DEBUG_FLAGS_VAR ) + +# define LOAD_DEBUG_FLAGS_FROM_REG(hkey, dwDefault) \ + DEBUG_FLAGS_VAR = PuLoadDebugFlagsFromReg((hkey), (dwDefault)) + +# define LOAD_DEBUG_FLAGS_FROM_REG_STR(pszRegKey, dwDefault) \ + DEBUG_FLAGS_VAR = PuLoadDebugFlagsFromRegStr((pszRegKey), (dwDefault)) + +# define SAVE_DEBUG_FLAGS_IN_REG(hkey, dwDbg) \ + PuSaveDebugFlagsInReg((hkey), (dwDbg)) + +# define DEBUG_IF( arg, s) if ( DEBUG_ ## arg & GET_DEBUG_FLAGS()) { \ + s \ + } else {} + +# define IF_DEBUG( arg) if ( DEBUG_## arg & GET_DEBUG_FLAGS()) + + +/*++ + class DEBUG_PRINTS + + This class is responsible for printing messages to log file / kernel debugger + + Currently the class supports only member functions for char. + ( not unicode-strings). + +--*/ + + +typedef struct _DEBUG_PRINTS { + + CHAR m_rgchLabel[MAX_LABEL_LENGTH]; + CHAR m_rgchLogFilePath[MAX_PATH]; + CHAR m_rgchLogFileName[MAX_PATH]; + HANDLE m_LogFileHandle; + HANDLE m_StdErrHandle; + BOOL m_fInitialized; + BOOL m_fBreakOnAssert; + DWORD m_dwOutputFlags; + VOID *m_pMemoryLog; +} DEBUG_PRINTS, FAR * LPDEBUG_PRINTS; + + +LPDEBUG_PRINTS +PuCreateDebugPrintsObject( + IN const char * pszPrintLabel, + IN DWORD dwOutputFlags); + +// +// frees the debug prints object and closes any file if necessary. +// Returns NULL on success or returns pDebugPrints on failure. +// +LPDEBUG_PRINTS +PuDeleteDebugPrintsObject( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + + +VOID +PuDbgPrint( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const char * pszFormat, + ...); + // arglist +VOID +PuDbgPrintW( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const WCHAR * pszFormat, + ...); // arglist + +// PuDbgPrintError is similar to PuDbgPrint() but allows +// one to print error code in friendly manner +VOID +PuDbgPrintError( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN DWORD dwError, + IN const char * pszFormat, + ...); // arglist + +/*++ + PuDbgDump() does not do any formatting of output. + It just dumps the given message onto the debug destinations. +--*/ +VOID +PuDbgDump( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const char * pszDump + ); + +// +// PuDbgAssertFailed() *must* be __cdecl to properly capture the +// thread context at the time of the failure. +// + +INT +__cdecl +PuDbgAssertFailed( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const char * pszExpression, + IN const char * pszMessage); + +INT +WINAPI +PuDbgPrintAssertFailed( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName, + IN const char * pszExpression, + IN const char * pszMessage); + +VOID +PuDbgCaptureContext ( + OUT PCONTEXT ContextRecord + ); + +VOID +PuDbgPrintCurrentTime( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFilePath, + IN int nLineNum, + IN const char * pszFunctionName + ); + +VOID +PuSetDbgOutputFlags( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN DWORD dwFlags); + +DWORD +PuGetDbgOutputFlags( + IN const LPDEBUG_PRINTS pDebugPrints); + + +// +// Following functions return Win32 error codes. +// NO_ERROR if success +// + +DWORD +PuOpenDbgPrintFile( + IN_OUT LPDEBUG_PRINTS pDebugPrints, + IN const char * pszFileName, + IN const char * pszPathForFile); + +DWORD +PuReOpenDbgPrintFile( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + +DWORD +PuCloseDbgPrintFile( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + +DWORD +PuOpenDbgMemoryLog( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + +DWORD +PuCloseDbgMemoryLog( + IN_OUT LPDEBUG_PRINTS pDebugPrints); + +DWORD +PuLoadDebugFlagsFromReg(IN HKEY hkey, IN DWORD dwDefault); + +DWORD +PuLoadDebugFlagsFromRegStr(IN LPCSTR pszRegKey, IN DWORD dwDefault); + +DWORD +PuSaveDebugFlagsInReg(IN HKEY hkey, IN DWORD dwDbg); + + +# define PuPrintToKdb( pszOutput) \ + if ( pszOutput != NULL) { \ + OutputDebugString( pszOutput); \ + } else {} + + + +# ifdef __cplusplus +}; +# endif // __cplusplus + +// begin_user_unmodifiable + + + +/*********************************************************** + * Macros + ************************************************************/ + + +extern +#ifdef __cplusplus +"C" +# endif // _cplusplus +DEBUG_PRINTS * g_pDebug; // define a global debug variable + +# if DBG + +// For the CHK build we want ODS enabled. For an explanation of these flags see +// the comment just after the definition of DBG_CONTEXT +# define DECLARE_DEBUG_PRINTS_OBJECT() \ + DEBUG_PRINTS * g_pDebug = NULL; \ + DWORD DEBUG_FLAGS_VAR = DEBUG_FLAG_ERROR; + +#else // !DBG + +# define DECLARE_DEBUG_PRINTS_OBJECT() \ + DEBUG_PRINTS * g_pDebug = NULL; \ + DWORD DEBUG_FLAGS_VAR = 0; + +#endif // !DBG + + +// +// Call the following macro as part of your initialization for program +// planning to use the debugging class. +// +/** DEBUGDEBUG +# define CREATE_DEBUG_PRINT_OBJECT( pszLabel) \ + g_pDebug = PuCreateDebugPrintsObject( pszLabel, DEFAULT_OUTPUT_FLAGS);\ + if ( g_pDebug == NULL) { \ + OutputDebugStringA( "Unable to Create Debug Print Object \n"); \ + } +*/ + +// +// Call the following macro once as part of the termination of program +// which uses the debugging class. +// +# define DELETE_DEBUG_PRINT_OBJECT( ) \ + g_pDebug = PuDeleteDebugPrintsObject( g_pDebug); + + +# define VALID_DEBUG_PRINT_OBJECT() \ + ( ( g_pDebug != NULL) && g_pDebug->m_fInitialized) + + +// +// Use the DBG_CONTEXT without any surrounding braces. +// This is used to pass the values for global DebugPrintObject +// and File/Line information +// +//# define DBG_CONTEXT g_pDebug, __FILE__, __LINE__, __FUNCTION__ + +// The 3 main tracing macros, each one corresponds to a different level of +// tracing + +// The 3 main tracing macros, each one corresponds to a different level of +// tracing +//# define DBGINFO(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_INFO) { PuDbgPrint args; }} +//# define DBGWARN(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_WARN) { PuDbgPrint args; }} +//# define DBGERROR(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrint args; }} + +# define DBGINFOW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_INFO) { PuDbgPrintW args; }} +# define DBGWARNW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_WARN) { PuDbgPrintW args; }} +# define DBGERRORW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrintW args; }} + + +// +// DBGPRINTF() is printing function ( much like printf) but always called +// with the DBG_CONTEXT as follows +// DBGPRINTF( ( DBG_CONTEXT, format-string, arguments for format list)); +// +# define DBGPRINTF DBGINFO + +// +// DPERROR() is printing function ( much like printf) but always called +// with the DBG_CONTEXT as follows +// DPERROR( ( DBG_CONTEXT, error, format-string, +// arguments for format list)); +// +# define DPERROR( args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrintError args; }} + +# if DBG + +# define DBG_CODE(s) s /* echoes code in debugging mode */ + +// The same 3 main tracing macros however in this case the macros are only compiled +// into the CHK build. This is necessary because some tracing info used functions or +// variables which are not compiled into the FRE build. +# define CHKINFO(args) { PuDbgPrint args; } +# define CHKWARN(args) { PuDbgPrint args; } +# define CHKERROR(args) { PuDbgPrint args; } + +# define CHKINFOW(args) { PuDbgPrintW args; } +# define CHKWARNW(args) { PuDbgPrintW args; } +# define CHKERRORW(args) { PuDbgPrintW args; } + + +#ifndef DBG_ASSERT +# ifdef _PREFAST_ +# define DBG_ASSERT(exp) ((void)0) /* Do Nothing */ +# define DBG_ASSERT_MSG(exp, pszMsg) ((void)0) /* Do Nothing */ +# define DBG_REQUIRE( exp) ((void) (exp)) +# else // !_PREFAST_ +# define DBG_ASSERT( exp ) \ + ( (VOID)( ( exp ) || ( DebugBreak(), \ + PuDbgPrintAssertFailed( DBG_CONTEXT, #exp, "" ) ) ) ) + +# define DBG_ASSERT_MSG( exp, pszMsg) \ + ( (VOID)( ( exp ) || ( DebugBreak(), \ + PuDbgPrintAssertFailed( DBG_CONTEXT, #exp, pszMsg ) ) ) ) + +# define DBG_REQUIRE( exp ) \ + DBG_ASSERT( exp ) +# endif // !_PREFAST_ +#endif + + +# define DBG_LOG() PuDbgPrint( DBG_CONTEXT, "\n" ) + +# define DBG_OPEN_LOG_FILE( pszFile, pszPath ) \ + PuOpenDbgPrintFile( g_pDebug, (pszFile), (pszPath) ) + +# define DBG_CLOSE_LOG_FILE( ) \ + PuCloseDbgPrintFile( g_pDebug ) + +# define DBG_OPEN_MEMORY_LOG( ) \ + PuOpenDbgMemoryLog( g_pDebug ) + + +# define DBGDUMP( args ) PuDbgDump args + +# define DBGPRINT_CURRENT_TIME() PuDbgPrintCurrentTime( DBG_CONTEXT ) + +# else // !DBG + +# define DBG_CODE(s) ((void)0) /* Do Nothing */ + +# define CHKINFO(args) ((void)0) /* Do Nothing */ +# define CHKWARN(args) ((void)0) /* Do Nothing */ +# define CHKERROR(args) ((void)0) /* Do Nothing */ + +# define CHKINFOW(args) ((void)0) /* Do Nothing */ +# define CHKWARNW(args) ((void)0) /* Do Nothing */ +# define CHKERRORW(args) ((void)0) /* Do Nothing */ + +#ifndef DBG_ASSERT +# define DBG_ASSERT(exp) ((void)0) /* Do Nothing */ + +# define DBG_ASSERT_MSG(exp, pszMsg) ((void)0) /* Do Nothing */ + +# define DBG_REQUIRE( exp) ((void) (exp)) +#endif // !DBG_ASSERT + +# define DBGDUMP( args) ((void)0) /* Do nothing */ + +# define DBG_LOG() ((void)0) /* Do Nothing */ + +# define DBG_OPEN_LOG_FILE( pszFile, pszPath) ((void)0) /* Do Nothing */ + +# define DBG_OPEN_MEMORY_LOG() ((void)0) /* Do Nothing */ + +# define DBG_CLOSE_LOG_FILE() ((void)0) /* Do Nothing */ + +# define DBGPRINT_CURRENT_TIME() ((void)0) /* Do Nothing */ + +# endif // !DBG + + +// end_user_unmodifiable + +// begin_user_unmodifiable + + +#ifdef ASSERT +# undef ASSERT +#endif + + +# define ASSERT( exp) DBG_ASSERT( exp) + + +// end_user_unmodifiable + +// begin_user_modifiable + +// +// Debugging constants consist of two pieces. +// All constants in the range 0x0 to 0x8000 are reserved +// User extensions may include additional constants (bit flags) +// + +# define DEBUG_API_ENTRY 0x00000001L +# define DEBUG_API_EXIT 0x00000002L +# define DEBUG_INIT_CLEAN 0x00000004L +# define DEBUG_ERROR 0x00000008L + + // End of Reserved Range +# define DEBUG_RESERVED 0x00000FFFL + +// end_user_modifiable + + + +/*********************************************************** + * Platform Type related variables and macros + ************************************************************/ + +// +// Enum for product types +// + +typedef enum _PLATFORM_TYPE { + + PtInvalid = 0, // Invalid + PtNtWorkstation = 1, // NT Workstation + PtNtServer = 2, // NT Server + +} PLATFORM_TYPE; + +// +// IISGetPlatformType is the function used to the platform type +// + +extern +#ifdef __cplusplus +"C" +# endif // _cplusplus +PLATFORM_TYPE +IISGetPlatformType( + VOID + ); + +// +// External Macros +// + +#define InetIsNtServer( _pt ) ((_pt) == PtNtServer) +#define InetIsNtWksta( _pt ) ((_pt) == PtNtWorkstation) +#define InetIsValidPT(_pt) ((_pt) != PtInvalid) + +extern +#ifdef __cplusplus +"C" +# endif // _cplusplus +PLATFORM_TYPE g_PlatformType; + + +// Use the DECLARE_PLATFORM_TYPE macro to declare the platform type +#define DECLARE_PLATFORM_TYPE() \ + PLATFORM_TYPE g_PlatformType = PtInvalid; + +// Use the INITIALIZE_PLATFORM_TYPE to init the platform type +// This should typically go inside the DLLInit or equivalent place. +#define INITIALIZE_PLATFORM_TYPE() \ + g_PlatformType = IISGetPlatformType(); + +// +// Additional Macros to use the Platform Type +// + +#define TsIsNtServer( ) InetIsNtServer(g_PlatformType) +#define TsIsNtWksta( ) InetIsNtWksta(g_PlatformType) +#define IISIsValidPlatform() InetIsValidPT(g_PlatformType) +#define IISPlatformType() (g_PlatformType) + + +/*********************************************************** + * Some utility functions for Critical Sections + ************************************************************/ + +// +// IISSetCriticalSectionSpinCount() provides a thunk for the +// original NT4.0sp3 API SetCriticalSectionSpinCount() for CS with Spin counts +// Users of this function should definitely dynlink with kernel32.dll, +// Otherwise errors will surface to a large extent +// +extern +# ifdef __cplusplus +"C" +# endif // _cplusplus +DWORD +IISSetCriticalSectionSpinCount( + LPCRITICAL_SECTION lpCriticalSection, + DWORD dwSpinCount +); + + +// +// Macro for the calls to SetCriticalSectionSpinCount() +// +# define SET_CRITICAL_SECTION_SPIN_COUNT( lpCS, dwSpins) \ + IISSetCriticalSectionSpinCount( (lpCS), (dwSpins)) + +// +// IIS_DEFAULT_CS_SPIN_COUNT is the default value of spins used by +// Critical sections defined within IIS. +// NYI: We should have to switch the individual values based on experiments! +// Current value is an arbitrary choice +// +# define IIS_DEFAULT_CS_SPIN_COUNT (1000) + +// +// Initializes a critical section and sets its spin count +// to IIS_DEFAULT_CS_SPIN_COUNT. Equivalent to +// InitializeCriticalSectionAndSpinCount(lpCS, IIS_DEFAULT_CS_SPIN_COUNT), +// but provides a safe thunking layer for older systems that don't provide +// this API. +// +extern +# ifdef __cplusplus +"C" +# endif // _cplusplus +BOOL +IISInitializeCriticalSection( + LPCRITICAL_SECTION lpCriticalSection +); + +// +// Macro for the calls to InitializeCriticalSection() +// +# define INITIALIZE_CRITICAL_SECTION(lpCS) IISInitializeCriticalSection(lpCS) + +# endif /* _DEBUG_HXX_ */ + +// +// The following macros allow the automatic naming of certain Win32 objects. +// See IIS\SVCS\IISRTL\WIN32OBJ.C for details on the naming convention. +// +// Set IIS_NAMED_WIN32_OBJECTS to a non-zero value to enable named events, +// semaphores, and mutexes. +// + +#if DBG +#define IIS_NAMED_WIN32_OBJECTS 1 +#else +#define IIS_NAMED_WIN32_OBJECTS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +HANDLE +PuDbgCreateEvent( + __in LPSTR FileName, + IN ULONG LineNumber, + __in LPSTR MemberName, + IN PVOID Address, + IN BOOL ManualReset, + IN BOOL InitialState + ); + +HANDLE +PuDbgCreateSemaphore( + __in LPSTR FileName, + IN ULONG LineNumber, + __in LPSTR MemberName, + IN PVOID Address, + IN LONG InitialCount, + IN LONG MaximumCount + ); + +HANDLE +PuDbgCreateMutex( + __in LPSTR FileName, + IN ULONG LineNumber, + __in LPSTR MemberName, + IN PVOID Address, + IN BOOL InitialOwner + ); + +#ifdef __cplusplus +} // extern "C" +#endif + +#if IIS_NAMED_WIN32_OBJECTS + +#define IIS_CREATE_EVENT( membername, address, manual, state ) \ + PuDbgCreateEvent( \ + (LPSTR)__FILE__, \ + (ULONG)__LINE__, \ + (membername), \ + (PVOID)(address), \ + (manual), \ + (state) \ + ) + +#define IIS_CREATE_SEMAPHORE( membername, address, initial, maximum ) \ + PuDbgCreateSemaphore( \ + (LPSTR)__FILE__, \ + (ULONG)__LINE__, \ + (membername), \ + (PVOID)(address), \ + (initial), \ + (maximum) \ + ) + +#define IIS_CREATE_MUTEX( membername, address, initial ) \ + PuDbgCreateMutex( \ + (LPSTR)__FILE__, \ + (ULONG)__LINE__, \ + (membername), \ + (PVOID)(address), \ + (initial) \ + ) + +#else // !IIS_NAMED_WIN32_OBJECTS + +#define IIS_CREATE_EVENT( membername, address, manual, state ) \ + CreateEventA( \ + NULL, \ + (manual), \ + (state), \ + NULL \ + ) + +#define IIS_CREATE_SEMAPHORE( membername, address, initial, maximum ) \ + CreateSemaphoreA( \ + NULL, \ + (initial), \ + (maximum), \ + NULL \ + ) + +#define IIS_CREATE_MUTEX( membername, address, initial ) \ + CreateMutexA( \ + NULL, \ + (initial), \ + NULL \ + ) + +#endif // IIS_NAMED_WIN32_OBJECTS + + +/************************ End of File ***********************/ + diff --git a/src/AspNetCoreModuleV2/IISLib/reftrace.c b/src/AspNetCoreModuleV2/IISLib/reftrace.c new file mode 100644 index 0000000000..c1b2e13a6e --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/reftrace.c @@ -0,0 +1,229 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include +#include "dbgutil.h" +#include "pudebug.h" +#include "reftrace.h" + + +PTRACE_LOG +CreateRefTraceLog( + IN LONG LogSize, + IN LONG ExtraBytesInHeader + ) +/*++ + +Routine Description: + + Creates a new (empty) ref count trace log buffer. + +Arguments: + + LogSize - The number of entries in the log. + + ExtraBytesInHeader - The number of extra bytes to include in the + log header. This is useful for adding application-specific + data to the log. + +Return Value: + + PTRACE_LOG - Pointer to the newly created log if successful, + NULL otherwise. + +--*/ +{ + + return CreateTraceLog( + LogSize, + ExtraBytesInHeader, + sizeof(REF_TRACE_LOG_ENTRY) + ); + +} // CreateRefTraceLog + + +VOID +DestroyRefTraceLog( + IN PTRACE_LOG Log + ) +/*++ + +Routine Description: + + Destroys a ref count trace log buffer created with CreateRefTraceLog(). + +Arguments: + + Log - The ref count trace log buffer to destroy. + +Return Value: + + None. + +--*/ +{ + + DestroyTraceLog( Log ); + +} // DestroyRefTraceLog + + +// +// N.B. For RtlCaptureBacktrace() to work properly, the calling function +// *must* be __cdecl, and must have a "normal" stack frame. So, we decorate +// WriteRefTraceLog[Ex]() with the __cdecl modifier and disable the frame +// pointer omission (FPO) optimization. +// + +//#pragma optimize( "y", off ) // disable frame pointer omission (FPO) +#pragma optimize( "", off ) // disable frame pointer omission (FPO) + +LONG +__cdecl +WriteRefTraceLog( + IN PTRACE_LOG Log, + IN LONG NewRefCount, + IN CONST VOID * Context + ) +/*++ + +Routine Description: + + Writes a new entry to the specified ref count trace log. The entry + written contains the updated reference count and a stack backtrace + leading up to the current caller. + +Arguments: + + Log - The log to write to. + + NewRefCount - The updated reference count. + + Context - An uninterpreted context to associate with the log entry. + +Return Value: + + Index of entry in log. + +--*/ +{ + + return WriteRefTraceLogEx( + Log, + NewRefCount, + Context, + REF_TRACE_EMPTY_CONTEXT, // suppress use of optional extra contexts + REF_TRACE_EMPTY_CONTEXT, + REF_TRACE_EMPTY_CONTEXT + ); + +} // WriteRefTraceLog + + + + +LONG +__cdecl +WriteRefTraceLogEx( + IN PTRACE_LOG Log, + IN LONG NewRefCount, + IN CONST VOID * Context, + IN CONST VOID * Context1, // optional extra context + IN CONST VOID * Context2, // optional extra context + IN CONST VOID * Context3 // optional extra context + ) +/*++ + +Routine Description: + + Writes a new "extended" entry to the specified ref count trace log. + The entry written contains the updated reference count, stack backtrace + leading up to the current caller and extra context information. + +Arguments: + + Log - The log to write to. + + NewRefCount - The updated reference count. + + Context - An uninterpreted context to associate with the log entry. + Context1 - An uninterpreted context to associate with the log entry. + Context2 - An uninterpreted context to associate with the log entry. + Context3 - An uninterpreted context to associate with the log entry. + + NOTE Context1/2/3 are "optional" in that the caller may suppress + debug display of these values by passing REF_TRACE_EMPTY_CONTEXT + for each of them. + +Return Value: + + Index of entry in log. + +--*/ +{ + + REF_TRACE_LOG_ENTRY entry; + ULONG hash; + DWORD cStackFramesSkipped; + + // + // Initialize the entry. + // + + RtlZeroMemory( + &entry, + sizeof(entry) + ); + + // + // Set log entry members. + // + + entry.NewRefCount = NewRefCount; + entry.Context = Context; + entry.Thread = GetCurrentThreadId(); + entry.Context1 = Context1; + entry.Context2 = Context2; + entry.Context3 = Context3; + + // + // Capture the stack backtrace. Normally, we skip two stack frames: + // one for this routine, and one for RtlCaptureBacktrace() itself. + // For non-Ex callers who come in via WriteRefTraceLog, + // we skip three stack frames. + // + + if ( entry.Context1 == REF_TRACE_EMPTY_CONTEXT + && entry.Context2 == REF_TRACE_EMPTY_CONTEXT + && entry.Context3 == REF_TRACE_EMPTY_CONTEXT + ) { + + cStackFramesSkipped = 2; + + } else { + + cStackFramesSkipped = 1; + + } + + RtlCaptureStackBackTrace( + cStackFramesSkipped, + REF_TRACE_LOG_STACK_DEPTH, + entry.Stack, + &hash + ); + + // + // Write it to the log. + // + + return WriteTraceLog( + Log, + &entry + ); + +} // WriteRefTraceLogEx + +#pragma optimize( "", on ) // restore frame pointer omission (FPO) + diff --git a/src/AspNetCoreModuleV2/IISLib/reftrace.h b/src/AspNetCoreModuleV2/IISLib/reftrace.h new file mode 100644 index 0000000000..e90ca0444a --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/reftrace.h @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _REFTRACE_H_ +#define _REFTRACE_H_ + + +#if defined(__cplusplus) +extern "C" { +#endif // __cplusplus + +#include +#include "tracelog.h" + +// +// This is the number of stack backtrace values captured in each +// trace log entry. This value is chosen to make the log entry +// exactly twelve dwords long, making it a bit easier to interpret +// from within the debugger without the debugger extension. +// + +#define REF_TRACE_LOG_STACK_DEPTH 9 + +// No-op value for the Context1,2,3 parameters of WriteRefTraceLogEx +//#define REF_TRACE_EMPTY_CONTEXT ((PVOID) -1) +#define REF_TRACE_EMPTY_CONTEXT NULL + + +// +// This defines the entry written to the trace log. +// + +typedef struct _REF_TRACE_LOG_ENTRY { + + LONG NewRefCount; + CONST VOID * Context; + CONST VOID * Context1; + CONST VOID * Context2; + CONST VOID * Context3; + DWORD Thread; + PVOID Stack[REF_TRACE_LOG_STACK_DEPTH]; + +} REF_TRACE_LOG_ENTRY, *PREF_TRACE_LOG_ENTRY; + + +// +// Manipulators. +// + +PTRACE_LOG +CreateRefTraceLog( + IN LONG LogSize, + IN LONG ExtraBytesInHeader + ); + +VOID +DestroyRefTraceLog( + IN PTRACE_LOG Log + ); + +LONG +__cdecl +WriteRefTraceLog( + IN PTRACE_LOG Log, + IN LONG NewRefCount, + IN CONST VOID * Context + ); + +LONG +__cdecl +WriteRefTraceLogEx( + IN PTRACE_LOG Log, + IN LONG NewRefCount, + IN CONST VOID * Context, + IN CONST VOID * Context1, + IN CONST VOID * Context2, + IN CONST VOID * Context3 + ); + + +#if defined(__cplusplus) +} // extern "C" +#endif // __cplusplus + + +#endif // _REFTRACE_H_ + diff --git a/src/AspNetCoreModuleV2/IISLib/rwlock.h b/src/AspNetCoreModuleV2/IISLib/rwlock.h new file mode 100644 index 0000000000..dc7ccf834b --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/rwlock.h @@ -0,0 +1,193 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#if (_WIN32_WINNT < 0x600) + +// +// XP implementation. +// +class CWSDRWLock +{ +public: + + CWSDRWLock() + : m_bInited(FALSE) + { + } + + ~CWSDRWLock() + { + if (m_bInited) + { + DeleteCriticalSection(&m_rwLock.critsec); + CloseHandle(m_rwLock.ReadersDoneEvent); + } + } + + BOOL QueryInited() const + { + return m_bInited; + } + + HRESULT Init() + { + HRESULT hr = S_OK; + + if (FALSE == m_bInited) + { + m_rwLock.fWriterWaiting = FALSE; + m_rwLock.LockCount = 0; + if ( !InitializeCriticalSectionAndSpinCount( &m_rwLock.critsec, 0 )) + { + DWORD dwError = GetLastError(); + hr = HRESULT_FROM_WIN32(dwError); + return hr; + } + + m_rwLock.ReadersDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if( NULL == m_rwLock.ReadersDoneEvent ) + { + DWORD dwError = GetLastError(); + hr = HRESULT_FROM_WIN32(dwError); + DeleteCriticalSection(&m_rwLock.critsec); + return hr; + } + m_bInited = TRUE; + } + + return hr; + } + + void SharedAcquire() + { + EnterCriticalSection(&m_rwLock.critsec); + InterlockedIncrement(&m_rwLock.LockCount); + LeaveCriticalSection(&m_rwLock.critsec); + } + + void SharedRelease() + { + ReleaseRWLock(); + } + + void ExclusiveAcquire() + { + EnterCriticalSection( &m_rwLock.critsec ); + + m_rwLock.fWriterWaiting = TRUE; + + // check if there are any readers active + if ( InterlockedExchangeAdd( &m_rwLock.LockCount, 0 ) > 0 ) + { + // + // Wait for all the readers to get done.. + // + WaitForSingleObject( m_rwLock.ReadersDoneEvent, INFINITE ); + } + m_rwLock.LockCount = -1; + } + + void ExclusiveRelease() + { + ReleaseRWLock(); + } + +private: + + BOOL m_bInited; + + typedef struct _RW_LOCK + { + BOOL fWriterWaiting; // Is a writer waiting on the lock? + LONG LockCount; + CRITICAL_SECTION critsec; + HANDLE ReadersDoneEvent; + } RW_LOCK, *PRW_LOCK; + + RW_LOCK m_rwLock; + +private: + + void ReleaseRWLock() + { + LONG Count = InterlockedDecrement( &m_rwLock.LockCount ); + + if ( 0 <= Count ) + { + // releasing a read lock + if (( m_rwLock.fWriterWaiting ) && ( 0 == Count )) + { + SetEvent( m_rwLock.ReadersDoneEvent ); + } + } + else + { + // Releasing a write lock + m_rwLock.LockCount = 0; + m_rwLock.fWriterWaiting = FALSE; + LeaveCriticalSection(&m_rwLock.critsec); + } + } +}; + +#else + +// +// Implementation for Windows Vista or greater. +// +class CWSDRWLock +{ +public: + + CWSDRWLock() + { + InitializeSRWLock(&m_rwLock); + } + + BOOL QueryInited() + { + return TRUE; + } + + + HRESULT Init() + { + // + // Method defined to keep compatibility with CWSDRWLock class for XP. + // + return S_OK; + } + + void SharedAcquire() + { + AcquireSRWLockShared(&m_rwLock); + } + + void SharedRelease() + { + ReleaseSRWLockShared(&m_rwLock); + } + + void ExclusiveAcquire() + { + AcquireSRWLockExclusive(&m_rwLock); + } + + void ExclusiveRelease() + { + ReleaseSRWLockExclusive(&m_rwLock); + } + +private: + + SRWLOCK m_rwLock; +}; + +#endif + +// +// Rename the lock class to a more clear name. +// +typedef CWSDRWLock READ_WRITE_LOCK; \ No newline at end of file diff --git a/src/AspNetCoreModuleV2/IISLib/stringa.cpp b/src/AspNetCoreModuleV2/IISLib/stringa.cpp new file mode 100644 index 0000000000..29da773bca --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/stringa.cpp @@ -0,0 +1,1767 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.h" + +STRA::STRA( + VOID +) : m_cchLen( 0 ) +{ + *( QueryStr() ) = '\0'; +} + +STRA::STRA( + __inout_ecount(cchInit) CHAR* pbInit, + __in DWORD cchInit +) : m_Buff( pbInit, cchInit * sizeof( CHAR ) ), + m_cchLen(0) +/*++ + Description: + + Used by STACK_STRA. Initially populates underlying buffer with pbInit. + + pbInit is not freed. + + Arguments: + + pbInit - initial memory to use + cchInit - count, in characters, of pbInit + + Returns: + + None. + +--*/ +{ + _ASSERTE( NULL != pbInit ); + _ASSERTE( cchInit > 0 ); + _ASSERTE( pbInit[0] == '\0' ); +} + +BOOL +STRA::IsEmpty( + VOID +) const +{ + return ( m_cchLen == 0 ); +} + +BOOL +STRA::Equals( + __in PCSTR pszRhs, + __in BOOL fIgnoreCase /*= FALSE*/ +) const +{ + _ASSERTE( NULL != pszRhs ); + + if( fIgnoreCase ) + { + return ( 0 == _stricmp( QueryStr(), pszRhs ) ); + } + + return ( 0 == strcmp( QueryStr(), pszRhs ) ); +} + +BOOL +STRA::Equals( + __in const STRA * pstrRhs, + __in BOOL fIgnoreCase /*= FALSE*/ +) const +{ + _ASSERTE( NULL != pstrRhs ); + return Equals( pstrRhs->QueryStr(), fIgnoreCase ); +} + +BOOL +STRA::Equals( + __in const STRA & strRhs, + __in BOOL fIgnoreCase /*= FALSE*/ +) const +{ + return Equals( strRhs.QueryStr(), fIgnoreCase ); +} + +DWORD +STRA::QueryCB( + VOID +) const +// +// Returns the number of bytes in the string excluding the terminating NULL +// +{ + return m_cchLen * sizeof( CHAR ); +} + +DWORD +STRA::QueryCCH( + VOID +) const +// +// Returns the number of characters in the string excluding the terminating NULL +// +{ + return m_cchLen; +} + +DWORD +STRA::QuerySizeCCH( + VOID +) const +// +// Returns size of the underlying storage buffer, in characters +// +{ + return m_Buff.QuerySize() / sizeof( CHAR ); +} + +DWORD +STRA::QuerySize( + VOID +) const +// +// Returns the size of the storage buffer in bytes +// +{ + return m_Buff.QuerySize(); +} + +__nullterminated +__bcount(this->m_cchLen) +CHAR * +STRA::QueryStr( + VOID +) const +// +// Return the string buffer +// +{ + return m_Buff.QueryPtr(); +} + +VOID +STRA::Reset( + VOID +) +// +// Resets the internal string to be NULL string. Buffer remains cached. +// +{ + _ASSERTE( QueryStr() != NULL ); + *(QueryStr()) = '\0'; + m_cchLen = 0; +} + +HRESULT +STRA::Resize( + __in DWORD cchSize +) +{ + if( !m_Buff.Resize( cchSize * sizeof( CHAR ) ) ) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT +STRA::SyncWithBuffer( + VOID +) +// +// Recalculate the length of the string, etc. because we've modified +// the buffer directly. +// +{ + HRESULT hr; + size_t size; + hr = StringCchLengthA( QueryStr(), + QuerySizeCCH(), + &size ); + if ( SUCCEEDED( hr ) ) + { + m_cchLen = static_cast(size); + } + return hr; +} + +HRESULT +STRA::Copy( + __in PCSTR pszCopy +) +{ + HRESULT hr; + size_t cbLen; + hr = StringCbLengthA( pszCopy, + STRSAFE_MAX_CCH, + &cbLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return Copy( pszCopy, cbLen ); +} + + +HRESULT +STRA::Copy( + __in_ecount(cchLen) + PCSTR pszCopy, + __in SIZE_T cbLen +) +// +// Copy the contents of another string to this one +// +{ + _ASSERTE( cbLen <= MAXDWORD ); + + return AuxAppend( + pszCopy, + static_cast(cbLen), + 0 + ); +} + +HRESULT +STRA::Copy( + __in const STRA * pstrRhs +) +{ + _ASSERTE( pstrRhs != NULL ); + return Copy( pstrRhs->QueryStr(), pstrRhs->QueryCCH() ); +} + +HRESULT +STRA::Copy( + __in const STRA & strRhs +) +{ + return Copy( strRhs.QueryStr(), strRhs.QueryCCH() ); +} + +HRESULT +STRA::CopyW( + __in PCWSTR pszCopyW +) +{ + HRESULT hr; + size_t cchLen; + hr = StringCchLengthW( pszCopyW, + STRSAFE_MAX_CCH, + &cchLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return CopyW( pszCopyW, cchLen ); +} + +HRESULT +STRA::CopyWTruncate( + __in PCWSTR pszCopyWTruncate +) +{ + HRESULT hr; + size_t cchLen; + hr = StringCchLengthW( pszCopyWTruncate, + STRSAFE_MAX_CCH, + &cchLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return CopyWTruncate( pszCopyWTruncate, cchLen ); +} + +HRESULT +STRA::CopyWTruncate( + __in_ecount(cchLen) + PCWSTR pszCopyWTruncate, + __in SIZE_T cchLen +) +// +// The "Truncate" methods do not do proper conversion. They do a (CHAR) caste +// +{ + _ASSERTE( cchLen <= MAXDWORD ); + + return AuxAppendWTruncate( + pszCopyWTruncate, + static_cast(cchLen), + 0 + ); +} + +HRESULT +STRA::Append( + __in PCSTR pszAppend +) +{ + HRESULT hr; + size_t cbLen; + hr = StringCbLengthA( pszAppend, + STRSAFE_MAX_CCH, + &cbLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return Append( pszAppend, cbLen ); +} + +HRESULT +STRA::Append( + __in_ecount(cchLen) + PCSTR pszAppend, + __in SIZE_T cbLen +) +{ + _ASSERTE( cbLen <= MAXDWORD ); + if ( cbLen == 0 ) + { + return S_OK; + } + return AuxAppend( + pszAppend, + static_cast(cbLen), + QueryCB() + ); +} + +HRESULT +STRA::Append( + __in const STRA * pstrRhs +) +{ + _ASSERTE( pstrRhs != NULL ); + return Append( pstrRhs->QueryStr(), pstrRhs->QueryCCH() ); +} + +HRESULT +STRA::Append( + __in const STRA & strRhs +) +{ + return Append( strRhs.QueryStr(), strRhs.QueryCCH() ); +} + +HRESULT +STRA::AppendWTruncate( + __in PCWSTR pszAppendWTruncate +) +{ + HRESULT hr; + size_t cchLen; + hr = StringCchLengthW( pszAppendWTruncate, + STRSAFE_MAX_CCH, + &cchLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return AppendWTruncate( pszAppendWTruncate, cchLen ); +} + +HRESULT +STRA::AppendWTruncate( + __in_ecount(cchLen) + PCWSTR pszAppendWTruncate, + __in SIZE_T cchLen +) +// +// The "Truncate" methods do not do proper conversion. They do a (CHAR) caste +// +{ + _ASSERTE( cchLen <= MAXDWORD ); + if ( cchLen == 0 ) + { + return S_OK; + } + return AuxAppendWTruncate( + pszAppendWTruncate, + static_cast(cchLen), + QueryCB() + ); +} + +HRESULT +STRA::CopyToBuffer( + __out_bcount(*pcb) CHAR* pszBuffer, + __inout DWORD * pcb +) const +// +// Makes a copy of the stored string into the given buffer +// +{ + _ASSERTE( NULL != pszBuffer ); + _ASSERTE( NULL != pcb ); + + HRESULT hr = S_OK; + DWORD cbNeeded = QueryCB() + sizeof( CHAR ); + + if( *pcb < cbNeeded ) + { + hr = HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ); + goto Finished; + } + + memcpy( pszBuffer, QueryStr(), cbNeeded ); + +Finished: + + *pcb = cbNeeded; + + return hr; +} + +HRESULT +STRA::SetLen( + __in DWORD cchLen +) +/*++ + * +Routine Description: + + Set the length of the string and null terminate, if there + is sufficient buffer already allocated. Will not reallocate. + +Arguments: + + cchLen - The number of characters in the new string. + +Return Value: + + HRESULT + +--*/ +{ + if( cchLen >= QuerySizeCCH() ) + { + return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + } + + *( QueryStr() + cchLen ) = '\0'; + m_cchLen = cchLen; + + return S_OK; +} + + +HRESULT +STRA::SafeSnprintf( + __in __format_string + PCSTR pszFormatString, + ... +) +/*++ + +Routine Description: + + Writes to a STRA, growing it as needed. It arbitrarily caps growth at 64k chars. + +Arguments: + + pszFormatString - printf format + ... - printf args + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + va_list argsList; + va_start( argsList, pszFormatString ); + + hr = SafeVsnprintf(pszFormatString, argsList); + + va_end( argsList ); + return hr; +} + +HRESULT +STRA::SafeVsnprintf( + __in __format_string + PCSTR pszFormatString, + va_list argsList +) +/*++ + +Routine Description: + + Writes to a STRA, growing it as needed. It arbitrarily caps growth at 64k chars. + +Arguments: + + pszFormatString - printf format + argsList - printf va_list + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + int cchOutput; + int cchNeeded; + + // + // Format the incoming message using vsnprintf() + // so that the overflows are captured + // + cchOutput = _vsnprintf_s( + QueryStr(), + QuerySizeCCH(), + QuerySizeCCH() - 1, + pszFormatString, + argsList + ); + + if( cchOutput == -1 ) + { + // + // Couldn't fit this in the original STRU size. + // + cchNeeded = _vscprintf( pszFormatString, argsList ); + if( cchNeeded > 64 * 1024 ) + { + // + // If we're trying to produce a string > 64k chars, then + // there is probably a problem + // + hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); + goto Finished; + } + + // + // _vscprintf doesn't include terminating null character + // + cchNeeded++; + + hr = Resize( cchNeeded ); + if( FAILED( hr ) ) + { + goto Finished; + } + + cchOutput = _vsnprintf_s( + QueryStr(), + QuerySizeCCH(), + QuerySizeCCH() - 1, + pszFormatString, + argsList + ); + if( -1 == cchOutput ) + { + // + // This should never happen, cause we should already have correctly sized memory + // + _ASSERTE( FALSE ); + + hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); + goto Finished; + } + } + + // + // always null terminate at the last WCHAR + // + QueryStr()[ QuerySizeCCH() - 1 ] = L'\0'; + + // + // we directly touched the buffer - therefore: + // + hr = SyncWithBuffer(); + if( FAILED( hr ) ) + { + goto Finished; + } + +Finished: + + if( FAILED( hr ) ) + { + Reset(); + } + + return hr; +} + +bool +FShouldEscapeUtf8( + BYTE ch + ) +{ + if ( ( ch >= 128 ) ) + { + return true; + } + + return false; +} + +bool +FShouldEscapeUrl( + BYTE ch + ) +{ + if ( ( ch >= 128 || + ch <= 32 || + ch == '<' || + ch == '>' || + ch == '%' || + ch == '?' || + ch == '#' ) && + !( ch == '\n' || ch == '\r' ) ) + { + return true; + } + + return false; +} + +HRESULT +STRA::Escape( + VOID +) +/*++ + +Routine Description: + + Escapes a STRA + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + return EscapeInternal( FShouldEscapeUrl ); +} + +HRESULT +STRA::EscapeUtf8( + VOID +) +/*++ + +Routine Description: + + Escapes the high-bit chars in a STRA. LWS, CR, LF & controls are untouched. + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + return EscapeInternal( FShouldEscapeUtf8 ); +} + + +HRESULT +STRA::EscapeInternal( + PFN_F_SHOULD_ESCAPE pfnFShouldEscape +) +/*++ + +Routine Description: + + Escapes a STRA according to the predicate function passed in + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + LPCSTR pch = QueryStr(); + __analysis_assume( pch != NULL ); + int i = 0; + BYTE ch; + HRESULT hr = S_OK; + BOOL fRet = FALSE; + SIZE_T NewSize = 0; + + // Set to true if any % escaping occurs + BOOL fEscapingDone = FALSE; + + // + // If there are any characters that need to be escaped we copy the entire string + // character by character into straTemp, escaping as we go, then at the end + // copy all of straTemp over. Don't modify InlineBuffer directly. + // + CHAR InlineBuffer[512]; + InlineBuffer[0] = '\0'; + STRA straTemp(InlineBuffer, sizeof(InlineBuffer)/sizeof(*InlineBuffer)); + + _ASSERTE( pch ); + + while (ch = pch[i]) + { + // + // Escape characters that are in the non-printable range + // but ignore CR and LF + // + + if ( pfnFShouldEscape( ch ) ) + { + if (FALSE == fEscapingDone) + { + // first character in the string that needed escaping + fEscapingDone = TRUE; + + // guess that the size needs to be larger than + // what we used to have times two + NewSize = QueryCCH() * 2; + if ( NewSize > MAXDWORD ) + { + hr = HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + return hr; + } + + hr = straTemp.Resize( static_cast(NewSize) ); + + if (FAILED(hr)) + { + return hr; + } + + // Copy all of the previous buffer into buffTemp, only if it is not the first character: + + if ( i > 0) + { + hr = straTemp.Copy(QueryStr(), + i * sizeof(CHAR)); + if (FAILED(hr)) + { + return hr; + } + } + } + + // resize the temporary (if needed) with the slop of the entire buffer length + // this fixes constant reallocation if the entire string needs to be escaped + NewSize = QueryCCH() + 2 * sizeof(CHAR) + 1 * sizeof(CHAR); + if ( NewSize > MAXDWORD ) + { + hr = HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + return hr; + } + + fRet = straTemp.m_Buff.Resize( NewSize ); + if ( !fRet ) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + // + // Create the string to append for the current character + // + + CHAR chHex[3]; + chHex[0] = '%'; + + // + // Convert the low then the high character to hex + // + + UINT nLowDigit = (UINT)(ch % 16); + chHex[2] = TODIGIT( nLowDigit ); + + ch /= 16; + + UINT nHighDigit = (UINT)(ch % 16); + + chHex[1] = TODIGIT( nHighDigit ); + + // + // Actually append the converted character to the end of the temporary + // + hr = straTemp.Append(chHex, 3); + if (FAILED(hr)) + { + return hr; + } + } + else + { + // if no escaping done, no need to copy + if (fEscapingDone) + { + // if ANY escaping done, copy current character into new buffer + straTemp.Append(&pch[i], 1); + } + } + + // inspect the next character in the string + i++; + } + + if (fEscapingDone) + { + // the escaped string is now in straTemp + hr = Copy(straTemp); + } + + return hr; + +} // EscapeInternal() + +VOID +STRA::Unescape( + VOID +) +/*++ + +Routine Description: + + Unescapes a STRA + + Supported escape sequences are: + %uxxxx unescapes Unicode character xxxx into system codepage + %xx unescapes character xx + % without following hex digits is ignored + +Arguments: + + None + +Return Value: + + None + +--*/ +{ + CHAR *pScan; + CHAR *pDest; + CHAR *pNextScan; + WCHAR wch; + DWORD dwLen; + BOOL fChanged = FALSE; + + // + // Now take care of any escape characters + // + pDest = pScan = strchr(QueryStr(), '%'); + + while (pScan) + { + if ((pScan[1] == 'u' || pScan[1] == 'U') && + SAFEIsXDigit(pScan[2]) && + SAFEIsXDigit(pScan[3]) && + SAFEIsXDigit(pScan[4]) && + SAFEIsXDigit(pScan[5])) + { + wch = TOHEX(pScan[2]) * 4096 + TOHEX(pScan[3]) * 256 + + TOHEX(pScan[4]) * 16 + TOHEX(pScan[5]); + + dwLen = WideCharToMultiByte(CP_ACP, + WC_NO_BEST_FIT_CHARS, + &wch, + 1, + (LPSTR) pDest, + 6, + NULL, + NULL); + + pDest += dwLen; + pScan += 6; + fChanged = TRUE; + } + else if (SAFEIsXDigit(pScan[1]) && SAFEIsXDigit(pScan[2])) + { + *pDest = TOHEX(pScan[1]) * 16 + TOHEX(pScan[2]); + + pDest ++; + pScan += 3; + fChanged = TRUE; + } + else // Not an escaped char, just a '%' + { + if (fChanged) + { + *pDest = *pScan; + } + + pDest++; + pScan++; + } + + // + // Copy all the information between this and the next escaped char + // + pNextScan = strchr(pScan, '%'); + + if (fChanged) // pScan!=pDest, so we have to copy the char's + { + if (!pNextScan) // That was the last '%' in the string + { + memmove(pDest, + pScan, + QueryCCH() - DIFF(pScan - QueryStr()) + 1); + } + else + { + // There is another '%', move intermediate chars + if ((dwLen = (DWORD)DIFF(pNextScan - pScan)) != 0) + { + memmove(pDest, + pScan, + dwLen); + pDest += dwLen; + } + } + } + + pScan = pNextScan; + } + + if (fChanged) + { + m_cchLen = (DWORD)strlen(QueryStr()); // for safety recalc the length + } + + return; +} + +HRESULT +STRA::CopyWToUTF8Unescaped( + __in LPCWSTR cpchStr +) +{ + return STRA::CopyWToUTF8Unescaped(cpchStr, (DWORD) wcslen(cpchStr)); +} + +HRESULT +STRA::CopyWToUTF8Unescaped( + __in_ecount(cch) + LPCWSTR cpchStr, + __in DWORD cch +) +{ + HRESULT hr = S_OK; + int iRet; + + if (cch == 0) + { + Reset(); + return S_OK; + } + + iRet = ConvertUnicodeToUTF8(cpchStr, + &m_Buff, + cch); + if (-1 == iRet) + { + // could not convert + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + m_cchLen = iRet; + + _ASSERTE(strlen(m_Buff.QueryPtr()) == m_cchLen); +Finished: + return hr; +} + +HRESULT +STRA::CopyWToUTF8Escaped( + __in LPCWSTR cpchStr +) +{ + return STRA::CopyWToUTF8Escaped(cpchStr, (DWORD) wcslen(cpchStr)); +} + +HRESULT +STRA::CopyWToUTF8Escaped( + __in_ecount(cch) + LPCWSTR cpchStr, + __in DWORD cch +) +{ + HRESULT hr = S_OK; + + hr = CopyWToUTF8Unescaped(cpchStr, cch); + if (FAILED(hr)) + { + goto Finished; + } + + hr = Escape(); + if (FAILED(hr)) + { + goto Finished; + } + + hr = S_OK; +Finished: + return hr; +} + +HRESULT +STRA::AuxAppend( + __in_ecount(cbLen) + LPCSTR pStr, + __in DWORD cbLen, + __in DWORD cbOffset +) +{ + _ASSERTE( NULL != pStr ); + _ASSERTE( cbOffset <= QueryCB() ); + + ULONGLONG cb64NewSize = (ULONGLONG)cbOffset + cbLen + sizeof( CHAR ); + if( cb64NewSize > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + + if( m_Buff.QuerySize() < cb64NewSize ) + { + if( !m_Buff.Resize( static_cast(cb64NewSize) ) ) + { + return E_OUTOFMEMORY; + } + } + + memcpy( reinterpret_cast(m_Buff.QueryPtr()) + cbOffset, pStr, cbLen ); + + m_cchLen = cbLen + cbOffset; + + *( QueryStr() + m_cchLen ) = '\0'; + + return S_OK; +} + +HRESULT +STRA::AuxAppendW( + __in_ecount(cchAppendW) + PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset, + __in UINT CodePage, + __in BOOL fFailIfNoTranslation, + __in DWORD dwFlags +) +{ + HRESULT hr = S_OK; + DWORD cbAvailable = 0; + DWORD cbRet = 0; + + // + // There are only two expect places to append + // + _ASSERTE( 0 == cbOffset || QueryCB() == cbOffset ); + + if ( cchAppendW == 0 ) + { + goto Finished; + } + + // + // start by assuming 1 char to 1 char will be enough space + // + if( !m_Buff.Resize( cbOffset + cchAppendW + sizeof( CHAR ) ) ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + cbAvailable = m_Buff.QuerySize() - cbOffset; + + cbRet = WideCharToMultiByte( + CodePage, + dwFlags, + pszAppendW, + cchAppendW, + QueryStr() + cbOffset, + cbAvailable, + NULL, + NULL + ); + if( 0 != cbRet ) + { + if(!m_Buff.Resize(cbOffset + cbRet + 1)) + { + hr = E_OUTOFMEMORY; + } + + // + // not zero --> success, so we're done + // + goto Finished; + } + + // + // We only know how to handle ERROR_INSUFFICIENT_BUFFER + // + hr = HRESULT_FROM_WIN32( GetLastError() ); + if( hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) ) + { + goto Finished; + } + + // + // Reset HResult because we need to get the number of bytes needed + // + hr = S_OK; + cbRet = WideCharToMultiByte( + CodePage, + dwFlags, + pszAppendW, + cchAppendW, + NULL, + 0, + NULL, + NULL + ); + if( 0 == cbRet ) + { + // + // no idea how we could ever reach here + // + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + + if( !m_Buff.Resize( cbOffset + cbRet + 1) ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + cbAvailable = m_Buff.QuerySize() - cbOffset; + + cbRet = WideCharToMultiByte( + CodePage, + dwFlags, + pszAppendW, + cchAppendW, + QueryStr() + cbOffset, + cbAvailable, + NULL, + NULL + ); + if( 0 == cbRet ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + +Finished: + + if( SUCCEEDED( hr ) && 0 != cbRet ) + { + m_cchLen = cbRet + cbOffset; + } + + // + // ensure we're still NULL terminated in the right spot + // (regardless of success or failure) + // + QueryStr()[m_cchLen] = '\0'; + + return hr; +} + +HRESULT +STRA::AuxAppendWTruncate( + __in_ecount(cchAppendW) + __in PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset +) +// +// Cheesey WCHAR --> CHAR conversion +// +{ + HRESULT hr = S_OK; + CHAR* pszBuffer; + + _ASSERTE( NULL != pszAppendW ); + _ASSERTE( 0 == cbOffset || cbOffset == QueryCB() ); + + if( !pszAppendW ) + { + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + ULONGLONG cbNeeded = (ULONGLONG)cbOffset + cchAppendW + sizeof( CHAR ); + if( cbNeeded > MAXDWORD ) + { + hr = HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + goto Finished; + } + + if( !m_Buff.Resize( static_cast(cbNeeded) ) ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // Copy/convert the UNICODE string over (by making two bytes into one) + // + pszBuffer = QueryStr() + cbOffset; + for( DWORD i = 0; i < cchAppendW; i++ ) + { + pszBuffer[i] = static_cast(pszAppendW[i]); + } + + m_cchLen = cchAppendW + cbOffset; + *( QueryStr() + m_cchLen ) = '\0'; + +Finished: + + return hr; +} + +// static +int +STRA::ConvertUnicodeToCodePage( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __inout BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen, + __in UINT uCodePage +) +{ + _ASSERTE(NULL != pszSrcUnicodeString); + _ASSERTE(NULL != pbufDstAnsiString); + + BOOL bTemp; + int iStrLen = 0; + DWORD dwFlags; + + if (uCodePage == CP_ACP) + { + dwFlags = WC_NO_BEST_FIT_CHARS; + } + else + { + dwFlags = 0; + } + + iStrLen = WideCharToMultiByte(uCodePage, + dwFlags, + pszSrcUnicodeString, + dwStringLen, + (LPSTR)pbufDstAnsiString->QueryPtr(), + (int)pbufDstAnsiString->QuerySize(), + NULL, + NULL); + if ((iStrLen == 0) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { + iStrLen = WideCharToMultiByte(uCodePage, + dwFlags, + pszSrcUnicodeString, + dwStringLen, + NULL, + 0, + NULL, + NULL); + if (iStrLen != 0) { + // add one just for the extra NULL + bTemp = pbufDstAnsiString->Resize(iStrLen + 1); + if (!bTemp) + { + iStrLen = 0; + } + else + { + iStrLen = WideCharToMultiByte(uCodePage, + dwFlags, + pszSrcUnicodeString, + dwStringLen, + (LPSTR)pbufDstAnsiString->QueryPtr(), + (int)pbufDstAnsiString->QuerySize(), + NULL, + NULL); + } + + } + } + + if (0 != iStrLen && + pbufDstAnsiString->Resize(iStrLen + 1)) + { + // insert a terminating NULL into buffer for the dwStringLen+1 in the case that the dwStringLen+1 was not a NULL. + ((CHAR*)pbufDstAnsiString->QueryPtr())[iStrLen] = '\0'; + } + else + { + iStrLen = -1; + } + + return iStrLen; +} + +// static +HRESULT +STRA::ConvertUnicodeToMultiByte( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __in BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen +) +{ + return ConvertUnicodeToCodePage( pszSrcUnicodeString, + pbufDstAnsiString, + dwStringLen, + CP_ACP ); +} + +// static +HRESULT +STRA::ConvertUnicodeToUTF8( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __in BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen +) +{ + return ConvertUnicodeToCodePage( pszSrcUnicodeString, + pbufDstAnsiString, + dwStringLen, + CP_UTF8 ); +} + +/*++ + +Routine Description: + + Removes leading and trailing whitespace + +--*/ + +VOID +STRA::Trim() +{ + PSTR pszString = QueryStr(); + DWORD cchNewLength = m_cchLen; + DWORD cchLeadingWhitespace = 0; + DWORD cchTempLength = 0; + + for (LONG ixString = m_cchLen - 1; ixString >= 0; ixString--) + { + if (isspace((unsigned char) pszString[ixString]) != 0) + { + pszString[ixString] = '\0'; + cchNewLength--; + } + else + { + break; + } + } + + cchTempLength = cchNewLength; + for (DWORD ixString = 0; ixString < cchTempLength; ixString++) + { + if (isspace((unsigned char) pszString[ixString]) != 0) + { + cchLeadingWhitespace++; + cchNewLength--; + } + else + { + break; + } + } + + if (cchNewLength == 0) + { + + Reset(); + } + else if (cchLeadingWhitespace > 0) + { + memmove(pszString, pszString + cchLeadingWhitespace, cchNewLength * sizeof(CHAR)); + pszString[cchNewLength] = '\0'; + } + + SyncWithBuffer(); +} + +/*++ + +Routine Description: + + Compares the string to the provided prefix to check for equality + +Arguments: + + pStraPrefix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if prefix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::StartsWith( + __in const STRA * pStraPrefix, + __in bool fIgnoreCase) const +{ + _ASSERTE( pStraPrefix != NULL ); + return StartsWith(pStraPrefix->QueryStr(), fIgnoreCase); +} + +/*++ + +Routine Description: + + Compares the string to the provided prefix to check for equality + +Arguments: + + straPrefix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if prefix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::StartsWith( + __in const STRA & straPrefix, + __in bool fIgnoreCase) const +{ + return StartsWith(straPrefix.QueryStr(), fIgnoreCase); +} + +/*++ + +Routine Description: + + Compares the string to the provided prefix to check for equality + +Arguments: + + pszPrefix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if prefix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::StartsWith( + __in PCSTR pszPrefix, + __in bool fIgnoreCase) const +{ + HRESULT hr = S_OK; + BOOL fMatch = FALSE; + size_t cchPrefix = 0; + + if (pszPrefix == NULL) + { + goto Finished; + } + + hr = StringCchLengthA( pszPrefix, + STRSAFE_MAX_CCH, + &cchPrefix ); + if (FAILED(hr)) + { + goto Finished; + } + + _ASSERTE( cchPrefix <= MAXDWORD ); + + if (cchPrefix > m_cchLen) + { + goto Finished; + } + + if( fIgnoreCase ) + { + fMatch = ( 0 == _strnicmp( QueryStr(), pszPrefix, cchPrefix ) ); + } + else + { + fMatch = ( 0 == strncmp( QueryStr(), pszPrefix, cchPrefix ) ); + } + + +Finished: + + return fMatch; +} + +/*++ + +Routine Description: + + Compares the string to the provided suffix to check for equality + +Arguments: + + pStraSuffix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if suffix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::EndsWith( + __in const STRA * pStraSuffix, + __in bool fIgnoreCase) const +{ + _ASSERTE( pStraSuffix != NULL ); + return EndsWith(pStraSuffix->QueryStr(), fIgnoreCase); +} + + +/*++ + +Routine Description: + + Compares the string to the provided suffix to check for equality + +Arguments: + + straSuffix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if suffix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::EndsWith( + __in const STRA & straSuffix, + __in bool fIgnoreCase) const +{ + return EndsWith(straSuffix.QueryStr(), fIgnoreCase); +} + + +/*++ + +Routine Description: + + Compares the string to the provided suffix to check for equality + +Arguments: + + pszSuffix - string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if suffix string matches with internal string, FALSE otherwise + +--*/ +BOOL +STRA::EndsWith( + __in PCSTR pszSuffix, + __in bool fIgnoreCase) const +{ + HRESULT hr = S_OK; + PSTR pszString = QueryStr(); + BOOL fMatch = FALSE; + size_t cchSuffix = 0; + ptrdiff_t ixOffset = 0; + + if (pszSuffix == NULL) + { + goto Finished; + } + + hr = StringCchLengthA( pszSuffix, + STRSAFE_MAX_CCH, + &cchSuffix ); + if (FAILED(hr)) + { + goto Finished; + } + + _ASSERTE( cchSuffix <= MAXDWORD ); + + if (cchSuffix > m_cchLen) + { + goto Finished; + } + + ixOffset = m_cchLen - cchSuffix; + _ASSERTE(ixOffset >= 0 && ixOffset <= MAXDWORD); + + if( fIgnoreCase ) + { + fMatch = ( 0 == _strnicmp( pszString + ixOffset, pszSuffix, cchSuffix ) ); + } + else + { + fMatch = ( 0 == strncmp( pszString + ixOffset, pszSuffix, cchSuffix ) ); + } + +Finished: + + return fMatch; +} + + +/*++ + +Routine Description: + + Searches the string for the first occurrence of the specified character. + +Arguments: + + charValue - character to find + dwStartIndex - the initial index. + +Return Value: + + The index for the first character occurence in the string. + + -1 if not found. + +--*/ +INT +STRA::IndexOf( + __in CHAR charValue, + __in DWORD dwStartIndex + ) const +{ + INT nIndex = -1; + + // Make sure that there are no buffer overruns. + if( dwStartIndex >= QueryCCH() ) + { + goto Finished; + } + + const CHAR* pChar = strchr( QueryStr() + dwStartIndex, charValue ); + + // Determine the index if found + if( pChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + + +/*++ + +Routine Description: + + Searches the string for the first occurrence of the specified substring. + +Arguments: + + pszValue - substring to find + dwStartIndex - initial index. + +Return Value: + + The index for the first character occurence in the string. + + -1 if not found. + +--*/ +INT +STRA::IndexOf( + __in PCSTR pszValue, + __in DWORD dwStartIndex + ) const +{ + HRESULT hr = S_OK; + INT nIndex = -1; + SIZE_T cchValue = 0; + + // Validate input parameters + if( dwStartIndex >= QueryCCH() || !pszValue ) + { + goto Finished; + } + + const CHAR* pChar = strstr( QueryStr() + dwStartIndex, pszValue ); + + // Determine the index if found + if( pChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + + +/*++ + +Routine Description: + + Searches the string for the last occurrence of the specified character. + +Arguments: + + charValue - character to find + dwStartIndex - initial index. + +Return Value: + + The index for the last character occurence in the string. + + -1 if not found. + +--*/ +INT +STRA::LastIndexOf( + __in CHAR charValue, + __in DWORD dwStartIndex + ) const +{ + INT nIndex = -1; + + // Make sure that there are no buffer overruns. + if( dwStartIndex >= QueryCCH() ) + { + goto Finished; + } + + const CHAR* pChar = strrchr( QueryStr() + dwStartIndex, charValue ); + + // Determine the index if found + if( pChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} diff --git a/src/AspNetCoreModuleV2/IISLib/stringa.h b/src/AspNetCoreModuleV2/IISLib/stringa.h new file mode 100644 index 0000000000..39737f4a69 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/stringa.h @@ -0,0 +1,515 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "buffer.h" +#include "macros.h" +#include + +class STRA +{ + +public: + + STRA( + VOID + ); + + STRA( + __inout_ecount(cchInit) CHAR* pbInit, + __in DWORD cchInit + ); + + BOOL + IsEmpty( + VOID + ) const; + + BOOL + Equals( + __in PCSTR pszRhs, + __in BOOL fIgnoreCase = FALSE + ) const; + + BOOL + Equals( + __in const STRA * pstrRhs, + __in BOOL fIgnoreCase = FALSE + ) const; + + BOOL + Equals( + __in const STRA & strRhs, + __in BOOL fIgnoreCase = FALSE + ) const; + + static + BOOL + Equals( + __in PCSTR pszLhs, + __in PCSTR pszRhs, + __in bool fIgnoreCase = false + ) + { + // Return FALSE if either or both strings are NULL. + if (!pszLhs || !pszRhs) return FALSE; + + if( fIgnoreCase ) + { + return ( 0 == _stricmp( pszLhs, pszRhs ) ); + } + + return ( 0 == strcmp( pszLhs, pszRhs ) ); + } + + VOID + Trim(); + + BOOL + StartsWith( + __in const STRA * pStraPrefix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + StartsWith( + __in const STRA & straPrefix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + StartsWith( + __in PCSTR pszPrefix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + EndsWith( + __in const STRA * pStraSuffix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + EndsWith( + __in const STRA & straSuffix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + EndsWith( + __in PCSTR pszSuffix, + __in bool fIgnoreCase = FALSE + ) const; + + INT + IndexOf( + __in CHAR charValue, + __in DWORD dwStartIndex = 0 + ) const; + + INT + IndexOf( + __in PCSTR pszValue, + __in DWORD dwStartIndex = 0 + ) const; + + INT + LastIndexOf( + __in CHAR charValue, + __in DWORD dwStartIndex = 0 + ) const; + + DWORD + QueryCB( + VOID + ) const; + + DWORD + QueryCCH( + VOID + ) const; + + DWORD + QuerySizeCCH( + VOID + ) const; + + DWORD + QuerySize( + VOID + ) const; + + __nullterminated + __bcount(this->m_cchLen) + CHAR * + QueryStr( + VOID + ) const; + + VOID + Reset( + VOID + ); + + HRESULT + Resize( + __in DWORD cchSize + ); + + HRESULT + SyncWithBuffer( + VOID + ); + + HRESULT + Copy( + __in PCSTR pszCopy + ); + + HRESULT + Copy( + __in_ecount(cbLen) + PCSTR pszCopy, + __in SIZE_T cbLen + ); + + HRESULT + Copy( + __in const STRA * pstrRhs + ); + + HRESULT + Copy( + __in const STRA & strRhs + ); + + HRESULT + CopyW( + __in PCWSTR pszCopyW + ); + + HRESULT + CopyW( + __in_ecount(cchLen) + PCWSTR pszCopyW, + __in SIZE_T cchLen, + __in UINT CodePage = CP_UTF8, + __in BOOL fFailIfNoTranslation = FALSE + ) + { + _ASSERTE( cchLen <= MAXDWORD ); + + return AuxAppendW( + pszCopyW, + static_cast(cchLen), + 0, + CodePage, + fFailIfNoTranslation + ); + } + + HRESULT + CopyWTruncate( + __in PCWSTR pszCopyWTruncate + ); + + HRESULT + CopyWTruncate( + __in_ecount(cchLen) + PCWSTR pszCopyWTruncate, + __in SIZE_T cchLen + ); + + HRESULT + Append( + __in PCSTR pszAppend + ); + + HRESULT + Append( + __in_ecount(cbLen) + PCSTR pszAppend, + __in SIZE_T cbLen + ); + + HRESULT + Append( + __in const STRA * pstrRhs + ); + + HRESULT + Append( + __in const STRA & strRhs + ); + + HRESULT + AppendW( + __in PCWSTR pszAppendW + ) + { + HRESULT hr; + size_t cchLen; + hr = StringCchLengthW( pszAppendW, + STRSAFE_MAX_CCH, + &cchLen ); + if ( FAILED( hr ) ) + { + return hr; + } + return AppendW( pszAppendW, cchLen ); + } + + HRESULT + AppendW( + __in_ecount(cchLen) + PCWSTR pszAppendW, + __in SIZE_T cchLen, + __in UINT CodePage = CP_UTF8, + __in BOOL fFailIfNoTranslation = FALSE + ) + { + _ASSERTE( cchLen <= MAXDWORD ); + if ( cchLen == 0 ) + { + return S_OK; + } + return AuxAppendW( + pszAppendW, + static_cast(cchLen), + QueryCB(), + CodePage, + fFailIfNoTranslation + ); + } + + HRESULT + AppendWTruncate( + __in PCWSTR pszAppendWTruncate + ); + + HRESULT + AppendWTruncate( + __in_ecount(cchLen) + PCWSTR pszAppendWTruncate, + __in SIZE_T cchLen + ); + + HRESULT + CopyToBuffer( + __out_bcount(*pcb) CHAR* pszBuffer, + __inout DWORD * pcb + ) const; + + HRESULT + SetLen( + __in DWORD cchLen + ); + + HRESULT + SafeSnprintf( + __in __format_string + PCSTR pszFormatString, + ... + ); + + HRESULT + SafeVsnprintf( + __in __format_string + PCSTR pszFormatString, + va_list argsList + ); + + HRESULT + Escape( + VOID + ); + + HRESULT + EscapeUtf8( + VOID + ); + + VOID + Unescape( + VOID + ); + + HRESULT + CopyWToUTF8Unescaped( + __in LPCWSTR cpchStr + ); + + HRESULT + CopyWToUTF8Unescaped( + __in_ecount(cch) + LPCWSTR cpchStr, + __in DWORD cch + ); + + HRESULT + CopyWToUTF8Escaped( + __in LPCWSTR cpchStr + ); + + HRESULT + CopyWToUTF8Escaped( + __in_ecount(cch) + LPCWSTR cpchStr, + __in DWORD cch + ); + +private: + + // + // Avoid C++ errors. This object should never go through a copy + // constructor, unintended cast or assignment. + // + STRA( const STRA &); + STRA & operator = (const STRA &); + + HRESULT + AuxAppend( + __in_ecount(cbLen) + LPCSTR pStr, + __in DWORD cbLen, + __in DWORD cbOffset + ); + + HRESULT + AuxAppendW( + __in_ecount(cchAppendW) + PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset, + __in UINT CodePage, + __in BOOL fFailIfNoTranslation + ) + { + DWORD dwFlags = 0; + + if( CP_ACP == CodePage ) + { + dwFlags = WC_NO_BEST_FIT_CHARS; + } + else if( fFailIfNoTranslation && CodePage == CP_UTF8 ) + { + // + // WC_ERR_INVALID_CHARS is only supported in Longhorn or greater. + // +#if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + dwFlags |= WC_ERR_INVALID_CHARS; +#else + UNREFERENCED_PARAMETER(fFailIfNoTranslation); +#endif + } + + return AuxAppendW( pszAppendW, + cchAppendW, + cbOffset, + CodePage, + fFailIfNoTranslation, + dwFlags ); + } + + HRESULT + AuxAppendW( + __in_ecount(cchAppendW) + PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset, + __in UINT CodePage, + __in BOOL fFailIfNoTranslation, + __in DWORD dwFlags + ); + + HRESULT + AuxAppendWTruncate( + __in_ecount(cchAppendW) + __in PCWSTR pszAppendW, + __in DWORD cchAppendW, + __in DWORD cbOffset + ); + + static + int + ConvertUnicodeToCodePage( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __inout BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen, + __in UINT uCodePage + ); + + static + HRESULT + ConvertUnicodeToMultiByte( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __in BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen + ); + + static + HRESULT + ConvertUnicodeToUTF8( + __in_ecount(dwStringLen) + LPCWSTR pszSrcUnicodeString, + __in BUFFER_T * pbufDstAnsiString, + __in DWORD dwStringLen + ); + + typedef bool (* PFN_F_SHOULD_ESCAPE)(BYTE ch); + + HRESULT + EscapeInternal( + PFN_F_SHOULD_ESCAPE pfnFShouldEscape + ); + + // + // Buffer with an inline buffer of 1, + // enough to hold null-terminating character. + // + BUFFER_T m_Buff; + DWORD m_cchLen; +}; + +inline +HRESULT +AppendToString( + ULONGLONG Number, + STRA & String +) +{ + // prefast complains Append requires input + // to be null terminated, so zero initialize + // and pass the size of the buffer minus one + // to _ui64toa_s + CHAR chNumber[32] = {0}; + if (_ui64toa_s(Number, + chNumber, + sizeof(chNumber) - sizeof(CHAR), + 10) != 0) + { + return E_INVALIDARG; + } + return String.Append(chNumber); +} + +template +CHAR* InitHelper(__out CHAR (&psz)[size]) +{ + psz[0] = '\0'; + return psz; +} + +// +// Heap operation reduction macros +// +#define STACK_STRA(name, size) CHAR __ach##name[size];\ + STRA name(InitHelper(__ach##name), sizeof(__ach##name)) + +#define INLINE_STRA(name, size) CHAR __ach##name[size];\ + STRA name; + +#define INLINE_STRA_INIT(name) name(InitHelper(__ach##name), sizeof(__ach##name)) diff --git a/src/AspNetCoreModuleV2/IISLib/stringu.cpp b/src/AspNetCoreModuleV2/IISLib/stringu.cpp new file mode 100644 index 0000000000..15da79a7fe --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/stringu.cpp @@ -0,0 +1,1271 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma warning (disable : 4267) + +#include "precomp.h" + +STRU::STRU( + VOID +) : m_cchLen( 0 ) +{ + *(QueryStr()) = L'\0'; +} + +STRU::STRU( + __inout_ecount(cchInit) WCHAR* pbInit, + __in DWORD cchInit +) : m_Buff( pbInit, cchInit * sizeof( WCHAR ) ), + m_cchLen( 0 ) +/*++ + Description: + + Used by STACK_STRU. Initially populates underlying buffer with pbInit. + + pbInit is not freed. + + Arguments: + + pbInit - initial memory to use + cchInit - count, in characters, of pbInit + + Returns: + + None. + +--*/ +{ + _ASSERTE( cchInit <= (MAXDWORD / sizeof( WCHAR )) ); + _ASSERTE( NULL != pbInit ); + _ASSERTE(cchInit > 0 ); + _ASSERTE(pbInit[0] == L'\0'); +} + +BOOL +STRU::IsEmpty( + VOID +) const +{ + return ( m_cchLen == 0 ); +} + +DWORD +STRU::QueryCB( + VOID +) const +// +// Returns the number of bytes in the string excluding the terminating NULL +// +{ + return m_cchLen * sizeof( WCHAR ); +} + +DWORD +STRU::QueryCCH( + VOID +) const +// +// Returns the number of characters in the string excluding the terminating NULL +// +{ + return m_cchLen; +} + +DWORD +STRU::QuerySizeCCH( + VOID +) const +// +// Returns size of the underlying storage buffer, in characters +// +{ + return m_Buff.QuerySize() / sizeof( WCHAR ); +} + +__nullterminated +__ecount(this->m_cchLen) +WCHAR* +STRU::QueryStr( + VOID +) const +// +// Return the string buffer +// +{ + return m_Buff.QueryPtr(); +} + +VOID +STRU::Reset( + VOID +) +// +// Resets the internal string to be NULL string. Buffer remains cached. +// +{ + _ASSERTE( QueryStr() != NULL ); + *(QueryStr()) = L'\0'; + m_cchLen = 0; +} + +HRESULT +STRU::Resize( + DWORD cchSize +) +{ + SIZE_T cbSize = cchSize * sizeof( WCHAR ); + if ( cbSize > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + if( !m_Buff.Resize( cbSize ) ) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT +STRU::SyncWithBuffer( + VOID +) +// +// Recalculate the length of the string, etc. because we've modified +// the buffer directly. +// +{ + HRESULT hr; + size_t size; + hr = StringCchLengthW( QueryStr(), + QuerySizeCCH(), + &size ); + if ( SUCCEEDED( hr ) ) + { + m_cchLen = static_cast(size); + } + return hr; +} + +HRESULT +STRU::Copy( + __in PCWSTR pszCopy +) +{ + HRESULT hr; + size_t cbStr; + + hr = StringCchLengthW( pszCopy, + STRSAFE_MAX_CCH, + &cbStr ); + if ( FAILED( hr ) ) + { + return hr; + } + + _ASSERTE( cbStr <= MAXDWORD ); + return Copy( pszCopy, + cbStr ); +} + +HRESULT +STRU::Copy( + __in_ecount(cchLen) + PCWSTR pszCopy, + SIZE_T cchLen +) +// +// Copy the contents of another string to this one +// +{ + return AuxAppend( pszCopy, + cchLen * sizeof(WCHAR), + 0); +} + +HRESULT +STRU::Copy( + __in const STRU * pstrRhs +) +{ + _ASSERTE( NULL != pstrRhs ); + return Copy( pstrRhs->QueryStr(), pstrRhs->QueryCCH() ); +} + +HRESULT +STRU::Copy( + __in const STRU & str +) +{ + return Copy( str.QueryStr(), str.QueryCCH() ); +} + +HRESULT +STRU::CopyAndExpandEnvironmentStrings( + __in PCWSTR pszSource +) +{ + HRESULT hr = S_OK; + DWORD cchDestReqBuff = 0; + + Reset(); + + cchDestReqBuff = ExpandEnvironmentStringsW( pszSource, + QueryStr(), + QuerySizeCCH() ); + if ( cchDestReqBuff == 0 ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + else if ( cchDestReqBuff > QuerySizeCCH() ) + { + hr = Resize( cchDestReqBuff ); + if ( FAILED( hr ) ) + { + goto Finished; + } + + cchDestReqBuff = ExpandEnvironmentStringsW( pszSource, + QueryStr(), + QuerySizeCCH() ); + + if ( cchDestReqBuff == 0 || cchDestReqBuff > QuerySizeCCH() ) + { + _ASSERTE( FALSE ); + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + } + + hr = SyncWithBuffer(); + if ( FAILED( hr ) ) + { + goto Finished; + } + +Finished: + + return hr; + +} + +HRESULT +STRU::CopyA( + __in PCSTR pszCopyA +) +{ + HRESULT hr; + size_t cbStr; + + hr = StringCbLengthA( pszCopyA, + STRSAFE_MAX_CCH, + &cbStr ); + if ( FAILED( hr ) ) + { + return hr; + } + + _ASSERTE( cbStr <= MAXDWORD ); + return CopyA( pszCopyA, + cbStr ); +} + +HRESULT +STRU::CopyA( + __in_bcount(cchLen) + PCSTR pszCopyA, + SIZE_T cchLen, + UINT CodePage /*= CP_UTF8*/ +) +{ + return AuxAppendA( + pszCopyA, + cchLen, + 0, + CodePage + ); +} + +HRESULT +STRU::Append( + __in PCWSTR pszAppend +) +{ + HRESULT hr; + size_t cbStr; + + hr = StringCchLengthW( pszAppend, + STRSAFE_MAX_CCH, + &cbStr ); + if ( FAILED( hr ) ) + { + return hr; + } + + _ASSERTE( cbStr <= MAXDWORD ); + return Append( pszAppend, + cbStr ); +} + +HRESULT +STRU::Append( + __in_ecount(cchLen) + PCWSTR pszAppend, + SIZE_T cchLen +) +// +// Append something to the end of the string +// +{ + if ( cchLen == 0 ) + { + return S_OK; + } + return AuxAppend( pszAppend, + cchLen * sizeof(WCHAR), + QueryCB() ); +} + +HRESULT +STRU::Append( + __in const STRU * pstrRhs +) +{ + _ASSERTE( NULL != pstrRhs ); + return Append( pstrRhs->QueryStr(), pstrRhs->QueryCCH() ); +} + +HRESULT +STRU::Append( + __in const STRU & strRhs +) +{ + return Append( strRhs.QueryStr(), strRhs.QueryCCH() ); +} + +HRESULT +STRU::AppendA( + __in PCSTR pszAppendA +) +{ + HRESULT hr; + size_t cbStr; + + hr = StringCbLengthA( pszAppendA, + STRSAFE_MAX_CCH, + &cbStr ); + if ( FAILED( hr ) ) + { + return hr; + } + + _ASSERTE( cbStr <= MAXDWORD ); + return AppendA( pszAppendA, + cbStr ); +} + +HRESULT +STRU::AppendA( + __in_bcount(cchLen) + PCSTR pszAppendA, + SIZE_T cchLen, + UINT CodePage /*= CP_UTF8*/ +) +{ + if ( cchLen == 0 ) + { + return S_OK; + } + return AuxAppendA( + pszAppendA, + cchLen, + QueryCB(), + CodePage + ); +} + +HRESULT +STRU::CopyToBuffer( + __out_bcount(*pcb) WCHAR* pszBuffer, + PDWORD pcb +) const +// +// Makes a copy of the stored string into the given buffer +// +{ + _ASSERTE( NULL != pszBuffer ); + _ASSERTE( NULL != pcb ); + + HRESULT hr = S_OK; + DWORD cbNeeded = QueryCB() + sizeof( WCHAR ); + + if( *pcb < cbNeeded ) + { + hr = HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ); + goto Finished; + } + + // + // BUGBUG: StringCchCopy? + // + memcpy( pszBuffer, QueryStr(), cbNeeded ); + +Finished: + + *pcb = cbNeeded; + + return hr; +} + +HRESULT +STRU::SetLen( + __in DWORD cchLen +) +/*++ + * +Routine Description: + + Set the length of the string and null terminate, if there + is sufficient buffer already allocated. Will not reallocate. + +Arguments: + + cchLen - The number of characters in the new string. + +Return Value: + + HRESULT + +--*/ +{ + if( cchLen >= QuerySizeCCH() ) + { + return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + } + + *( QueryStr() + cchLen ) = L'\0'; + m_cchLen = cchLen; + + return S_OK; +} + +HRESULT +STRU::SafeSnwprintf( + __in PCWSTR pwszFormatString, + ... +) +/*++ + +Routine Description: + + Writes to a STRU, growing it as needed. It arbitrarily caps growth at 64k chars. + +Arguments: + + pwszFormatString - printf format + ... - printf args + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + va_list argsList; + va_start( argsList, pwszFormatString ); + + hr = SafeVsnwprintf(pwszFormatString, argsList); + + va_end( argsList ); + return hr; +} + +HRESULT +STRU::SafeVsnwprintf( + __in PCWSTR pwszFormatString, + va_list argsList +) +/*++ + +Routine Description: + + Writes to a STRU, growing it as needed. It arbitrarily caps growth at 64k chars. + +Arguments: + + pwszFormatString - printf format + argsList - printf va_list + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + int cchOutput; + int cchNeeded; + + // + // Format the incoming message using vsnprintf() + // so that the overflows are captured + // + cchOutput = _vsnwprintf_s( + QueryStr(), + QuerySizeCCH(), + QuerySizeCCH() - 1, + pwszFormatString, + argsList + ); + + if( cchOutput == -1 ) + { + // + // Couldn't fit this in the original STRU size. + // + cchNeeded = _vscwprintf( pwszFormatString, argsList ); + if( cchNeeded > 64 * 1024 ) + { + // + // If we're trying to produce a string > 64k chars, then + // there is probably a problem + // + hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); + goto Finished; + } + + // + // _vscprintf doesn't include terminating null character + // + cchNeeded++; + + hr = Resize( cchNeeded ); + if( FAILED( hr ) ) + { + goto Finished; + } + + cchOutput = _vsnwprintf_s( + QueryStr(), + QuerySizeCCH(), + QuerySizeCCH() - 1, + pwszFormatString, + argsList + ); + if( -1 == cchOutput ) + { + // + // This should never happen, cause we should already have correctly sized memory + // + _ASSERTE( FALSE ); + + hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); + goto Finished; + } + } + + // + // always null terminate at the last WCHAR + // + QueryStr()[ QuerySizeCCH() - 1 ] = L'\0'; + + // + // we directly touched the buffer - therefore: + // + hr = SyncWithBuffer(); + if ( FAILED( hr ) ) + { + goto Finished; + } + +Finished: + + if( FAILED( hr ) ) + { + Reset(); + } + + return hr; +} + +HRESULT +STRU::AuxAppend( + __in_ecount(cNumStrings) + PCWSTR const rgpszStrings[], + SIZE_T cNumStrings +) +/*++ + +Routine Description: + + Appends an array of strings of length cNumStrings + +Arguments: + + rgStrings - The array of strings to be appened + cNumStrings - The count of String + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + size_t cbStringsTotal = sizeof( WCHAR ); // Account for null-terminator + + // + // Compute total size of the string. + // Resize internal buffer + // Copy each array element one by one to backing buffer + // Update backing buffer string length + // + for ( SIZE_T i = 0; i < cNumStrings; i++ ) + { + _ASSERTE( rgpszStrings[ i ] != NULL ); + if ( NULL == rgpszStrings[ i ] ) + { + return E_INVALIDARG; + } + + size_t cbString = 0; + + hr = StringCbLengthW( rgpszStrings[ i ], + STRSAFE_MAX_CCH * sizeof( WCHAR ), + &cbString ); + if ( FAILED( hr ) ) + { + return hr; + } + + cbStringsTotal += cbString; + + if ( cbStringsTotal > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + } + + size_t cbBufSizeRequired = QueryCB() + cbStringsTotal; + if ( cbBufSizeRequired > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + + if( m_Buff.QuerySize() < cbBufSizeRequired ) + { + if( !m_Buff.Resize( cbBufSizeRequired ) ) + { + return E_OUTOFMEMORY; + } + } + + STRSAFE_LPWSTR pszStringEnd = QueryStr() + QueryCCH(); + size_t cchRemaining = QuerySizeCCH() - QueryCCH(); + for ( SIZE_T i = 0; i < cNumStrings; i++ ) + { + hr = StringCchCopyExW( pszStringEnd, // pszDest + cchRemaining, // cchDest + rgpszStrings[ i ], // pszSrc + &pszStringEnd, // ppszDestEnd + &cchRemaining, // pcchRemaining + 0 ); // dwFlags + if ( FAILED( hr ) ) + { + _ASSERTE( FALSE ); + HRESULT hr2 = SyncWithBuffer(); + if ( FAILED( hr2 ) ) + { + return hr2; + } + return hr; + } + } + + m_cchLen = static_cast< DWORD >( cbBufSizeRequired ) / sizeof( WCHAR ) - 1; + + return S_OK; +} + +HRESULT +STRU::AuxAppend( + __in_bcount(cbStr) + const WCHAR* pStr, + SIZE_T cbStr, + DWORD cbOffset +) +/*++ + +Routine Description: + + Appends to the string starting at the (byte) offset cbOffset. + +Arguments: + + pStr - A unicode string to be appended + cbStr - Length, in bytes, of pStr + cbOffset - Offset, in bytes, at which to begin the append + +Return Value: + + HRESULT + +--*/ +{ + _ASSERTE( NULL != pStr ); + _ASSERTE( 0 == cbStr % sizeof( WCHAR ) ); + _ASSERTE( cbOffset <= QueryCB() ); + _ASSERTE( 0 == cbOffset % sizeof( WCHAR ) ); + + ULONGLONG cb64NewSize = (ULONGLONG)cbOffset + cbStr + sizeof( WCHAR ); + if( cb64NewSize > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + + if( m_Buff.QuerySize() < cb64NewSize ) + { + if( !m_Buff.Resize( static_cast(cb64NewSize) ) ) + { + return E_OUTOFMEMORY; + } + } + + memcpy( reinterpret_cast(m_Buff.QueryPtr()) + cbOffset, pStr, cbStr ); + + m_cchLen = (static_cast(cbStr) + cbOffset) / sizeof(WCHAR); + + *( QueryStr() + m_cchLen ) = L'\0'; + + return S_OK; +} + +HRESULT +STRU::AuxAppendA( + __in_bcount(cbStr) + const CHAR* pStr, + SIZE_T cbStr, + DWORD cbOffset, + UINT CodePage +) +/*++ + +Routine Description: + + Convert and append an ANSI string to the string starting at + the (byte) offset cbOffset + +Arguments: + + pStr - An ANSI string to be appended + cbStr - Length, in bytes, of pStr + cbOffset - Offset, in bytes, at which to begin the append + CodePage - code page to use for conversion + +Return Value: + + HRESULT + +--*/ +{ + WCHAR* pszBuffer; + DWORD cchBuffer; + DWORD cchCharsCopied = 0; + + _ASSERTE( NULL != pStr ); + _ASSERTE( cbOffset <= QueryCB() ); + _ASSERTE( 0 == cbOffset % sizeof( WCHAR ) ); + + if ( NULL == pStr ) + { + return E_INVALIDARG; + } + + if( 0 == cbStr ) + { + return S_OK; + } + + // + // Only resize when we have to. When we do resize, we tack on + // some extra space to avoid extra reallocations. + // + if( m_Buff.QuerySize() < (ULONGLONG)cbOffset + (cbStr * sizeof( WCHAR )) + sizeof(WCHAR) ) + { + ULONGLONG cb64NewSize = (ULONGLONG)( cbOffset + cbStr * sizeof(WCHAR) + sizeof( WCHAR ) ); + + // + // Check for the arithmetic overflow + // + if( cb64NewSize > MAXDWORD ) + { + return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); + } + + if( !m_Buff.Resize( static_cast(cb64NewSize) ) ) + { + return E_OUTOFMEMORY; + } + } + + pszBuffer = reinterpret_cast(reinterpret_cast(m_Buff.QueryPtr()) + cbOffset); + cchBuffer = ( m_Buff.QuerySize() - cbOffset - sizeof( WCHAR ) ) / sizeof( WCHAR ); + + cchCharsCopied = MultiByteToWideChar( + CodePage, + MB_ERR_INVALID_CHARS, + pStr, + static_cast(cbStr), + pszBuffer, + cchBuffer + ); + if( 0 == cchCharsCopied ) + { + return HRESULT_FROM_WIN32( GetLastError() ); + } + + // + // set the new length + // + m_cchLen = cchCharsCopied + cbOffset/sizeof(WCHAR); + + // + // Must be less than, cause still need to add NULL + // + _ASSERTE( m_cchLen < QuerySizeCCH() ); + + // + // append NULL character + // + *(QueryStr() + m_cchLen) = L'\0'; + + return S_OK; +} + + +/*++ + +Routine Description: + + Removes leading and trailing whitespace + +--*/ + +VOID +STRU::Trim() +{ + PWSTR pwszString = QueryStr(); + DWORD cchNewLength = m_cchLen; + DWORD cchLeadingWhitespace = 0; + DWORD cchTempLength = 0; + + for (LONG ixString = m_cchLen - 1; ixString >= 0; ixString--) + { + if (iswspace(pwszString[ixString]) != 0) + { + pwszString[ixString] = L'\0'; + cchNewLength--; + } + else + { + break; + } + } + + cchTempLength = cchNewLength; + for (DWORD ixString = 0; ixString < cchTempLength; ixString++) + { + if (iswspace(pwszString[ixString]) != 0) + { + cchLeadingWhitespace++; + cchNewLength--; + } + else + { + break; + } + } + + if (cchNewLength == 0) + { + + Reset(); + } + else if (cchLeadingWhitespace > 0) + { + memmove(pwszString, pwszString + cchLeadingWhitespace, cchNewLength * sizeof(WCHAR)); + pwszString[cchNewLength] = L'\0'; + } + + SyncWithBuffer(); +} + +/*++ + +Routine Description: + + Compares the string to the provided prefix to check for equality + +Arguments: + + pwszPrefix - wide char string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if prefix string matches with internal string, FALSE otherwise + +--*/ + +BOOL +STRU::StartsWith( + __in PCWSTR pwszPrefix, + __in bool fIgnoreCase) const +{ + HRESULT hr = S_OK; + BOOL fMatch = FALSE; + size_t cchPrefix = 0; + + if (pwszPrefix == NULL) + { + goto Finished; + } + + hr = StringCchLengthW( pwszPrefix, + STRSAFE_MAX_CCH, + &cchPrefix ); + if (FAILED(hr)) + { + goto Finished; + } + + _ASSERTE( cchPrefix <= MAXDWORD ); + + if (cchPrefix > m_cchLen) + { + goto Finished; + } + + #if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + + fMatch = ( CSTR_EQUAL == CompareStringOrdinal( QueryStr(), + cchPrefix, + pwszPrefix, + cchPrefix, + fIgnoreCase ) ); + #else + + if( fIgnoreCase ) + { + fMatch = ( 0 == _wcsnicmp( QueryStr(), pwszPrefix, cchPrefix ) ); + } + else + { + fMatch = ( 0 == wcsncmp( QueryStr(), pwszPrefix, cchPrefix ) ); + } + + #endif + +Finished: + + return fMatch; +} + +/*++ + +Routine Description: + + Compares the string to the provided suffix to check for equality + +Arguments: + + pwszSuffix - wide char string to compare with + fIgnoreCase - indicates whether the string comparison should be case-sensitive + +Return Value: + + TRUE if suffix string matches with internal string, FALSE otherwise + +--*/ + + +BOOL +STRU::EndsWith( + __in PCWSTR pwszSuffix, + __in bool fIgnoreCase) const +{ + HRESULT hr = S_OK; + PWSTR pwszString = QueryStr(); + BOOL fMatch = FALSE; + size_t cchSuffix = 0; + ptrdiff_t ixOffset = 0; + + if (pwszSuffix == NULL) + { + goto Finished; + } + + hr = StringCchLengthW( pwszSuffix, + STRSAFE_MAX_CCH, + &cchSuffix ); + if (FAILED(hr)) + { + goto Finished; + } + + _ASSERTE( cchSuffix <= MAXDWORD ); + + if (cchSuffix > m_cchLen) + { + goto Finished; + } + + ixOffset = m_cchLen - cchSuffix; + _ASSERTE(ixOffset >= 0 && ixOffset <= MAXDWORD); + + #if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + + fMatch = ( CSTR_EQUAL == CompareStringOrdinal( pwszString + ixOffset, + cchSuffix, + pwszSuffix, + cchSuffix, + fIgnoreCase ) ); + #else + + if( fIgnoreCase ) + { + fMatch = ( 0 == _wcsnicmp( pwszString + ixOffset, pwszSuffix, cchSuffix ) ); + } + else + { + fMatch = ( 0 == wcsncmp( pwszString + ixOffset, pwszSuffix, cchSuffix ) ); + } + + #endif + +Finished: + + return fMatch; +} + +/*++ + +Routine Description: + + Searches the string for the first occurrence of the specified character. + +Arguments: + + charValue - character to find + dwStartIndex - the initial index. + +Return Value: + + The index for the first character occurence in the string. + + -1 if not found. + +--*/ +INT +STRU::IndexOf( + __in WCHAR charValue, + __in DWORD dwStartIndex + ) const +{ + INT nIndex = -1; + + // Make sure that there are no buffer overruns. + if( dwStartIndex >= QueryCCH() ) + { + goto Finished; + } + + const WCHAR* pwChar = wcschr( QueryStr() + dwStartIndex, charValue ); + + // Determine the index if found + if( pwChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pwChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + + +/*++ + +Routine Description: + + Searches the string for the first occurrence of the specified substring. + +Arguments: + + pwszValue - substring to find + dwStartIndex - initial index. + +Return Value: + + The index for the first character occurence in the string. + + -1 if not found. + +--*/ +INT +STRU::IndexOf( + __in PCWSTR pwszValue, + __in DWORD dwStartIndex + ) const +{ + HRESULT hr = S_OK; + INT nIndex = -1; + SIZE_T cchValue = 0; + + // Validate input parameters + if( dwStartIndex >= QueryCCH() || !pwszValue ) + { + goto Finished; + } + + const WCHAR* pwChar = wcsstr( QueryStr() + dwStartIndex, pwszValue ); + + // Determine the index if found + if( pwChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pwChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + + +/*++ + +Routine Description: + + Searches the string for the last occurrence of the specified character. + +Arguments: + + charValue - character to find + dwStartIndex - initial index. + +Return Value: + + The index for the last character occurence in the string. + + -1 if not found. + +--*/ +INT +STRU::LastIndexOf( + __in WCHAR charValue, + __in DWORD dwStartIndex + ) const +{ + INT nIndex = -1; + + // Make sure that there are no buffer overruns. + if( dwStartIndex >= QueryCCH() ) + { + goto Finished; + } + + const WCHAR* pwChar = wcsrchr( QueryStr() + dwStartIndex, charValue ); + + // Determine the index if found + if( pwChar ) + { + // nIndex will be set to -1 on failure. + (VOID)SizeTToInt( pwChar - QueryStr(), &nIndex ); + } + +Finished: + + return nIndex; +} + +//static +HRESULT +STRU::ExpandEnvironmentVariables( + __in PCWSTR pszString, + __out STRU * pstrExpandedString + ) +/*++ + +Routine Description: + + Expand the environment variables in a string + +Arguments: + + pszString - String with environment variables to expand + pstrExpandedString - Receives expanded string on success + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + DWORD cchNewSize = 0; + + if ( pszString == NULL || + pstrExpandedString == NULL ) + { + DBG_ASSERT( FALSE ); + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Exit; + } + + cchNewSize = ExpandEnvironmentStrings( pszString, + pstrExpandedString->QueryStr(), + pstrExpandedString->QuerySizeCCH() ); + if ( cchNewSize == 0 ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Exit; + } + + if ( cchNewSize > pstrExpandedString->QuerySizeCCH() ) + { + hr = pstrExpandedString->Resize( + ( cchNewSize + 1 ) * sizeof( WCHAR ) + ); + if ( FAILED( hr ) ) + { + goto Exit; + } + + cchNewSize = ExpandEnvironmentStrings( + pszString, + pstrExpandedString->QueryStr(), + pstrExpandedString->QuerySizeCCH() + ); + + if ( cchNewSize == 0 || + cchNewSize > pstrExpandedString->QuerySizeCCH() ) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Exit; + } + } + + pstrExpandedString->SyncWithBuffer(); + + hr = S_OK; + +Exit: + + return hr; +} + +#pragma warning(default:4267) diff --git a/src/AspNetCoreModuleV2/IISLib/stringu.h b/src/AspNetCoreModuleV2/IISLib/stringu.h new file mode 100644 index 0000000000..6f27c5421d --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/stringu.h @@ -0,0 +1,427 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "buffer.h" +#include + +class STRU +{ + +public: + + STRU( + VOID + ); + + STRU( + __inout_ecount(cchInit) WCHAR* pbInit, + __in DWORD cchInit + ); + + BOOL + IsEmpty( + VOID + ) const; + + BOOL + Equals( + __in const STRU * pstrRhs, + __in BOOL fIgnoreCase = FALSE + ) const + { + _ASSERTE( pstrRhs != NULL ); + return Equals( pstrRhs->QueryStr(), fIgnoreCase ); + } + + BOOL + Equals( + __in const STRU & strRhs, + __in BOOL fIgnoreCase = FALSE + ) const + { + return Equals( strRhs.QueryStr(), fIgnoreCase ); + } + + BOOL + Equals( + __in PCWSTR pszRhs, + __in BOOL fIgnoreCase = FALSE + ) const + { + _ASSERTE( NULL != pszRhs ); + if ( NULL == pszRhs ) + { + return FALSE; + } + + #if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + + return ( CSTR_EQUAL == CompareStringOrdinal( QueryStr(), + QueryCCH(), + pszRhs, + -1, + fIgnoreCase ) ); + #else + + if( fIgnoreCase ) + { + return ( 0 == _wcsicmp( QueryStr(), pszRhs ) ); + } + return ( 0 == wcscmp( QueryStr(), pszRhs ) ); + + #endif + } + + + static + BOOL + Equals( + __in PCWSTR pwszLhs, + __in PCWSTR pwszRhs, + __in bool fIgnoreCase = false + ) + { + // Return FALSE if either or both strings are NULL. + if (!pwszLhs || !pwszRhs) return FALSE; + + // + // This method performs a ordinal string comparison when OS is Vista or + // greater and a culture sensitive comparison if not (XP). This is + // consistent with the existing Equals implementation (see above). + // +#if defined( NTDDI_VERSION ) && NTDDI_VERSION >= NTDDI_LONGHORN + + return ( CSTR_EQUAL == CompareStringOrdinal( pwszLhs, + -1, + pwszRhs, + -1, + fIgnoreCase ) ); +#else + + if( fIgnoreCase ) + { + return ( 0 == _wcsicmp( pwszLhs, pwszRhs ) ); + } + else + { + return ( 0 == wcscmp( pwszLhs, pwszRhs ) ); + } + +#endif + } + + VOID + Trim(); + + BOOL + StartsWith( + __in const STRU * pStruPrefix, + __in bool fIgnoreCase = FALSE + ) const + { + _ASSERTE( pStruPrefix != NULL ); + return StartsWith( pStruPrefix->QueryStr(), fIgnoreCase ); + } + + BOOL + StartsWith( + __in const STRU & struPrefix, + __in bool fIgnoreCase = FALSE + ) const + { + return StartsWith( struPrefix.QueryStr(), fIgnoreCase ); + } + + BOOL + StartsWith( + __in PCWSTR pwszPrefix, + __in bool fIgnoreCase = FALSE + ) const; + + BOOL + EndsWith( + __in const STRU * pStruSuffix, + __in bool fIgnoreCase = FALSE + ) const + { + _ASSERTE( pStruSuffix != NULL ); + return EndsWith( pStruSuffix->QueryStr(), fIgnoreCase ); + } + + BOOL + EndsWith( + __in const STRU & struSuffix, + __in bool fIgnoreCase = FALSE + ) const + { + return EndsWith( struSuffix.QueryStr(), fIgnoreCase ); + } + + BOOL + EndsWith( + __in PCWSTR pwszSuffix, + __in bool fIgnoreCase = FALSE + ) const; + + INT + IndexOf( + __in WCHAR charValue, + __in DWORD dwStartIndex = 0 + ) const; + + INT + IndexOf( + __in PCWSTR pwszValue, + __in DWORD dwStartIndex = 0 + ) const; + + INT + LastIndexOf( + __in WCHAR charValue, + __in DWORD dwStartIndex = 0 + ) const; + + DWORD + QueryCB( + VOID + ) const; + + DWORD + QueryCCH( + VOID + ) const; + + DWORD + QuerySizeCCH( + VOID + ) const; + + __nullterminated + __ecount(this->m_cchLen) + WCHAR* + QueryStr( + VOID + ) const; + + VOID + Reset( + VOID + ); + + HRESULT + Resize( + DWORD cchSize + ); + + HRESULT + SyncWithBuffer( + VOID + ); + + template + HRESULT + Copy( + __in PCWSTR const (&rgpszStrings)[size] + ) + // + // Copies an array of strings declared as stack array. For example: + // + // LPCWSTR rgExample[] { L"one", L"two" }; + // hr = str.Copy( rgExample ); + // + { + Reset(); + + return AuxAppend( rgpszStrings, _countof( rgpszStrings ) ); + } + + HRESULT + Copy( + __in PCWSTR pszCopy + ); + + HRESULT + Copy( + __in_ecount(cchLen) + PCWSTR pszCopy, + SIZE_T cchLen + ); + + HRESULT + Copy( + __in const STRU * pstrRhs + ); + + HRESULT + Copy( + __in const STRU & str + ); + + HRESULT + CopyAndExpandEnvironmentStrings( + __in PCWSTR pszSource + ); + + HRESULT + CopyA( + __in PCSTR pszCopyA + ); + + HRESULT + CopyA( + __in_bcount(cchLen) + PCSTR pszCopyA, + SIZE_T cchLen, + UINT CodePage = CP_UTF8 + ); + + template + HRESULT + Append( + __in PCWSTR const (&rgpszStrings)[size] + ) + // + // Appends an array of strings declared as stack array. For example: + // + // LPCWSTR rgExample[] { L"one", L"two" }; + // hr = str.Append( rgExample ); + // + { + return AuxAppend( rgpszStrings, _countof( rgpszStrings ) ); + } + + HRESULT + Append( + __in PCWSTR pszAppend + ); + + HRESULT + Append( + __in_ecount(cchLen) + PCWSTR pszAppend, + SIZE_T cchLen + ); + + HRESULT + Append( + __in const STRU * pstrRhs + ); + + HRESULT + Append( + __in const STRU & strRhs + ); + + HRESULT + AppendA( + __in PCSTR pszAppendA + ); + + HRESULT + AppendA( + __in_bcount(cchLen) + PCSTR pszAppendA, + SIZE_T cchLen, + UINT CodePage = CP_UTF8 + ); + + HRESULT + CopyToBuffer( + __out_bcount(*pcb) WCHAR* pszBuffer, + PDWORD pcb + ) const; + + HRESULT + SetLen( + __in DWORD cchLen + ); + + HRESULT + SafeSnwprintf( + __in PCWSTR pwszFormatString, + ... + ); + + HRESULT + SafeVsnwprintf( + __in PCWSTR pwszFormatString, + va_list argsList + ); + + static + HRESULT ExpandEnvironmentVariables( + __in PCWSTR pszString, + __out STRU * pstrExpandedString + ); + +private: + + // + // Avoid C++ errors. This object should never go through a copy + // constructor, unintended cast or assignment. + // + STRU( const STRU & ); + STRU & operator = ( const STRU & ); + + HRESULT + AuxAppend( + __in_ecount(cNumStrings) + PCWSTR const rgpszStrings[], + SIZE_T cNumStrings + ); + + HRESULT + AuxAppend( + __in_bcount(cbStr) + const WCHAR* pStr, + SIZE_T cbStr, + DWORD cbOffset + ); + + HRESULT + AuxAppendA( + __in_bcount(cbStr) + const CHAR* pStr, + SIZE_T cbStr, + DWORD cbOffset, + UINT CodePage + ); + + // + // Buffer with an inline buffer of 1, + // enough to hold null-terminating character. + // + BUFFER_T m_Buff; + DWORD m_cchLen; +}; + +// +// Helps to initialize an external buffer before +// constructing the STRU object. +// +template +WCHAR* InitHelper(__out WCHAR (&psz)[size]) +{ + psz[0] = L'\0'; + return psz; +} + +// +// Heap operation reduction macros +// +#define STACK_STRU(name, size) WCHAR __ach##name[size];\ + STRU name(InitHelper(__ach##name), sizeof(__ach##name)/sizeof(*__ach##name)) + +#define INLINE_STRU(name, size) WCHAR __ach##name[size];\ + STRU name; + +#define INLINE_STRU_INIT(name) name(InitHelper(__ach##name), sizeof(__ach##name)/sizeof(*__ach##name)) + + +HRESULT +MakePathCanonicalizationProof( + IN PCWSTR pszName, + OUT STRU * pstrPath +); diff --git a/src/AspNetCoreModuleV2/IISLib/tracelog.c b/src/AspNetCoreModuleV2/IISLib/tracelog.c new file mode 100644 index 0000000000..f7b2da5e43 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/tracelog.c @@ -0,0 +1,235 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include +#include "pudebug.h" +#include "tracelog.h" +#include + + +#define ALLOC_MEM(cb) (PVOID)LocalAlloc( LPTR, (cb) ) +#define FREE_MEM(ptr) (VOID)LocalFree( (HLOCAL)(ptr) ) + + + +PTRACE_LOG +CreateTraceLog( + IN LONG LogSize, + IN LONG ExtraBytesInHeader, + IN LONG EntrySize + ) +/*++ + +Routine Description: + + Creates a new (empty) trace log buffer. + +Arguments: + + LogSize - The number of entries in the log. + + ExtraBytesInHeader - The number of extra bytes to include in the + log header. This is useful for adding application-specific + data to the log. + + EntrySize - The size (in bytes) of each entry. + +Return Value: + + PTRACE_LOG - Pointer to the newly created log if successful, + NULL otherwise. + +--*/ +{ + + ULONG ulTotalSize = 0; + ULONG ulLogSize = 0; + ULONG ulEntrySize = 0; + ULONG ulTmpResult = 0; + ULONG ulExtraBytesInHeader = 0; + PTRACE_LOG log = NULL; + HRESULT hr = S_OK; + + // + // Sanity check the parameters. + // + + //DBG_ASSERT( LogSize > 0 ); + //DBG_ASSERT( EntrySize > 0 ); + //DBG_ASSERT( ExtraBytesInHeader >= 0 ); + //DBG_ASSERT( ( EntrySize & 3 ) == 0 ); + + // + // converting to unsigned long. Since all these values are positive + // so its safe to cast them to their unsigned equivalent directly. + // + ulLogSize = (ULONG) LogSize; + ulEntrySize = (ULONG) EntrySize; + ulExtraBytesInHeader = (ULONG) ExtraBytesInHeader; + + // + // Check if the multiplication operation will overflow a LONG + // ulTotalSize = LogSize * EntrySize; + // + hr = ULongMult( ulLogSize, ulEntrySize, &ulTotalSize ); + if ( FAILED(hr) ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return NULL; + } + + // + // check for overflow in addition operation. + // ulTmpResult = sizeof(TRACE_LOG) + ulExtraBytesInHeader + // + hr = ULongAdd( (ULONG) sizeof(TRACE_LOG), ulExtraBytesInHeader, &ulTmpResult ); + if ( FAILED(hr) ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return NULL; + } + + // + // check for overflow in addition operation. + // ulTotalSize = ulTotalSize + ulTmpResult; + // + hr = ULongAdd( ulTmpResult, ulTotalSize, &ulTotalSize ); + if ( FAILED(hr) ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return NULL; + } + + if ( ulTotalSize > (ULONG) 0x7FFFFFFF ) + { + SetLastError( ERROR_ARITHMETIC_OVERFLOW ); + return NULL; + } + + // + // Allocate & initialize the log structure. + // + + log = (PTRACE_LOG)ALLOC_MEM( ulTotalSize ); + + // + // Initialize it. + // + + if( log != NULL ) { + + RtlZeroMemory( log, ulTotalSize ); + + log->Signature = TRACE_LOG_SIGNATURE; + log->LogSize = LogSize; + log->NextEntry = -1; + log->EntrySize = EntrySize; + log->LogBuffer = (PUCHAR)( log + 1 ) + ExtraBytesInHeader; + } + + return log; + +} // CreateTraceLog + + +VOID +DestroyTraceLog( + IN PTRACE_LOG Log + ) +/*++ + +Routine Description: + + Destroys a trace log buffer created with CreateTraceLog(). + +Arguments: + + Log - The trace log buffer to destroy. + +Return Value: + + None. + +--*/ +{ + if ( Log != NULL ) { + //DBG_ASSERT( Log->Signature == TRACE_LOG_SIGNATURE ); + + Log->Signature = TRACE_LOG_SIGNATURE_X; + FREE_MEM( Log ); + } + +} // DestroyTraceLog + + +LONG +WriteTraceLog( + IN PTRACE_LOG Log, + IN PVOID Entry + ) +/*++ + +Routine Description: + + Writes a new entry to the specified trace log. + +Arguments: + + Log - The log to write to. + + Entry - Pointer to the data to write. This buffer is assumed to be + Log->EntrySize bytes long. + +Return Value: + + Index of entry in log. This is useful for correlating the output + of !inetdbg.ref to a particular point in the output debug stream + +--*/ +{ + + PUCHAR target; + ULONG index; + + //DBG_ASSERT( Log != NULL ); + //DBG_ASSERT( Log->Signature == TRACE_LOG_SIGNATURE ); + //DBG_ASSERT( Entry != NULL ); + + // + // Find the next slot, copy the entry to the slot. + // + + index = ( (ULONG) InterlockedIncrement( &Log->NextEntry ) ) % (ULONG) Log->LogSize; + + //DBG_ASSERT( index < (ULONG) Log->LogSize ); + + target = Log->LogBuffer + ( index * Log->EntrySize ); + + RtlCopyMemory( + target, + Entry, + Log->EntrySize + ); + + return index; +} // WriteTraceLog + + +VOID +ResetTraceLog( + IN PTRACE_LOG Log + ) +{ + + //DBG_ASSERT( Log != NULL ); + //DBG_ASSERT( Log->Signature == TRACE_LOG_SIGNATURE ); + + RtlZeroMemory( + ( Log + 1 ), + Log->LogSize * Log->EntrySize + ); + + Log->NextEntry = -1; + +} // ResetTraceLog + diff --git a/src/AspNetCoreModuleV2/IISLib/tracelog.h b/src/AspNetCoreModuleV2/IISLib/tracelog.h new file mode 100644 index 0000000000..ed34bcffc9 --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/tracelog.h @@ -0,0 +1,105 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#ifndef _TRACELOG_H_ +#define _TRACELOG_H_ + + +#if defined(__cplusplus) +extern "C" { +#endif // __cplusplus + + +typedef struct _TRACE_LOG { + + // + // Signature. + // + + LONG Signature; + + // + // The total number of entries available in the log. + // + + LONG LogSize; + + // + // The index of the next entry to use. + // + + LONG NextEntry; + + // + // The byte size of each entry. + // + + LONG EntrySize; + + // + // Pointer to the start of the circular buffer. + // + + PUCHAR LogBuffer; + + // + // The extra header bytes and actual log entries go here. + // + // BYTE ExtraHeaderBytes[ExtraBytesInHeader]; + // BYTE Entries[LogSize][EntrySize]; + // + +} TRACE_LOG, *PTRACE_LOG; + + +// +// Log header signature. +// + +#define TRACE_LOG_SIGNATURE ((DWORD)'gOlT') +#define TRACE_LOG_SIGNATURE_X ((DWORD)'golX') + + +// +// This macro maps a TRACE_LOG pointer to a pointer to the 'extra' +// data associated with the log. +// + +#define TRACE_LOG_TO_EXTRA_DATA(log) (PVOID)( (log) + 1 ) + + +// +// Manipulators. +// + +PTRACE_LOG +CreateTraceLog( + IN LONG LogSize, + IN LONG ExtraBytesInHeader, + IN LONG EntrySize + ); + +VOID +DestroyTraceLog( + IN PTRACE_LOG Log + ); + +LONG +WriteTraceLog( + IN PTRACE_LOG Log, + IN PVOID Entry + ); + +VOID +ResetTraceLog( + IN PTRACE_LOG Log + ); + + +#if defined(__cplusplus) +} // extern "C" +#endif // __cplusplus + + +#endif // _TRACELOG_H_ + diff --git a/src/AspNetCoreModuleV2/IISLib/treehash.h b/src/AspNetCoreModuleV2/IISLib/treehash.h new file mode 100644 index 0000000000..baa50726ce --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/treehash.h @@ -0,0 +1,850 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "rwlock.h" +#include "prime.h" + +template +class TREE_HASH_NODE +{ + template + friend class TREE_HASH_TABLE; + + private: + // Next node in the hash table look-aside + TREE_HASH_NODE<_Record> *_pNext; + + // links in the tree structure + TREE_HASH_NODE * _pParentNode; + TREE_HASH_NODE * _pFirstChild; + TREE_HASH_NODE * _pNextSibling; + + // actual record + _Record * _pRecord; + + // hash value + PCWSTR _pszPath; + DWORD _dwHash; +}; + +template +class TREE_HASH_TABLE +{ +protected: + typedef BOOL + (PFN_DELETE_IF)( + _Record * pRecord, + PVOID pvContext + ); + + typedef VOID + (PFN_APPLY)( + _Record * pRecord, + PVOID pvContext + ); + +public: + TREE_HASH_TABLE( + BOOL fCaseSensitive + ) : _ppBuckets( NULL ), + _nBuckets( 0 ), + _nItems( 0 ), + _fCaseSensitive( fCaseSensitive ) + { + } + + virtual + ~TREE_HASH_TABLE(); + + virtual + VOID + ReferenceRecord( + _Record * pRecord + ) = 0; + + virtual + VOID + DereferenceRecord( + _Record * pRecord + ) = 0; + + virtual + PCWSTR + GetKey( + _Record * pRecord + ) = 0; + + DWORD + Count() + { + return _nItems; + } + + virtual + VOID + Clear(); + + HRESULT + Initialize( + DWORD nBucketSize + ); + + DWORD + CalcHash( + PCWSTR pszKey + ) + { + return _fCaseSensitive ? HashString(pszKey) : HashStringNoCase(pszKey); + } + + virtual + VOID + FindKey( + PCWSTR pszKey, + _Record ** ppRecord + ); + + virtual + HRESULT + InsertRecord( + _Record * pRecord + ); + + virtual + VOID + DeleteKey( + PCWSTR pszKey + ); + + virtual + VOID + DeleteIf( + PFN_DELETE_IF pfnDeleteIf, + PVOID pvContext + ); + + VOID + Apply( + PFN_APPLY pfnApply, + PVOID pvContext + ); + +private: + + BOOL + FindNodeInternal( + PCWSTR pszKey, + DWORD dwHash, + TREE_HASH_NODE<_Record> ** ppNode, + TREE_HASH_NODE<_Record> *** pppPreviousNodeNextPointer = NULL + ); + + HRESULT + AddNodeInternal( + PCWSTR pszPath, + DWORD dwHash, + _Record * pRecord, + TREE_HASH_NODE<_Record> * pParentNode, + TREE_HASH_NODE<_Record> ** ppNewNode + ); + + HRESULT + AllocateNode( + PCWSTR pszPath, + DWORD dwHash, + _Record * pRecord, + TREE_HASH_NODE<_Record> * pParentNode, + TREE_HASH_NODE<_Record> ** ppNewNode + ); + + VOID + DeleteNode( + TREE_HASH_NODE<_Record> * pNode + ) + { + if (pNode->_pRecord != NULL) + { + DereferenceRecord(pNode->_pRecord); + pNode->_pRecord = NULL; + } + + HeapFree(GetProcessHeap(), + 0, + pNode); + } + + VOID + DeleteNodeInternal( + TREE_HASH_NODE<_Record> ** ppPreviousNodeNextPointer, + TREE_HASH_NODE<_Record> * pNode + ); + + VOID + RehashTableIfNeeded( + VOID + ); + + TREE_HASH_NODE<_Record> ** _ppBuckets; + DWORD _nBuckets; + DWORD _nItems; + BOOL _fCaseSensitive; + CWSDRWLock _tableLock; +}; + +template +HRESULT +TREE_HASH_TABLE<_Record>::AllocateNode( + PCWSTR pszPath, + DWORD dwHash, + _Record * pRecord, + TREE_HASH_NODE<_Record> * pParentNode, + TREE_HASH_NODE<_Record> ** ppNewNode +) +{ + // + // Allocate enough extra space for pszPath + // + DWORD cchPath = (DWORD) wcslen(pszPath); + if (cchPath >= ((0xffffffff - sizeof(TREE_HASH_NODE<_Record>))/sizeof(WCHAR) - 1)) + { + return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + } + TREE_HASH_NODE<_Record> *pNode = (TREE_HASH_NODE<_Record> *)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + sizeof(TREE_HASH_NODE<_Record>) + (cchPath+1)*sizeof(WCHAR)); + if (pNode == NULL) + { + return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + } + + memcpy(pNode+1, pszPath, (cchPath+1)*sizeof(WCHAR)); + pNode->_pszPath = (PCWSTR)(pNode+1); + pNode->_dwHash = dwHash; + pNode->_pNext = pNode->_pNextSibling = pNode->_pFirstChild = NULL; + pNode->_pParentNode = pParentNode; + pNode->_pRecord = pRecord; + + *ppNewNode = pNode; + return S_OK; +} + +template +HRESULT +TREE_HASH_TABLE<_Record>::Initialize( + DWORD nBuckets +) +{ + HRESULT hr = S_OK; + + if ( nBuckets == 0 ) + { + hr = E_INVALIDARG; + goto Failed; + } + + hr = _tableLock.Init(); + if ( FAILED( hr ) ) + { + goto Failed; + } + + if (nBuckets >= 0xffffffff/sizeof(TREE_HASH_NODE<_Record> *)) + { + hr = E_INVALIDARG; + goto Failed; + } + + _ppBuckets = (TREE_HASH_NODE<_Record> **)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + nBuckets*sizeof(TREE_HASH_NODE<_Record> *)); + if (_ppBuckets == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Failed; + } + _nBuckets = nBuckets; + + return S_OK; + +Failed: + + if (_ppBuckets) + { + HeapFree(GetProcessHeap(), + 0, + _ppBuckets); + _ppBuckets = NULL; + } + + return hr; +} + + +template +TREE_HASH_TABLE<_Record>::~TREE_HASH_TABLE() +{ + if (_ppBuckets == NULL) + { + return; + } + + _ASSERTE(_nItems == 0); + + HeapFree(GetProcessHeap(), + 0, + _ppBuckets); + _ppBuckets = NULL; + _nBuckets = 0; +} + +template +VOID +TREE_HASH_TABLE<_Record>::Clear() +{ + TREE_HASH_NODE<_Record> *pCurrent; + TREE_HASH_NODE<_Record> *pNext; + + _tableLock.ExclusiveAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + pCurrent = _ppBuckets[i]; + _ppBuckets[i] = NULL; + while (pCurrent != NULL) + { + pNext = pCurrent->_pNext; + DeleteNode(pCurrent); + pCurrent = pNext; + } + } + + _nItems = 0; + _tableLock.ExclusiveRelease(); +} + +template +BOOL +TREE_HASH_TABLE<_Record>::FindNodeInternal( + PCWSTR pszKey, + DWORD dwHash, + TREE_HASH_NODE<_Record> ** ppNode, + TREE_HASH_NODE<_Record> *** pppPreviousNodeNextPointer +) +/*++ + Return value indicates whether the item is found + key, dwHash - key and hash for the node to find + ppNode - on successful return, the node found, on failed return, the first + node with hash value greater than the node to be found + pppPreviousNodeNextPointer - the pointer to previous node's _pNext + + This routine may be called under either read or write lock +--*/ +{ + TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer; + TREE_HASH_NODE<_Record> *pNode; + BOOL fFound = FALSE; + + ppPreviousNodeNextPointer = _ppBuckets + (dwHash % _nBuckets); + pNode = *ppPreviousNodeNextPointer; + while (pNode != NULL) + { + if (pNode->_dwHash == dwHash) + { + if (CompareStringOrdinal(pszKey, + -1, + pNode->_pszPath, + -1, + !_fCaseSensitive) == CSTR_EQUAL) + { + fFound = TRUE; + break; + } + } + else if (pNode->_dwHash > dwHash) + { + break; + } + + ppPreviousNodeNextPointer = &(pNode->_pNext); + pNode = *ppPreviousNodeNextPointer; + } + + *ppNode = pNode; + if (pppPreviousNodeNextPointer != NULL) + { + *pppPreviousNodeNextPointer = ppPreviousNodeNextPointer; + } + return fFound; +} + +template +VOID +TREE_HASH_TABLE<_Record>::FindKey( + PCWSTR pszKey, + _Record ** ppRecord +) +{ + TREE_HASH_NODE<_Record> *pNode; + + *ppRecord = NULL; + + DWORD dwHash = CalcHash(pszKey); + + _tableLock.SharedAcquire(); + + if (FindNodeInternal(pszKey, dwHash, &pNode) && + pNode->_pRecord != NULL) + { + ReferenceRecord(pNode->_pRecord); + *ppRecord = pNode->_pRecord; + } + + _tableLock.SharedRelease(); +} + +template +HRESULT +TREE_HASH_TABLE<_Record>::AddNodeInternal( + PCWSTR pszPath, + DWORD dwHash, + _Record * pRecord, + TREE_HASH_NODE<_Record> * pParentNode, + TREE_HASH_NODE<_Record> ** ppNewNode +) +/*++ + Return value is HRESULT indicating sucess or failure + pszPath, dwHash, pRecord - path, hash value and record to be inserted + pParentNode - this will be the parent of the node being inserted + ppNewNode - on successful return, the new node created and inserted + + This function may be called under a read or write lock +--*/ +{ + TREE_HASH_NODE<_Record> *pNewNode; + TREE_HASH_NODE<_Record> *pNextNode; + TREE_HASH_NODE<_Record> **ppNextPointer; + HRESULT hr; + + // + // Ownership of pRecord is not transferred to pNewNode yet, so remember + // to either set it to null before deleting pNewNode or add an extra + // reference later - this is to make sure we do not do an extra ref/deref + // which users may view as getting flushed out of the hash-table + // + hr = AllocateNode(pszPath, + dwHash, + pRecord, + pParentNode, + &pNewNode); + if (FAILED(hr)) + { + return hr; + } + + do + { + // + // Find the right place to add this node + // + + if (FindNodeInternal(pszPath, dwHash, &pNextNode, &ppNextPointer)) + { + // + // If node already there, record may still need updating + // + if (pRecord != NULL && + InterlockedCompareExchangePointer((PVOID *)&pNextNode->_pRecord, + pRecord, + NULL) == NULL) + { + ReferenceRecord(pRecord); + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + } + + // ownership of pRecord has either passed to existing record or + // not to anyone at all + pNewNode->_pRecord = NULL; + DeleteNode(pNewNode); + *ppNewNode = pNextNode; + return hr; + } + + // + // If another node got inserted in betwen, we will have to retry + // + pNewNode->_pNext = pNextNode; + } while (InterlockedCompareExchangePointer((PVOID *)ppNextPointer, + pNewNode, + pNextNode) != pNextNode); + // pass ownership of pRecord now + if (pRecord != NULL) + { + ReferenceRecord(pRecord); + pRecord = NULL; + } + InterlockedIncrement((LONG *)&_nItems); + + // + // update the parent + // + if (pParentNode != NULL) + { + ppNextPointer = &pParentNode->_pFirstChild; + do + { + pNextNode = *ppNextPointer; + pNewNode->_pNextSibling = pNextNode; + } while (InterlockedCompareExchangePointer((PVOID *)ppNextPointer, + pNewNode, + pNextNode) != pNextNode); + } + + *ppNewNode = pNewNode; + return S_OK; +} + +template +HRESULT +TREE_HASH_TABLE<_Record>::InsertRecord( + _Record * pRecord +) +/*++ + This method inserts a node for this record and also empty nodes for paths + in the heirarchy leading upto this path + + The insert is done under only a read-lock - this is possible by keeping + the hashes in a bucket in increasing order and using interlocked operations + to actually insert the item in the hash-bucket lookaside list and the parent + children list + + Returns HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) if the record already exists. + Never leak this error to the end user because "*file* already exists" may be confusing. +--*/ +{ + PCWSTR pszKey = GetKey(pRecord); + STACK_STRU( strPartialPath, 256); + PWSTR pszPartialPath; + DWORD dwHash; + DWORD cchEnd; + HRESULT hr; + TREE_HASH_NODE<_Record> *pParentNode = NULL; + + hr = strPartialPath.Copy(pszKey); + if (FAILED(hr)) + { + goto Finished; + } + pszPartialPath = strPartialPath.QueryStr(); + + _tableLock.SharedAcquire(); + + // + // First find the lowest parent node present + // + for (cchEnd = strPartialPath.QueryCCH() - 1; cchEnd > 0; cchEnd--) + { + if (pszPartialPath[cchEnd] == L'/' || pszPartialPath[cchEnd] == L'\\') + { + pszPartialPath[cchEnd] = L'\0'; + + dwHash = CalcHash(pszPartialPath); + if (FindNodeInternal(pszPartialPath, dwHash, &pParentNode)) + { + pszPartialPath[cchEnd] = pszKey[cchEnd]; + break; + } + pParentNode = NULL; + } + } + + // + // Now go ahead and add the rest of the tree (including our record) + // + for (; cchEnd <= strPartialPath.QueryCCH(); cchEnd++) + { + if (pszPartialPath[cchEnd] == L'\0') + { + dwHash = CalcHash(pszPartialPath); + hr = AddNodeInternal( + pszPartialPath, + dwHash, + (cchEnd == strPartialPath.QueryCCH()) ? pRecord : NULL, + pParentNode, + &pParentNode); + if (FAILED(hr) && + hr != HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) + { + goto Finished; + } + + pszPartialPath[cchEnd] = pszKey[cchEnd]; + } + } + +Finished: + _tableLock.SharedRelease(); + + if (SUCCEEDED(hr)) + { + RehashTableIfNeeded(); + } + + return hr; +} + +template +VOID +TREE_HASH_TABLE<_Record>::DeleteNodeInternal( + TREE_HASH_NODE<_Record> ** ppNextPointer, + TREE_HASH_NODE<_Record> * pNode +) +/*++ + pNode is the node to be deleted + ppNextPointer is the pointer to the previous node's next pointer pointing + to this node + + This function should be called under write-lock +--*/ +{ + // + // First remove this node from hash table + // + *ppNextPointer = pNode->_pNext; + + // + // Now fixup parent + // + if (pNode->_pParentNode != NULL) + { + ppNextPointer = &pNode->_pParentNode->_pFirstChild; + while (*ppNextPointer != pNode) + { + ppNextPointer = &(*ppNextPointer)->_pNextSibling; + } + *ppNextPointer = pNode->_pNextSibling; + } + + // + // Now remove all children recursively + // + TREE_HASH_NODE<_Record> *pChild = pNode->_pFirstChild; + TREE_HASH_NODE<_Record> *pNextChild; + while (pChild != NULL) + { + pNextChild = pChild->_pNextSibling; + + ppNextPointer = _ppBuckets + (pChild->_dwHash % _nBuckets); + while (*ppNextPointer != pChild) + { + ppNextPointer = &(*ppNextPointer)->_pNext; + } + pChild->_pParentNode = NULL; + DeleteNodeInternal(ppNextPointer, pChild); + + pChild = pNextChild; + } + + DeleteNode(pNode); + _nItems--; +} + +template +VOID +TREE_HASH_TABLE<_Record>::DeleteKey( + PCWSTR pszKey +) +{ + TREE_HASH_NODE<_Record> *pNode; + TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer; + + DWORD dwHash = CalcHash(pszKey); + + _tableLock.ExclusiveAcquire(); + + if (FindNodeInternal(pszKey, dwHash, &pNode, &ppPreviousNodeNextPointer)) + { + DeleteNodeInternal(ppPreviousNodeNextPointer, pNode); + } + + _tableLock.ExclusiveRelease(); +} + +template +VOID +TREE_HASH_TABLE<_Record>::DeleteIf( + PFN_DELETE_IF pfnDeleteIf, + PVOID pvContext +) +{ + TREE_HASH_NODE<_Record> *pNode; + TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer; + BOOL fDelete; + + _tableLock.ExclusiveAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + ppPreviousNodeNextPointer = _ppBuckets + i; + pNode = *ppPreviousNodeNextPointer; + while (pNode != NULL) + { + // + // Non empty nodes deleted based on DeleteIf, empty nodes deleted + // if they have no children + // + fDelete = FALSE; + if (pNode->_pRecord != NULL) + { + if (pfnDeleteIf(pNode->_pRecord, pvContext)) + { + fDelete = TRUE; + } + } + else if (pNode->_pFirstChild == NULL) + { + fDelete = TRUE; + } + + if (fDelete) + { + if (pNode->_pFirstChild == NULL) + { + DeleteNodeInternal(ppPreviousNodeNextPointer, pNode); + } + else + { + DereferenceRecord(pNode->_pRecord); + pNode->_pRecord = NULL; + } + } + else + { + ppPreviousNodeNextPointer = &pNode->_pNext; + } + + pNode = *ppPreviousNodeNextPointer; + } + } + + _tableLock.ExclusiveRelease(); +} + +template +VOID +TREE_HASH_TABLE<_Record>::Apply( + PFN_APPLY pfnApply, + PVOID pvContext +) +{ + TREE_HASH_NODE<_Record> *pNode; + + _tableLock.SharedAcquire(); + + for (DWORD i=0; i<_nBuckets; i++) + { + pNode = _ppBuckets[i]; + while (pNode != NULL) + { + if (pNode->_pRecord != NULL) + { + pfnApply(pNode->_pRecord, pvContext); + } + + pNode = pNode->_pNext; + } + } + + _tableLock.SharedRelease(); +} + +template +VOID +TREE_HASH_TABLE<_Record>::RehashTableIfNeeded( + VOID +) +{ + TREE_HASH_NODE<_Record> **ppBuckets; + DWORD nBuckets; + TREE_HASH_NODE<_Record> *pNode; + TREE_HASH_NODE<_Record> *pNextNode; + TREE_HASH_NODE<_Record> **ppNextPointer; + TREE_HASH_NODE<_Record> *pNewNextNode; + DWORD nNewBuckets; + + // + // If number of items has become too many, we will double the hash table + // size (we never reduce it however) + // + if (_nItems <= PRIME::GetPrime(2*_nBuckets)) + { + return; + } + + _tableLock.ExclusiveAcquire(); + + nNewBuckets = PRIME::GetPrime(2*_nBuckets); + + if (_nItems <= nNewBuckets) + { + goto Finished; + } + + nBuckets = nNewBuckets; + if (nBuckets >= 0xffffffff/sizeof(TREE_HASH_NODE<_Record> *)) + { + goto Finished; + } + ppBuckets = (TREE_HASH_NODE<_Record> **)HeapAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + nBuckets*sizeof(TREE_HASH_NODE<_Record> *)); + if (ppBuckets == NULL) + { + goto Finished; + } + + // + // Take out nodes from the old hash table and insert in the new one, make + // sure to keep the hashes in increasing order + // + for (DWORD i=0; i<_nBuckets; i++) + { + pNode = _ppBuckets[i]; + while (pNode != NULL) + { + pNextNode = pNode->_pNext; + + ppNextPointer = ppBuckets + (pNode->_dwHash % nBuckets); + pNewNextNode = *ppNextPointer; + while (pNewNextNode != NULL && + pNewNextNode->_dwHash <= pNode->_dwHash) + { + ppNextPointer = &pNewNextNode->_pNext; + pNewNextNode = pNewNextNode->_pNext; + } + pNode->_pNext = pNewNextNode; + *ppNextPointer = pNode; + + pNode = pNextNode; + } + } + + HeapFree(GetProcessHeap(), 0, _ppBuckets); + _ppBuckets = ppBuckets; + _nBuckets = nBuckets; + ppBuckets = NULL; + +Finished: + + _tableLock.ExclusiveRelease(); +} + diff --git a/src/AspNetCoreModuleV2/IISLib/util.cxx b/src/AspNetCoreModuleV2/IISLib/util.cxx new file mode 100644 index 0000000000..214ee65abf --- /dev/null +++ b/src/AspNetCoreModuleV2/IISLib/util.cxx @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#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 (wcslen(pszName) > MAX_PATH) + { + if (FAILED(hr = pstrPath->Copy(L"\\\\?\\"))) + { + return hr; + } + } + else + { + pstrPath->Reset(); + } + + return pstrPath->Append(pszName); +} + diff --git a/src/RequestHandler/RequestHandler.vcxproj b/src/AspNetCoreModuleV2/RequestHandler/RequestHandler.vcxproj similarity index 99% rename from src/RequestHandler/RequestHandler.vcxproj rename to src/AspNetCoreModuleV2/RequestHandler/RequestHandler.vcxproj index fd198ba5b3..955839f2b9 100644 --- a/src/RequestHandler/RequestHandler.vcxproj +++ b/src/AspNetCoreModuleV2/RequestHandler/RequestHandler.vcxproj @@ -1,6 +1,6 @@ - + Debug @@ -252,7 +252,7 @@ - + diff --git a/src/RequestHandler/Source.def b/src/AspNetCoreModuleV2/RequestHandler/Source.def similarity index 100% rename from src/RequestHandler/Source.def rename to src/AspNetCoreModuleV2/RequestHandler/Source.def diff --git a/src/RequestHandler/aspnetcore_event.h b/src/AspNetCoreModuleV2/RequestHandler/aspnetcore_event.h similarity index 100% rename from src/RequestHandler/aspnetcore_event.h rename to src/AspNetCoreModuleV2/RequestHandler/aspnetcore_event.h diff --git a/src/RequestHandler/disconnectcontext.h b/src/AspNetCoreModuleV2/RequestHandler/disconnectcontext.h similarity index 100% rename from src/RequestHandler/disconnectcontext.h rename to src/AspNetCoreModuleV2/RequestHandler/disconnectcontext.h diff --git a/src/RequestHandler/dllmain.cxx b/src/AspNetCoreModuleV2/RequestHandler/dllmain.cxx similarity index 100% rename from src/RequestHandler/dllmain.cxx rename to src/AspNetCoreModuleV2/RequestHandler/dllmain.cxx diff --git a/src/RequestHandler/environmentvariablehelpers.h b/src/AspNetCoreModuleV2/RequestHandler/environmentvariablehelpers.h similarity index 100% rename from src/RequestHandler/environmentvariablehelpers.h rename to src/AspNetCoreModuleV2/RequestHandler/environmentvariablehelpers.h diff --git a/src/RequestHandler/inprocess/inprocessapplication.cpp b/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.cpp similarity index 100% rename from src/RequestHandler/inprocess/inprocessapplication.cpp rename to src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.cpp diff --git a/src/RequestHandler/inprocess/inprocessapplication.h b/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.h similarity index 100% rename from src/RequestHandler/inprocess/inprocessapplication.h rename to src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.h diff --git a/src/RequestHandler/inprocess/inprocesshandler.cpp b/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.cpp similarity index 100% rename from src/RequestHandler/inprocess/inprocesshandler.cpp rename to src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.cpp diff --git a/src/RequestHandler/inprocess/inprocesshandler.h b/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.h similarity index 100% rename from src/RequestHandler/inprocess/inprocesshandler.h rename to src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.h diff --git a/src/RequestHandler/managedexports.cxx b/src/AspNetCoreModuleV2/RequestHandler/managedexports.cxx similarity index 100% rename from src/RequestHandler/managedexports.cxx rename to src/AspNetCoreModuleV2/RequestHandler/managedexports.cxx diff --git a/src/RequestHandler/outofprocess/forwarderconnection.cxx b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.cxx similarity index 100% rename from src/RequestHandler/outofprocess/forwarderconnection.cxx rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.cxx diff --git a/src/RequestHandler/outofprocess/forwarderconnection.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.h similarity index 100% rename from src/RequestHandler/outofprocess/forwarderconnection.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.h diff --git a/src/RequestHandler/outofprocess/forwardinghandler.cpp b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.cpp similarity index 100% rename from src/RequestHandler/outofprocess/forwardinghandler.cpp rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.cpp diff --git a/src/RequestHandler/outofprocess/forwardinghandler.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.h similarity index 100% rename from src/RequestHandler/outofprocess/forwardinghandler.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.h diff --git a/src/RequestHandler/outofprocess/outprocessapplication.cpp b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.cpp similarity index 100% rename from src/RequestHandler/outofprocess/outprocessapplication.cpp rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.cpp diff --git a/src/RequestHandler/outofprocess/outprocessapplication.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.h similarity index 100% rename from src/RequestHandler/outofprocess/outprocessapplication.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.h diff --git a/src/RequestHandler/outofprocess/processmanager.cxx b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.cxx similarity index 100% rename from src/RequestHandler/outofprocess/processmanager.cxx rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.cxx diff --git a/src/RequestHandler/outofprocess/processmanager.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.h similarity index 100% rename from src/RequestHandler/outofprocess/processmanager.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.h diff --git a/src/RequestHandler/outofprocess/protocolconfig.cxx b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.cxx similarity index 100% rename from src/RequestHandler/outofprocess/protocolconfig.cxx rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.cxx diff --git a/src/RequestHandler/outofprocess/protocolconfig.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.h similarity index 100% rename from src/RequestHandler/outofprocess/protocolconfig.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.h diff --git a/src/RequestHandler/outofprocess/responseheaderhash.cxx b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.cxx similarity index 100% rename from src/RequestHandler/outofprocess/responseheaderhash.cxx rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.cxx diff --git a/src/RequestHandler/outofprocess/responseheaderhash.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.h similarity index 100% rename from src/RequestHandler/outofprocess/responseheaderhash.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.h diff --git a/src/RequestHandler/outofprocess/serverprocess.cxx b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/serverprocess.cxx similarity index 100% rename from src/RequestHandler/outofprocess/serverprocess.cxx rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/serverprocess.cxx diff --git a/src/RequestHandler/outofprocess/serverprocess.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/serverprocess.h similarity index 100% rename from src/RequestHandler/outofprocess/serverprocess.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/serverprocess.h diff --git a/src/RequestHandler/outofprocess/websockethandler.cxx b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.cxx similarity index 100% rename from src/RequestHandler/outofprocess/websockethandler.cxx rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.cxx diff --git a/src/RequestHandler/outofprocess/websockethandler.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.h similarity index 100% rename from src/RequestHandler/outofprocess/websockethandler.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.h diff --git a/src/RequestHandler/outofprocess/winhttphelper.cxx b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.cxx similarity index 100% rename from src/RequestHandler/outofprocess/winhttphelper.cxx rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.cxx diff --git a/src/RequestHandler/outofprocess/winhttphelper.h b/src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.h similarity index 100% rename from src/RequestHandler/outofprocess/winhttphelper.h rename to src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.h diff --git a/src/RequestHandler/precomp.hxx b/src/AspNetCoreModuleV2/RequestHandler/precomp.hxx similarity index 100% rename from src/RequestHandler/precomp.hxx rename to src/AspNetCoreModuleV2/RequestHandler/precomp.hxx diff --git a/src/RequestHandler/requesthandler.rc b/src/AspNetCoreModuleV2/RequestHandler/requesthandler.rc similarity index 100% rename from src/RequestHandler/requesthandler.rc rename to src/AspNetCoreModuleV2/RequestHandler/requesthandler.rc diff --git a/src/AspNetCoreModuleV2/RequestHandler/sttimer.h b/src/AspNetCoreModuleV2/RequestHandler/sttimer.h new file mode 100644 index 0000000000..1bd4b67543 --- /dev/null +++ b/src/AspNetCoreModuleV2/RequestHandler/sttimer.h @@ -0,0 +1,280 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#ifndef _STTIMER_H +#define _STTIMER_H + +class STTIMER +{ +public: + + STTIMER() + : _pTimer( NULL ) + { + fInCanel = FALSE; + } + + virtual + ~STTIMER() + { + if ( _pTimer ) + { + CancelTimer(); + + CloseThreadpoolTimer( _pTimer ); + + _pTimer = NULL; + } + } + + HRESULT + InitializeTimer( + PTP_TIMER_CALLBACK pfnCallback, + VOID * pContext, + DWORD dwInitialWait = 0, + DWORD dwPeriod = 0 + ) + { + _pTimer = CreateThreadpoolTimer( pfnCallback, + pContext, + NULL ); + + if ( !_pTimer ) + { + return HRESULT_FROM_WIN32( GetLastError() ); + } + + if ( dwInitialWait ) + { + SetTimer( dwInitialWait, + dwPeriod ); + } + + return S_OK; + } + + VOID + SetTimer( + DWORD dwInitialWait, + DWORD dwPeriod = 0 + ) + { + FILETIME ftInitialWait; + + if ( dwInitialWait == 0 && dwPeriod == 0 ) + { + // + // Special case. We are preventing new callbacks + // from being queued. Any existing callbacks in the + // queue will still run. + // + // This effectively disables the timer. It can be + // re-enabled by setting non-zero initial wait or + // period values. + // + if (_pTimer != NULL) + { + SetThreadpoolTimer(_pTimer, NULL, 0, 0); + } + + return; + } + + InitializeRelativeFileTime( &ftInitialWait, dwInitialWait ); + + SetThreadpoolTimer( _pTimer, + &ftInitialWait, + dwPeriod, + 0 ); + } + + VOID + CancelTimer() + { + // + // Disable the timer + // + if (fInCanel) + return; + + fInCanel = TRUE; + SetTimer( 0 ); + + // + // Wait until any callbacks queued prior to disabling + // have completed. + // + if (_pTimer != NULL) + WaitForThreadpoolTimerCallbacks( _pTimer, TRUE ); + + _pTimer = NULL; + fInCanel = FALSE; + } + + static + VOID + CALLBACK + TimerCallback( + _In_ PTP_CALLBACK_INSTANCE Instance, + _In_ PVOID Context, + _In_ PTP_TIMER Timer + ) + { + Instance; + Timer; + STRU* pstruLogFilePath = (STRU*)Context; + HANDLE hStdoutHandle = NULL; + SECURITY_ATTRIBUTES saAttr = { 0 }; + HRESULT hr = S_OK; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + hStdoutHandle = CreateFileW(pstruLogFilePath->QueryStr(), + FILE_READ_DATA, + FILE_SHARE_WRITE, + &saAttr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hStdoutHandle == INVALID_HANDLE_VALUE) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + CloseHandle(hStdoutHandle); + } + +private: + + VOID + InitializeRelativeFileTime( + FILETIME * pft, + DWORD dwMilliseconds + ) + { + LARGE_INTEGER li; + + // + // The pftDueTime parameter expects the time to be + // expressed as the number of 100 nanosecond intervals + // times -1. + // + // To convert from milliseconds, we'll multiply by + // -10000 + // + + li.QuadPart = (LONGLONG)dwMilliseconds * -10000; + + pft->dwHighDateTime = li.HighPart; + pft->dwLowDateTime = li.LowPart; + }; + + TP_TIMER * _pTimer; + BOOL fInCanel; +}; + +class STELAPSED +{ +public: + + STELAPSED() + : _dwInitTime( 0 ), + _dwInitTickCount( 0 ), + _dwPerfCountsPerMillisecond( 0 ), + _fUsingHighResolution( FALSE ) + { + LARGE_INTEGER li; + BOOL fResult; + + _dwInitTickCount = GetTickCount64(); + + fResult = QueryPerformanceFrequency( &li ); + + if ( !fResult ) + { + goto Finished; + } + + _dwPerfCountsPerMillisecond = li.QuadPart / 1000; + + fResult = QueryPerformanceCounter( &li ); + + if ( !fResult ) + { + goto Finished; + } + + _dwInitTime = li.QuadPart / _dwPerfCountsPerMillisecond; + + _fUsingHighResolution = TRUE; + +Finished: + + return; + } + + virtual + ~STELAPSED() + { + } + + LONGLONG + QueryElapsedTime() + { + LARGE_INTEGER li; + + if ( _fUsingHighResolution && QueryPerformanceCounter( &li ) ) + { + DWORD64 dwCurrentTime = li.QuadPart / _dwPerfCountsPerMillisecond; + + if ( dwCurrentTime < _dwInitTime ) + { + // + // It's theoretically possible that QueryPerformanceCounter + // may return slightly different values on different CPUs. + // In this case, we don't want to return an unexpected value + // so we'll return zero. This is acceptable because + // presumably such a case would only happen for a very short + // time window. + // + // It would be possible to prevent this by ensuring processor + // affinity for all calls to QueryPerformanceCounter, but that + // would be undesirable in the general case because it could + // introduce unnecessary context switches and potentially a + // CPU bottleneck. + // + // Note that this issue also applies to callers doing rapid + // calls to this function. If a caller wants to mitigate + // that, they could enforce the affinitization, or they + // could implement a similar sanity check when comparing + // returned values from this function. + // + + return 0; + } + + return dwCurrentTime - _dwInitTime; + } + + return GetTickCount64() - _dwInitTickCount; + } + + BOOL + QueryUsingHighResolution() + { + return _fUsingHighResolution; + } + +private: + + DWORD64 _dwInitTime; + DWORD64 _dwInitTickCount; + DWORD64 _dwPerfCountsPerMillisecond; + BOOL _fUsingHighResolution; +}; + +#endif // _STTIMER_H \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.nuspec b/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.nuspec index 1c1cf423c9..3124aad25f 100644 --- a/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.nuspec +++ b/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.nuspec @@ -26,7 +26,7 @@ - - + +
diff --git a/src/RequestHandler/RequestHandler.vcxproj.filters b/src/RequestHandler/RequestHandler.vcxproj.filters deleted file mode 100644 index fb47904137..0000000000 --- a/src/RequestHandler/RequestHandler.vcxproj.filters +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - InProcess - - - InProcess - - - InProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - - - - InProcess - - - InProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - OutOfProcess - - - - - - - - - - - - - - - - - - {e567abb5-bac5-4f05-a320-5e25dcfc0000} - - - {5568209f-269e-4d0a-bbb7-ba14f874ccb7} - - - \ No newline at end of file diff --git a/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj b/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj new file mode 100644 index 0000000000..8f12548e9f --- /dev/null +++ b/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj @@ -0,0 +1,184 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {0692D963-DB10-4387-B3EA-460FBB9BD9A3} + Win32Proj + AspNetCoreModuleTests + 10.0.15063.0 + NativeUnitTestProject + + + + DynamicLibrary + true + v141 + Unicode + false + + + DynamicLibrary + false + v141 + true + Unicode + false + + + DynamicLibrary + true + v141 + Unicode + false + + + DynamicLibrary + false + v141 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + true + + + true + + + + Use + Level3 + Disabled + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\CommonLib;..\..\src\AspNetCoreModuleV2\IISLib + _DEBUG;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + Use + Level3 + Disabled + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\CommonLib;..\..\src\AspNetCoreModuleV2\IISLib + WIN32;_DEBUG;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + Use + MaxSpeed + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\CommonLib;..\..\src\AspNetCoreModuleV2\IISLib + WIN32;NDEBUG;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + Use + MaxSpeed + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\CommonLib;..\..\src\AspNetCoreModuleV2\IISLib + NDEBUG;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + + + + + Create + Create + Create + Create + + + + + + + {55494e58-e061-4c4c-a0a8-837008e72f85} + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + + + + + + \ No newline at end of file diff --git a/test/AspNetCoreModuleTests/stdafx.h b/test/AspNetCoreModuleTests/stdafx.h new file mode 100644 index 0000000000..67bd5aa27b --- /dev/null +++ b/test/AspNetCoreModuleTests/stdafx.h @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "stringa.h" +#include "stringu.h" +#include "dbgutil.h" +#include "ahutil.h" +#include "multisz.h" +#include "multisza.h" +#include "base64.h" +#include +#include +#include +#include +#include + +#include "..\..\src\AspNetCoreModuleV2\IISLib\hashtable.h" +#include "..\..\src\AspNetCoreModuleV2\IISLib\stringu.h" +#include "..\..\src\AspNetCoreModuleV2\IISLib\stringa.h" +#include "..\..\src\AspNetCoreModuleV2\IISLib\multisz.h" +#include "..\..\src\AspNetCoreModuleV2\IISLib\dbgutil.h" +#include "..\..\src\AspNetCoreModuleV2\IISLib\ahutil.h" +#include "..\..\src\AspNetCoreModuleV2\IISLib\hashfn.h" + +#include "..\..\src\AspNetCoreModuleV2\CommonLib\hostfxr_utility.h" +#include "..\..\src\AspNetCoreModuleV2\CommonLib\environmentvariablehash.h" +#include "..\..\src\AspNetCoreModuleV2\CommonLib\aspnetcoreconfig.h" +#include "..\..\src\AspNetCoreModuleV2\CommonLib\application.h" +#include "..\..\src\AspNetCoreModuleV2\CommonLib\utility.h" +#include "..\..\src\AspNetCoreModuleV2\CommonLib\debugutil.h" +#include "..\..\src\AspNetCoreModuleV2\CommonLib\requesthandler.h" +#include "..\..\src\AspNetCoreModuleV2\CommonLib\resources.h" +#include "..\..\src\AspNetCoreModuleV2\CommonLib\aspnetcore_msg.h" + +#include "CppUnitTest.h" diff --git a/test/CommonLibTests/CommonLibTests.vcxproj b/test/CommonLibTests/CommonLibTests.vcxproj index 90ecc876b3..c68f83af6d 100644 --- a/test/CommonLibTests/CommonLibTests.vcxproj +++ b/test/CommonLibTests/CommonLibTests.vcxproj @@ -47,11 +47,11 @@ - + {55494e58-e061-4c4c-a0a8-837008e72f85} - - {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + {09d9d1d6-2951-4e14-bc35-76a23cf9391a} {2af210a9-5bdc-45e8-95dd-07b5a2616493} @@ -69,7 +69,7 @@ EnableFastChecks MultiThreadedDebug Level3 - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\IISLib;..\..\src\CommonLib;..\gtest-1.8.0\include + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest-1.8.0\include /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" @@ -91,7 +91,7 @@ EnableFastChecks MultiThreadedDebug Level3 - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\IISLib;..\..\src\CommonLib;..\gtest-1.8.0\include + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest-1.8.0\include /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" @@ -111,7 +111,7 @@ MultiThreaded Level3 ProgramDatabase - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\IISLib;..\..\src\CommonLib;..\gtest-1.8.0\include + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest-1.8.0\include /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" @@ -133,7 +133,7 @@ MultiThreaded Level3 ProgramDatabase - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\IISLib;..\..\src\CommonLib;..\gtest-1.8.0\include + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest-1.8.0\include /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" diff --git a/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs b/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs index 19f272a491..370ce2cf63 100644 --- a/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs +++ b/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs @@ -19,19 +19,23 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { } - [Fact(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] - public Task HelloWorld_IISExpress_Clr_X64_Portable() + [Theory(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] + [InlineData("V1")] + [InlineData("V2")] + public Task HelloWorld_IISExpress_Clr_X64_Portable(string ancmVersion) { - return HelloWorld(RuntimeFlavor.Clr, ApplicationType.Portable); + return HelloWorld(RuntimeFlavor.Clr, ApplicationType.Portable, ancmVersion); } - [Fact] - public Task HelloWorld_IISExpress_CoreClr_X64_Portable() + [Theory] + [InlineData("V1")] + [InlineData("V2")] + public Task HelloWorld_IISExpress_CoreClr_X64_Portable(string ancmVersion) { - return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable); + return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable, ancmVersion); } - private async Task HelloWorld(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + private async Task HelloWorld(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, string ancmVersion) { var serverType = ServerType.IISExpress; var architecture = RuntimeArchitecture.x64; @@ -49,10 +53,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests ApplicationType = applicationType, Configuration = #if DEBUG - "Debug" + "Debug", #else - "Release" + "Release", #endif + AdditionalPublishParameters = $" /p:ANCMVersion={ancmVersion}" }; using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) diff --git a/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs b/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs index a91a3b78d3..0e71a21fdc 100644 --- a/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs +++ b/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs @@ -25,19 +25,23 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { } - [Fact(Skip = "Full framework web.config generation is currently incorrect. See: https://github.com/aspnet/websdk/pull/322")] - public Task Https_HelloWorld_CLR_X64() + [Theory(Skip = "Full framework web.config generation is currently incorrect. See: https://github.com/aspnet/websdk/pull/322")] + [InlineData("V1")] + [InlineData("V2")] + public Task Https_HelloWorld_CLR_X64(string ancmVersion) { - return HttpsHelloWorld(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44396); + return HttpsHelloWorld(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44396, ancmVersion); } - [Fact] - public Task Https_HelloWorld_CoreCLR_X64_Portable() + [Theory] + [InlineData("V1")] + [InlineData("V2")] + public Task Https_HelloWorld_CoreCLR_X64_Portable(string ancmVersion) { - return HttpsHelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 44394); + return HttpsHelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 44394, ancmVersion); } - private async Task HttpsHelloWorld(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port) + private async Task HttpsHelloWorld(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port, string ancmVersion) { var serverType = ServerType.IISExpress; var architecture = RuntimeArchitecture.x64; @@ -58,10 +62,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests ApplicationType = applicationType, Configuration = #if DEBUG - "Debug" + "Debug", #else - "Release" + "Release", #endif + AdditionalPublishParameters = $" /p:ANCMVersion={ancmVersion}" + }; using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) @@ -93,35 +99,43 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests } } - [Fact] - public Task Https_HelloWorld_NoClientCert_CoreCLR_X64_Portable() + [Theory] + [InlineData("V1")] + [InlineData("V2")] + public Task Https_HelloWorld_NoClientCert_CoreCLR_X64_Portable(string ancmVersion) { - return HttpsHelloWorldCerts(RuntimeFlavor.CoreClr, ApplicationType.Portable , port: 44397, sendClientCert: false); + return HttpsHelloWorldCerts(RuntimeFlavor.CoreClr, ApplicationType.Portable , port: 44397, sendClientCert: false, ancmVersion); } - [Fact(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] - public Task Https_HelloWorld_NoClientCert_Clr_X64() + [Theory(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] + [InlineData("V1")] + [InlineData("V2")] + public Task Https_HelloWorld_NoClientCert_Clr_X64(string ancmVersion) { - return HttpsHelloWorldCerts(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44398, sendClientCert: false); + return HttpsHelloWorldCerts(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44398, sendClientCert: false, ancmVersion); } #pragma warning disable xUnit1004 // Test methods should not be skipped - [Fact(Skip = "Manual test only, selecting a client cert is non-determanistic on different machines.")] + [Theory(Skip = "Manual test only, selecting a client cert is non-determanistic on different machines.")] + [InlineData("V1")] + [InlineData("V2")] #pragma warning restore xUnit1004 // Test methods should not be skipped - public Task Https_HelloWorld_ClientCert_Clr_X64() + public Task Https_HelloWorld_ClientCert_Clr_X64(string ancmVersion) { - return HttpsHelloWorldCerts(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44301, sendClientCert: true); + return HttpsHelloWorldCerts(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44301, sendClientCert: true, ancmVersion); } #pragma warning disable xUnit1004 // Test methods should not be skipped - [Fact(Skip = "Manual test only, selecting a client cert is non-determanistic on different machines.")] + [Theory(Skip = "Manual test only, selecting a client cert is non-determanistic on different machines.")] + [InlineData("V1")] + [InlineData("V2")] #pragma warning restore xUnit1004 // Test methods should not be skipped - public Task Https_HelloWorld_ClientCert_CoreCLR_X64_Portable() + public Task Https_HelloWorld_ClientCert_CoreCLR_X64_Portable(string ancmVersion) { - return HttpsHelloWorldCerts(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 44302, sendClientCert: true); + return HttpsHelloWorldCerts(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 44302, sendClientCert: true, ancmVersion); } - private async Task HttpsHelloWorldCerts(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port, bool sendClientCert) + private async Task HttpsHelloWorldCerts(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port, bool sendClientCert, string ancmVersion) { var serverType = ServerType.IISExpress; var architecture = RuntimeArchitecture.x64; @@ -141,10 +155,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests ApplicationType = applicationType, Configuration = #if DEBUG - "Debug" + "Debug", #else - "Release" + "Release", #endif + AdditionalPublishParameters = $" /p:ANCMVersion={ancmVersion}" }; using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) diff --git a/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs b/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs index fe3b583a0f..809c213b27 100644 --- a/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs +++ b/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs @@ -25,19 +25,23 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { } - [Fact(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] - public Task NtlmAuthentication_Clr_X64() + [Theory(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] + [InlineData("V1")] + [InlineData("V2")] + public Task NtlmAuthentication_Clr_X64(string ancmVersion) { - return NtlmAuthentication(RuntimeFlavor.Clr, ApplicationType.Portable, port: 5051); + return NtlmAuthentication(RuntimeFlavor.Clr, ApplicationType.Portable, port: 5051, ancmVersion); } - [Fact] - public Task NtlmAuthentication_CoreClr_X64_Portable() + [Theory] + [InlineData("V1")] + [InlineData("V2")] + public Task NtlmAuthentication_CoreClr_X64_Portable(string ancmVersion) { - return NtlmAuthentication(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 5052); + return NtlmAuthentication(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 5052, ancmVersion); } - private async Task NtlmAuthentication(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port) + private async Task NtlmAuthentication(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port, string ancmVersion) { var serverType = ServerType.IISExpress; var architecture = RuntimeArchitecture.x64; @@ -49,7 +53,13 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var windowsRid = architecture == RuntimeArchitecture.x64 ? "win7-x64" : "win7-x86"; + var additionalPublishParameters = $" /p:ANCMVersion={ancmVersion}"; + if (ApplicationType.Standalone == applicationType && RuntimeFlavor.CoreClr == runtimeFlavor) + { + additionalPublishParameters += " -r " + windowsRid; + } + var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) { ApplicationBaseUriHint = $"http://localhost:{port}", @@ -58,9 +68,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests SiteName = "NtlmAuthenticationTestSite", // This is configured in the NtlmAuthentication.config TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", ApplicationType = applicationType, - AdditionalPublishParameters = ApplicationType.Standalone == applicationType && RuntimeFlavor.CoreClr == runtimeFlavor - ? "-r " + windowsRid - : null, + AdditionalPublishParameters = additionalPublishParameters, Configuration = #if DEBUG "Debug" diff --git a/tools/update_schema.ps1 b/tools/update_schema.ps1 index a5dc6c7c03..b299c91e6a 100644 --- a/tools/update_schema.ps1 +++ b/tools/update_schema.ps1 @@ -9,7 +9,7 @@ param() $ErrorActionPreference = 'Stop' Set-StrictMode -Version 1 -$schemaSource = Resolve-Path "$PSScriptRoot\..\src\AspNetCore\aspnetcore_schema.xml" +$schemaSource = Resolve-Path "$PSScriptRoot\..\src\AspNetCoreModuleV2\AspNetCore\aspnetcore_schema.xml" [bool]$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") if (-not $isAdmin -and -not $WhatIfPreference) {