From 26c0f3b12664a3011afe1b8fc9a9ea1c85b12e3d Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 7 Apr 2020 17:44:48 -0700 Subject: [PATCH] Trailers for IIS --- AspNetCore.sln | 151 ++++++++++++++ .../managedexports.cpp | 72 +++++++ .../Core/IISHttpContext.FeatureCollection.cs | 20 +- .../IIS/src/Core/IISHttpContext.Features.cs | 15 ++ .../IIS/IIS/src/Core/IISHttpContext.IO.cs | 6 + .../IIS/IIS/src/Core/IISHttpContext.cs | 43 +++- src/Servers/IIS/IIS/src/NativeMethods.cs | 17 ++ .../FixtureLoggedTest.cs | 10 +- .../Inprocess/ResponseTrailersTests.cs | 196 ++++++++++++++++++ .../testassets/InProcessWebSite/Startup.cs | 148 ++++++++++++- .../IIS/tools/RemoveRedirectConfig.ps1 | 1 + 11 files changed, 669 insertions(+), 10 deletions(-) create mode 100644 src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseTrailersTests.cs create mode 100644 src/Servers/IIS/tools/RemoveRedirectConfig.ps1 diff --git a/AspNetCore.sln b/AspNetCore.sln index 989394fa94e1..6b23e95a098e 100644 --- a/AspNetCore.sln +++ b/AspNetCore.sln @@ -1457,6 +1457,28 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropWebsite", "src\Grpc\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.ConsoleHost", "src\Components\benchmarkapps\Wasm.Performance\ConsoleHost\Wasm.Performance.ConsoleHost.csproj", "{E9408723-E6A9-4715-B906-3B25B0238ABA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProcessWebSite", "src\Servers\IIS\IIS\test\testassets\InProcessWebSite\InProcessWebSite.csproj", "{8DA61885-B95E-4BA1-A752-C79B6597FC44}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ANCM", "ANCM", "{D62AF49B-F9FE-4794-AC39-A473FF13CA81}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\Servers\IIS\AspNetCoreModuleV2\AspNetCore\AspNetCore.vcxproj", "{EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "src\Servers\IIS\AspNetCoreModuleV2\CommonLib\CommonLib.vcxproj", "{55494E58-E061-4C4C-A0A8-837008E72F85}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLibTests", "src\Servers\IIS\AspNetCoreModuleV2\CommonLibTests\CommonLibTests.vcxproj", "{1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gtest", "src\Servers\IIS\AspNetCoreModuleV2\gtest\gtest.vcxproj", "{CAC1267B-8778-4257-AAC6-CAF481723B01}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\Servers\IIS\AspNetCoreModuleV2\IISLib\IISLib.vcxproj", "{09D9D1D6-2951-4E14-BC35-76A23CF9391A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InProcessRequestHandler", "src\Servers\IIS\AspNetCoreModuleV2\InProcessRequestHandler\InProcessRequestHandler.vcxproj", "{D57EA297-6DC2-4BC0-8C91-334863327863}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OutOfProcessRequestHandler", "src\Servers\IIS\AspNetCoreModuleV2\OutOfProcessRequestHandler\OutOfProcessRequestHandler.vcxproj", "{7F87406C-A3C8-4139-A68D-E4C344294A67}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RequestHandlerLib", "src\Servers\IIS\AspNetCoreModuleV2\RequestHandlerLib\RequestHandlerLib.vcxproj", "{1533E271-F61B-441B-8B74-59FB61DF0552}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ANCMSymbols", "src\Servers\IIS\AspNetCoreModuleV2\Symbols\Microsoft.AspNetCore.ANCMSymbols.csproj", "{7E268085-1046-4362-80CB-2977FF826DCA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -6903,6 +6925,124 @@ Global {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x64.Build.0 = Release|Any CPU {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x86.ActiveCfg = Release|Any CPU {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x86.Build.0 = Release|Any CPU + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Debug|Any CPU.ActiveCfg = Debug|x86 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Debug|x64.ActiveCfg = Debug|x64 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Debug|x64.Build.0 = Debug|x64 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Debug|x86.ActiveCfg = Debug|x86 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Debug|x86.Build.0 = Debug|x86 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Release|Any CPU.ActiveCfg = Release|x86 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Release|x64.ActiveCfg = Release|x64 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Release|x64.Build.0 = Release|x64 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Release|x86.ActiveCfg = Release|x86 + {8DA61885-B95E-4BA1-A752-C79B6597FC44}.Release|x86.Build.0 = Release|x86 + {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}.Debug|x86.Deploy.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 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x86.Deploy.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}.Debug|x86.Deploy.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 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.Deploy.0 = Release|Win32 + {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}.Debug|x86.Deploy.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 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x86.Deploy.0 = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x64.ActiveCfg = Debug|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x64.Build.0 = Debug|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x86.ActiveCfg = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x86.Build.0 = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x86.Deploy.0 = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|Any CPU.ActiveCfg = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x64.ActiveCfg = Release|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x64.Build.0 = Release|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x86.ActiveCfg = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x86.Build.0 = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x86.Deploy.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}.Debug|x86.Deploy.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 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x86.Deploy.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}.Debug|x86.Deploy.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 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.Deploy.0 = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x64.ActiveCfg = Debug|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x64.Build.0 = Debug|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x86.ActiveCfg = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x86.Build.0 = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x86.Deploy.0 = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|Any CPU.ActiveCfg = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x64.ActiveCfg = Release|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x64.Build.0 = Release|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.ActiveCfg = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.Build.0 = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.Deploy.0 = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x64.ActiveCfg = Debug|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x64.Build.0 = Debug|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x86.ActiveCfg = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x86.Build.0 = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x86.Deploy.0 = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|Any CPU.ActiveCfg = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x64.ActiveCfg = Release|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x64.Build.0 = Release|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x86.ActiveCfg = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x86.Build.0 = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x86.Deploy.0 = Release|Win32 + {7E268085-1046-4362-80CB-2977FF826DCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Debug|x64.ActiveCfg = Debug|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Debug|x64.Build.0 = Debug|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Debug|x86.ActiveCfg = Debug|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Debug|x86.Build.0 = Debug|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Release|Any CPU.Build.0 = Release|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Release|x64.ActiveCfg = Release|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Release|x64.Build.0 = Release|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Release|x86.ActiveCfg = Release|Any CPU + {7E268085-1046-4362-80CB-2977FF826DCA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -7633,6 +7773,17 @@ Global {C3A0F425-669F-46A8-893F-CF449A6DAE56} = {00B2DD87-7E2A-4460-BE1B-5E18B1062B7F} {19189670-E206-471D-94F8-7D3D545E5020} = {00B2DD87-7E2A-4460-BE1B-5E18B1062B7F} {E9408723-E6A9-4715-B906-3B25B0238ABA} = {6276A9A0-791B-49C1-AD8F-50AC47CDC196} + {8DA61885-B95E-4BA1-A752-C79B6597FC44} = {41BB7BA4-AC08-4E9A-83EA-6D587A5B951C} + {D62AF49B-F9FE-4794-AC39-A473FF13CA81} = {D67E977E-75DF-41EE-8315-6DBF5C2B7357} + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} + {55494E58-E061-4C4C-A0A8-837008E72F85} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} + {CAC1267B-8778-4257-AAC6-CAF481723B01} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} + {09D9D1D6-2951-4E14-BC35-76A23CF9391A} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} + {D57EA297-6DC2-4BC0-8C91-334863327863} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} + {7F87406C-A3C8-4139-A68D-E4C344294A67} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} + {1533E271-F61B-441B-8B74-59FB61DF0552} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} + {7E268085-1046-4362-80CB-2977FF826DCA} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F} diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp index a7bcaa3675e4..25bd2a2da9f1 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp @@ -10,6 +10,50 @@ extern bool g_fInProcessApplicationCreated; extern std::string g_errorPageContent; extern IHttpServer* g_pHttpServer; +// +// Add support for certain HTTP/2.0 features like trailing headers +// and GOAWAY or RST_STREAM frames. +// + +class __declspec(uuid("1a2acc57-cae2-4f28-b4ab-00c8f96b12ec")) + IHttpResponse4 : public IHttpResponse3 +{ +public: + virtual + HRESULT + DeleteTrailer( + _In_ PCSTR pszHeaderName + ) = 0; + + virtual + PCSTR + GetTrailer( + _In_ PCSTR pszHeaderName, + _Out_ USHORT* pcchHeaderValue = NULL + ) const = 0; + + virtual + VOID + ResetStream( + _In_ ULONG errorCode + ) = 0; + + virtual + VOID + SetNeedGoAway( + VOID + ) = 0; + + virtual + HRESULT + SetTrailer( + _In_ PCSTR pszHeaderName, + _In_ PCSTR pszHeaderValue, + _In_ USHORT cchHeaderValue, + _In_ BOOL fReplace + ) = 0; +}; + // // Initialization export // @@ -517,4 +561,32 @@ http_set_startup_error_page_content(_In_ byte* errorPageContent, int length) memcpy(&g_errorPageContent[0], errorPageContent, length); } +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_has_response4( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _Out_ BOOL* supportsTrailers +) +{ + IHttpResponse4* pHttpResponse; + + HRESULT hr = HttpGetExtendedInterface(g_pHttpServer, pInProcessHandler->QueryHttpContext()->GetResponse(), &pHttpResponse); + *supportsTrailers = SUCCEEDED(hr); + + return 0; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_response_set_trailer( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ PCSTR pszHeaderName, + _In_ PCSTR pszHeaderValue, + _In_ USHORT usHeaderValueLength, + _In_ BOOL fReplace) +{ + // always unknown + IHttpResponse4* pHttpResponse = (IHttpResponse4*)pInProcessHandler->QueryHttpContext()->GetResponse(); + return pHttpResponse->SetTrailer(pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace); +} // End of export diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs index 1cdfc4be3285..7d5f3aafd90a 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs @@ -32,7 +32,8 @@ internal partial class IISHttpContext : IFeatureCollection, IServerVariablesFeature, ITlsConnectionFeature, IHttpBodyControlFeature, - IHttpMaxRequestBodySizeFeature + IHttpMaxRequestBodySizeFeature, + IHttpResponseTrailersFeature { // NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation, // then the list of `implementedFeatures` in the generated code project MUST also be updated. @@ -376,6 +377,23 @@ unsafe X509Certificate2 ITlsConnectionFeature.ClientCertificate } } + internal IHttpResponseTrailersFeature GetResponseTrailersFeature() + { + // Check version is above 2. + if (HttpVersion >= System.Net.HttpVersion.Version20 && NativeMethods.HttpSupportTrailer(_pInProcessHandler)) + { + return this; + } + + return null; + } + + IHeaderDictionary IHttpResponseTrailersFeature.Trailers + { + get => ResponseTrailers ??= HttpResponseTrailers; + set => ResponseTrailers = value; + } + void IHttpResponseBodyFeature.DisableBuffering() { NativeMethods.HttpDisableBuffering(_pInProcessHandler); diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.Features.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.Features.cs index e6acacb78395..0945312becb6 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.Features.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.Features.cs @@ -28,6 +28,7 @@ internal partial class IISHttpContext private static readonly Type IISHttpContextType = typeof(IISHttpContext); private static readonly Type IServerVariablesFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature); private static readonly Type IHttpMaxRequestBodySizeFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature); + private static readonly Type IHttpResponseTrailersFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpResponseTrailersFeature); private object _currentIHttpRequestFeature; private object _currentIHttpResponseFeature; @@ -48,6 +49,7 @@ internal partial class IISHttpContext private object _currentIHttpBodyControlFeature; private object _currentIServerVariablesFeature; private object _currentIHttpMaxRequestBodySizeFeature; + private object _currentIHttpResponseTrailersFeature; private void Initialize() { @@ -63,6 +65,7 @@ private void Initialize() _currentIServerVariablesFeature = this; _currentIHttpMaxRequestBodySizeFeature = this; _currentITlsConnectionFeature = this; + _currentIHttpResponseTrailersFeature = GetResponseTrailersFeature(); } internal object FastFeatureGet(Type key) @@ -147,6 +150,10 @@ internal object FastFeatureGet(Type key) { return _currentIHttpMaxRequestBodySizeFeature; } + if (key == IHttpResponseTrailersFeature) + { + return _currentIHttpResponseTrailersFeature; + } return ExtraFeatureGet(key); } @@ -249,6 +256,10 @@ internal void FastFeatureSet(Type key, object feature) { _currentIHttpMaxRequestBodySizeFeature = feature; } + if (key == IHttpResponseTrailersFeature) + { + _currentIHttpResponseTrailersFeature = feature; + } if (key == IISHttpContextType) { throw new InvalidOperationException("Cannot set IISHttpContext in feature collection"); @@ -334,6 +345,10 @@ private IEnumerable> FastEnumerable() { yield return new KeyValuePair(IHttpMaxRequestBodySizeFeature, _currentIHttpMaxRequestBodySizeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature); } + if (_currentIHttpResponseTrailersFeature != null) + { + yield return new KeyValuePair(IHttpResponseTrailersFeature, _currentIHttpResponseTrailersFeature as global::Microsoft.AspNetCore.Http.Features.IHttpResponseTrailersFeature); + } if (MaybeExtra != null) { diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs index 60d098bc1107..31cb48019101 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs @@ -169,6 +169,12 @@ private async Task WriteBody(bool flush = false) // if request is done no need to flush, http.sys would do it for us if (result.IsCompleted) { + // When is the reader completed? Is it always after the request pipeline exits? Or can CompleteAsync make it complete early? + if (HasTrailers) + { + SetResponseTrailers(); + } + break; } diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs index ed798ba12ecf..08f10bb71e9e 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs @@ -67,6 +67,8 @@ internal abstract partial class IISHttpContext : NativeRequestContext, IThreadPo protected Pipe _bodyInputPipe; protected OutputProducer _bodyOutput; + private HeaderCollection _trailers; + private const string NtlmString = "NTLM"; private const string NegotiateString = "Negotiate"; private const string BasicString = "Basic"; @@ -114,7 +116,11 @@ internal unsafe IISHttpContext( public IHeaderDictionary RequestHeaders { get; set; } public IHeaderDictionary ResponseHeaders { get; set; } + public IHeaderDictionary ResponseTrailers { get; set; } private HeaderCollection HttpResponseHeaders { get; set; } + private HeaderCollection HttpResponseTrailers => _trailers ??= new HeaderCollection(checkTrailers: true); + internal bool HasTrailers => _trailers?.Count > 0; + internal HttpApiTypes.HTTP_VERB KnownMethod { get; private set; } private bool HasStartedConsumingRequestBody { get; set; } @@ -418,6 +424,41 @@ public unsafe void SetResponseHeaders() } } + public unsafe void SetResponseTrailers() + { + HttpResponseTrailers.IsReadOnly = true; + foreach (var headerPair in HttpResponseTrailers) + { + var headerValues = headerPair.Value; + + if (headerValues.Count == 0) + { + continue; + } + + var headerNameBytes = Encoding.ASCII.GetBytes(headerPair.Key); + fixed (byte* pHeaderName = headerNameBytes) + { + for (var i = 0; i < headerValues.Count; i++) + { + var isFirst = i == 0; + + var headerValue = headerValues[i]; + if (string.IsNullOrEmpty(headerValue)) + { + continue; + } + + var headerValueBytes = Encoding.UTF8.GetBytes(headerValue); + fixed (byte* pHeaderValue = headerValueBytes) + { + NativeMethods.HttpResponseSetTrailer(_pInProcessHandler, pHeaderName, pHeaderValue, (ushort)headerValueBytes.Length, replace: isFirst); + } + } + } + } + } + public abstract Task ProcessRequestAsync(); public void OnStarting(Func callback, object state) @@ -615,7 +656,7 @@ private async Task HandleRequest() } catch (Exception ex) { - _logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpContext)}.{nameof(HandleRequest)}."); + _logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpContext)}.{nameof(HandleRequest)}."); } finally { diff --git a/src/Servers/IIS/IIS/src/NativeMethods.cs b/src/Servers/IIS/IIS/src/NativeMethods.cs index 3fa84190d876..83fb9ee7d704 100644 --- a/src/Servers/IIS/IIS/src/NativeMethods.cs +++ b/src/Servers/IIS/IIS/src/NativeMethods.cs @@ -141,6 +141,11 @@ private static extern unsafe int http_websockets_write_bytes( [DllImport(AspNetCoreModuleDll)] private static extern unsafe int http_response_set_unknown_header(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool fReplace); + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_has_response4(IntPtr pInProcessHandler, out bool isResponse4); + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_response_set_trailer(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool replace); + [DllImport(AspNetCoreModuleDll)] private static extern unsafe int http_response_set_known_header(IntPtr pInProcessHandler, int headerId, byte* pHeaderValue, ushort length, bool fReplace); @@ -308,6 +313,18 @@ internal static unsafe void HttpSetStartupErrorPageContent(byte[] content) } } + internal static unsafe void HttpResponseSetTrailer(IntPtr pInProcessHandler, byte* pHeaderName, byte* pHeaderValue, ushort length, bool replace) + { + Validate(http_response_set_trailer(pInProcessHandler, pHeaderName, pHeaderValue, length, false)); + } + + internal static unsafe bool HttpSupportTrailer(IntPtr pInProcessHandler) + { + bool supportsTrailers; + Validate(http_has_response4(pInProcessHandler, out supportsTrailers)); + return supportsTrailers; + } + private static void Validate(int hr) { if (hr != HR_OK) diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/FixtureLoggedTest.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/FixtureLoggedTest.cs index 29af89da9b03..2467039621aa 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/FixtureLoggedTest.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/FixtureLoggedTest.cs @@ -8,24 +8,24 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { - public class FixtureLoggedTest: LoggedTest + public class FixtureLoggedTest : LoggedTest { - private readonly IISTestSiteFixture _fixture; + protected IISTestSiteFixture Fixture { get; set; } public FixtureLoggedTest(IISTestSiteFixture fixture) { - _fixture = fixture; + Fixture = fixture; } public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) { base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper); - _fixture.Attach(this); + Fixture.Attach(this); } public override void Dispose() { - _fixture.Detach(this); + Fixture.Detach(this); base.Dispose(); } } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseTrailersTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseTrailersTests.cs new file mode 100644 index 000000000000..fe44060de3db --- /dev/null +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseTrailersTests.cs @@ -0,0 +1,196 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class ResponseTrailersTests : IISFunctionalTestBase + { + public ResponseTrailersTests(PublishedSitesFixture fixture) : base(fixture) + { + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_HTTP2_TrailersAvailable() + { + var version = System.Environment.OSVersion.Version; + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_HTTP2_TrailersAvailable"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Empty(response.TrailingHeaders); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_HTTP1_TrailersNotAvailable() + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(hostingModel: IntegrationTesting.HostingModel.OutOfProcess); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_HTTP1_TrailersNotAvailable"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version11, response.Version); + Assert.Empty(response.TrailingHeaders); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_ProhibitedTrailers_Blocked() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_ProhibitedTrailers_Blocked"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Empty(response.TrailingHeaders); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_NoBody_TrailersSent() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_NoBody_TrailersSent"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("TrailerValue", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_WithBody_TrailersSent() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_WithBody_TrailersSent"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("Trailer Value", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_WithContentLengthBody_TrailersSent() + { + var body = "Hello World"; + + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_WithContentLengthBody_TrailersSent"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.Equal(body, await response.Content.ReadAsStringAsync()); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("Trailer Value", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_WithTrailersBeforeContentLengthBody_TrailersSent() + { + var body = "Hello World"; + + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_WithTrailersBeforeContentLengthBody_TrailersSent"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + // Avoid HttpContent's automatic content-length calculation. + Assert.True(response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out var contentLength), HeaderNames.ContentLength); + Assert.Equal((2 * body.Length).ToString(CultureInfo.InvariantCulture), contentLength.First()); + Assert.Equal(body + body, await response.Content.ReadAsStringAsync()); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("Trailer Value", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_WithContentLengthBodyAndDeclared_TrailersSent() + { + var body = "Hello World"; + + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_WithContentLengthBodyAndDeclared_TrailersSent"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + // Avoid HttpContent's automatic content-length calculation. + Assert.True(response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out var contentLength), HeaderNames.ContentLength); + Assert.Equal(body.Length.ToString(CultureInfo.InvariantCulture), contentLength.First()); + Assert.Equal("TrailerName", response.Headers.Trailer.Single()); + Assert.Equal(body, await response.Content.ReadAsStringAsync()); + Assert.NotEmpty(response.TrailingHeaders); + Assert.Equal("Trailer Value", response.TrailingHeaders.GetValues("TrailerName").Single()); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.20180")] + public async Task ResponseTrailers_MultipleValues_SentAsSeparateHeaders() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await SendRequestAsync(deploymentResult.HttpClient.BaseAddress.ToString() + "ResponseTrailers_MultipleValues_SentAsSeparateHeaders"); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response.Version); + Assert.NotEmpty(response.TrailingHeaders); + + Assert.Equal(new[] { "TrailerValue0", "TrailerValue1" }, response.TrailingHeaders.GetValues("TrailerName")); + } + + private IISDeploymentParameters GetHttpsDeploymentParameters() + { + var port = TestPortHelper.GetNextSSLPort(); + var deploymentParameters = Fixture.GetBaseDeploymentParameters(); + deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/"; + deploymentParameters.AddHttpsToServerConfig(); + return deploymentParameters; + } + + private async Task SendRequestAsync(string uri, bool http2 = true) + { + var handler = new HttpClientHandler(); + handler.MaxResponseHeadersLength = 128; + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + using var client = new HttpClient(handler); + client.DefaultRequestVersion = http2 ? HttpVersion.Version20 : HttpVersion.Version11; + return await client.GetAsync(uri); + } + } +} diff --git a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs index 03fc108b925a..2fcdec580bfa 100644 --- a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs +++ b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -25,6 +26,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; using Xunit; using HttpFeatures = Microsoft.AspNetCore.Http.Features; @@ -942,7 +944,7 @@ private async Task CommandLineArgs(HttpContext ctx) public Task HttpsHelloWorld(HttpContext ctx) => ctx.Response.WriteAsync("Scheme:" + ctx.Request.Scheme + "; Original:" + ctx.Request.Headers["x-original-proto"]); - public Task Path(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Request.Path.Value); + public Task Path(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Request.Path.Value); public Task Query(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Request.QueryString.Value); @@ -978,7 +980,7 @@ public Task RestrictedNTLM(HttpContext context) } public Task UpgradeFeatureDetection(HttpContext context) => - context.Response.WriteAsync(context.Features.Get() != null? "Enabled": "Disabled"); + context.Response.WriteAsync(context.Features.Get() != null ? "Enabled" : "Disabled"); public Task CheckRequestHandlerVersion(HttpContext context) { @@ -989,7 +991,7 @@ public Task CheckRequestHandlerVersion(HttpContext context) { File.Delete(context.Request.Headers["ANCMRHPath"]); } - catch(UnauthorizedAccessException) + catch (UnauthorizedAccessException) { // TODO calling delete on the file will succeed when running with IIS return context.Response.WriteAsync("Hello World"); @@ -1030,5 +1032,145 @@ public Task InvalidCharacter(HttpContext context) Assert.Equal("�", value); return Task.CompletedTask; } + + #if !FORWARDCOMPAT + public Task ResponseTrailers_HTTP2_TrailersAvailable(HttpContext context) + { + Assert.Equal("HTTP/2", context.Request.Protocol); + Assert.True(context.Response.SupportsTrailers()); + return Task.FromResult(0); + } + + public Task ResponseTrailers_HTTP1_TrailersNotAvailable(HttpContext context) + { + Assert.Equal("HTTP/1.1", context.Request.Protocol); + Assert.False(context.Response.SupportsTrailers()); + return Task.FromResult(0); + } + + public Task ResponseTrailers_ProhibitedTrailers_Blocked(HttpContext context) + { + Assert.True(context.Response.SupportsTrailers()); + foreach (var header in DisallowedTrailers) + { + Assert.Throws(() => context.Response.AppendTrailer(header, "value")); + } + return Task.FromResult(0); + } + + public Task ResponseTrailers_NoBody_TrailersSent(HttpContext context) + { + context.Response.DeclareTrailer("trailername"); + context.Response.AppendTrailer("trailername", "TrailerValue"); + return Task.FromResult(0); + } + + public async Task ResponseTrailers_WithBody_TrailersSent(HttpContext context) + { + await context.Response.WriteAsync("Hello World"); + context.Response.AppendTrailer("TrailerName", "Trailer Value"); + } + + public async Task ResponseTrailers_WithContentLengthBody_TrailersSent(HttpContext context) + { + var body = "Hello World"; + context.Response.ContentLength = body.Length; + await context.Response.WriteAsync(body); + context.Response.AppendTrailer("TrailerName", "Trailer Value"); + } + + public async Task ResponseTrailers_WithTrailersBeforeContentLengthBody_TrailersSent(HttpContext context) + { + var body = "Hello World"; + context.Response.ContentLength = body.Length * 2; + await context.Response.WriteAsync(body); + context.Response.AppendTrailer("TrailerName", "Trailer Value"); + await context.Response.WriteAsync(body); + } + + public async Task ResponseTrailers_WithContentLengthBodyAndDeclared_TrailersSent(HttpContext context) + { + var body = "Hello World"; + context.Response.ContentLength = body.Length; + context.Response.DeclareTrailer("TrailerName"); + await context.Response.WriteAsync(body); + context.Response.AppendTrailer("TrailerName", "Trailer Value"); + } + + public async Task ResponseTrailers_WithContentLengthBodyAndDeclaredButMissingTrailers_Completes(HttpContext context) + { + var body = "Hello World"; + context.Response.ContentLength = body.Length; + context.Response.DeclareTrailer("TrailerName"); + await context.Response.WriteAsync(body); + } + + public Task ResponseTrailers_MultipleValues_SentAsSeparateHeaders(HttpContext context) + { + context.Response.AppendTrailer("trailername", new StringValues(new[] { "TrailerValue0", "TrailerValue1" })); + return Task.FromResult(0); + } + + public Task ResponseTrailers_LargeTrailers_Success(HttpContext context) + { + var values = new[] { + new string('a', 1024), + new string('b', 1024 * 4), + new string('c', 1024 * 8), + new string('d', 1024 * 16), + new string('e', 1024 * 32), + new string('f', 1024 * 64 - 1) }; // Max header size + + context.Response.AppendTrailer("ThisIsALongerHeaderNameThatStillWorksForReals", new StringValues(values)); + return Task.FromResult(0); + } + + public Task ResponseTrailers_NullValues_Ignored(HttpContext context) + { + foreach (var kvp in NullTrailers) + { + context.Response.AppendTrailer(kvp.Item1, kvp.Item2); + } + + return Task.FromResult(0); + } + + internal static readonly HashSet<(string, StringValues, StringValues)> NullTrailers = new HashSet<(string, StringValues, StringValues)>() + { + ("NullString", (string)null, (string)null), + ("EmptyString", "", ""), + ("NullStringArray", new string[] { null }, ""), + ("EmptyStringArray", new string[] { "" }, ""), + ("MixedStringArray", new string[] { null, "" }, new string[] { "", "" }), + ("WithValidStrings", new string[] { null, "Value", "" }, new string[] { "", "Value", "" }) + }; + + // https://tools.ietf.org/html/rfc7230#section-4.1.2 + internal static readonly HashSet DisallowedTrailers = new HashSet(StringComparer.OrdinalIgnoreCase) + { + // Message framing headers. + HeaderNames.TransferEncoding, HeaderNames.ContentLength, + + // Routing headers. + HeaderNames.Host, + + // Request modifiers: controls and conditionals. + // rfc7231#section-5.1: Controls. + HeaderNames.CacheControl, HeaderNames.Expect, HeaderNames.MaxForwards, HeaderNames.Pragma, HeaderNames.Range, HeaderNames.TE, + + // rfc7231#section-5.2: Conditionals. + HeaderNames.IfMatch, HeaderNames.IfNoneMatch, HeaderNames.IfModifiedSince, HeaderNames.IfUnmodifiedSince, HeaderNames.IfRange, + + // Authentication headers. + HeaderNames.WWWAuthenticate, HeaderNames.Authorization, HeaderNames.ProxyAuthenticate, HeaderNames.ProxyAuthorization, HeaderNames.SetCookie, HeaderNames.Cookie, + + // Response control data. + // rfc7231#section-7.1: Control Data. + HeaderNames.Age, HeaderNames.Expires, HeaderNames.Date, HeaderNames.Location, HeaderNames.RetryAfter, HeaderNames.Vary, HeaderNames.Warning, + + // Content-Encoding, Content-Type, Content-Range, and Trailer itself. + HeaderNames.ContentEncoding, HeaderNames.ContentType, HeaderNames.ContentRange, HeaderNames.Trailer + }; +#endif } } diff --git a/src/Servers/IIS/tools/RemoveRedirectConfig.ps1 b/src/Servers/IIS/tools/RemoveRedirectConfig.ps1 new file mode 100644 index 000000000000..2dec28ad1676 --- /dev/null +++ b/src/Servers/IIS/tools/RemoveRedirectConfig.ps1 @@ -0,0 +1 @@ +(Get-Content C:\Windows\System32\inetsrv\config\redirection.config) -replace 'enabled="true"', 'enabled="false"' | Out-File C:\Windows\System32\inetsrv\config\redirection.config