From 7579dfccbdd8e80a7461fe16c96988a2b381a99a Mon Sep 17 00:00:00 2001 From: AKaliumhexacyanoferrat Date: Tue, 4 Jun 2024 08:09:09 +0200 Subject: [PATCH 01/14] Prepare for next iteration --- API/GenHTTP.Api.csproj | 6 +++--- Engine/GenHTTP.Engine.csproj | 6 +++--- .../GenHTTP.Modules.Authentication.Web.csproj | 6 +++--- .../Authentication/GenHTTP.Modules.Authentication.csproj | 6 +++--- Modules/AutoReload/GenHTTP.Modules.AutoReload.csproj | 6 +++--- Modules/Basics/GenHTTP.Modules.Basics.csproj | 6 +++--- Modules/Caching/GenHTTP.Modules.Caching.csproj | 6 +++--- Modules/ClientCaching/GenHTTP.Modules.ClientCaching.csproj | 6 +++--- Modules/Compression/GenHTTP.Modules.Compression.csproj | 6 +++--- Modules/Controllers/GenHTTP.Modules.Controllers.csproj | 6 +++--- Modules/Conversion/GenHTTP.Modules.Conversion.csproj | 6 +++--- .../GenHTTP.Modules.DirectoryBrowsing.csproj | 6 +++--- Modules/ErrorHandling/GenHTTP.Modules.ErrorHandling.csproj | 6 +++--- Modules/Functional/GenHTTP.Modules.Functional.csproj | 6 +++--- Modules/IO/GenHTTP.Modules.IO.csproj | 6 +++--- Modules/Layouting/GenHTTP.Modules.Layouting.csproj | 6 +++--- Modules/LoadBalancing/GenHTTP.Modules.LoadBalancing.csproj | 6 +++--- Modules/Markdown/GenHTTP.Modules.Markdown.csproj | 6 +++--- Modules/Minification/GenHTTP.Modules.Minification.csproj | 6 +++--- Modules/Pages/GenHTTP.Modules.Pages.csproj | 6 +++--- Modules/Placeholders/GenHTTP.Modules.Placeholders.csproj | 6 +++--- Modules/Practices/GenHTTP.Modules.Practices.csproj | 6 +++--- Modules/Protobuf/GenHTTP.Modules.Protobuf.csproj | 6 +++--- Modules/Razor/GenHTTP.Modules.Razor.csproj | 6 +++--- Modules/Reflection/GenHTTP.Modules.Reflection.csproj | 6 +++--- Modules/ReverseProxy/GenHTTP.Modules.ReverseProxy.csproj | 6 +++--- Modules/Robots/GenHTTP.Modules.Robots.csproj | 6 +++--- Modules/Scriban/GenHTTP.Modules.Scriban.csproj | 6 +++--- Modules/Security/GenHTTP.Modules.Security.csproj | 6 +++--- Modules/ServerCaching/GenHTTP.Modules.ServerCaching.csproj | 6 +++--- .../GenHTTP.Modules.SinglePageApplications.csproj | 6 +++--- Modules/Sitemaps/GenHTTP.Modules.Sitemaps.csproj | 6 +++--- .../StaticWebsites/GenHTTP.Modules.StaticWebsites.csproj | 6 +++--- .../VirtualHosting/GenHTTP.Modules.VirtualHosting.csproj | 6 +++--- Modules/Webservices/GenHTTP.Modules.Webservices.csproj | 6 +++--- Modules/Websites/GenHTTP.Modules.Websites.csproj | 6 +++--- Packages/GenHTTP.Core.nuspec | 2 +- Testing/Testing/GenHTTP.Testing.csproj | 6 +++--- 38 files changed, 112 insertions(+), 112 deletions(-) diff --git a/API/GenHTTP.Api.csproj b/API/GenHTTP.Api.csproj index 7c9b9113..25a00270 100644 --- a/API/GenHTTP.Api.csproj +++ b/API/GenHTTP.Api.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Engine/GenHTTP.Engine.csproj b/Engine/GenHTTP.Engine.csproj index cfadada1..45fb1591 100644 --- a/Engine/GenHTTP.Engine.csproj +++ b/Engine/GenHTTP.Engine.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Authentication.Web/GenHTTP.Modules.Authentication.Web.csproj b/Modules/Authentication.Web/GenHTTP.Modules.Authentication.Web.csproj index f350f83d..cc298678 100644 --- a/Modules/Authentication.Web/GenHTTP.Modules.Authentication.Web.csproj +++ b/Modules/Authentication.Web/GenHTTP.Modules.Authentication.Web.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Authentication/GenHTTP.Modules.Authentication.csproj b/Modules/Authentication/GenHTTP.Modules.Authentication.csproj index 74d0b577..4a0b9bfa 100644 --- a/Modules/Authentication/GenHTTP.Modules.Authentication.csproj +++ b/Modules/Authentication/GenHTTP.Modules.Authentication.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/AutoReload/GenHTTP.Modules.AutoReload.csproj b/Modules/AutoReload/GenHTTP.Modules.AutoReload.csproj index 582ddcfc..edee3b83 100644 --- a/Modules/AutoReload/GenHTTP.Modules.AutoReload.csproj +++ b/Modules/AutoReload/GenHTTP.Modules.AutoReload.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Basics/GenHTTP.Modules.Basics.csproj b/Modules/Basics/GenHTTP.Modules.Basics.csproj index 8d2caf09..c13fde76 100644 --- a/Modules/Basics/GenHTTP.Modules.Basics.csproj +++ b/Modules/Basics/GenHTTP.Modules.Basics.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Caching/GenHTTP.Modules.Caching.csproj b/Modules/Caching/GenHTTP.Modules.Caching.csproj index 5f1abf30..12d9fc97 100644 --- a/Modules/Caching/GenHTTP.Modules.Caching.csproj +++ b/Modules/Caching/GenHTTP.Modules.Caching.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/ClientCaching/GenHTTP.Modules.ClientCaching.csproj b/Modules/ClientCaching/GenHTTP.Modules.ClientCaching.csproj index f90cea4d..9e787689 100644 --- a/Modules/ClientCaching/GenHTTP.Modules.ClientCaching.csproj +++ b/Modules/ClientCaching/GenHTTP.Modules.ClientCaching.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Compression/GenHTTP.Modules.Compression.csproj b/Modules/Compression/GenHTTP.Modules.Compression.csproj index fb51a4f7..a4f7b8e0 100644 --- a/Modules/Compression/GenHTTP.Modules.Compression.csproj +++ b/Modules/Compression/GenHTTP.Modules.Compression.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Controllers/GenHTTP.Modules.Controllers.csproj b/Modules/Controllers/GenHTTP.Modules.Controllers.csproj index d076ca4c..53b59c63 100644 --- a/Modules/Controllers/GenHTTP.Modules.Controllers.csproj +++ b/Modules/Controllers/GenHTTP.Modules.Controllers.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Conversion/GenHTTP.Modules.Conversion.csproj b/Modules/Conversion/GenHTTP.Modules.Conversion.csproj index 899bde6f..2d74955f 100644 --- a/Modules/Conversion/GenHTTP.Modules.Conversion.csproj +++ b/Modules/Conversion/GenHTTP.Modules.Conversion.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/DirectoryBrowsing/GenHTTP.Modules.DirectoryBrowsing.csproj b/Modules/DirectoryBrowsing/GenHTTP.Modules.DirectoryBrowsing.csproj index 94286f04..551d32a7 100644 --- a/Modules/DirectoryBrowsing/GenHTTP.Modules.DirectoryBrowsing.csproj +++ b/Modules/DirectoryBrowsing/GenHTTP.Modules.DirectoryBrowsing.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/ErrorHandling/GenHTTP.Modules.ErrorHandling.csproj b/Modules/ErrorHandling/GenHTTP.Modules.ErrorHandling.csproj index 1e486519..9dac9330 100644 --- a/Modules/ErrorHandling/GenHTTP.Modules.ErrorHandling.csproj +++ b/Modules/ErrorHandling/GenHTTP.Modules.ErrorHandling.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Functional/GenHTTP.Modules.Functional.csproj b/Modules/Functional/GenHTTP.Modules.Functional.csproj index 7efde048..5579a3b9 100644 --- a/Modules/Functional/GenHTTP.Modules.Functional.csproj +++ b/Modules/Functional/GenHTTP.Modules.Functional.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/IO/GenHTTP.Modules.IO.csproj b/Modules/IO/GenHTTP.Modules.IO.csproj index e1ac8cf6..4aa80cef 100644 --- a/Modules/IO/GenHTTP.Modules.IO.csproj +++ b/Modules/IO/GenHTTP.Modules.IO.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Layouting/GenHTTP.Modules.Layouting.csproj b/Modules/Layouting/GenHTTP.Modules.Layouting.csproj index 706b7161..117337c8 100644 --- a/Modules/Layouting/GenHTTP.Modules.Layouting.csproj +++ b/Modules/Layouting/GenHTTP.Modules.Layouting.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/LoadBalancing/GenHTTP.Modules.LoadBalancing.csproj b/Modules/LoadBalancing/GenHTTP.Modules.LoadBalancing.csproj index e29d8899..ae85b133 100644 --- a/Modules/LoadBalancing/GenHTTP.Modules.LoadBalancing.csproj +++ b/Modules/LoadBalancing/GenHTTP.Modules.LoadBalancing.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Markdown/GenHTTP.Modules.Markdown.csproj b/Modules/Markdown/GenHTTP.Modules.Markdown.csproj index df16d613..d53e6b48 100644 --- a/Modules/Markdown/GenHTTP.Modules.Markdown.csproj +++ b/Modules/Markdown/GenHTTP.Modules.Markdown.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Vitaly Derbin, Andreas Nägeli diff --git a/Modules/Minification/GenHTTP.Modules.Minification.csproj b/Modules/Minification/GenHTTP.Modules.Minification.csproj index b8dcdce7..793b2c37 100644 --- a/Modules/Minification/GenHTTP.Modules.Minification.csproj +++ b/Modules/Minification/GenHTTP.Modules.Minification.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Pages/GenHTTP.Modules.Pages.csproj b/Modules/Pages/GenHTTP.Modules.Pages.csproj index c9d44fc3..d13c1797 100644 --- a/Modules/Pages/GenHTTP.Modules.Pages.csproj +++ b/Modules/Pages/GenHTTP.Modules.Pages.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Placeholders/GenHTTP.Modules.Placeholders.csproj b/Modules/Placeholders/GenHTTP.Modules.Placeholders.csproj index 9d292417..78d7a66f 100644 --- a/Modules/Placeholders/GenHTTP.Modules.Placeholders.csproj +++ b/Modules/Placeholders/GenHTTP.Modules.Placeholders.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Practices/GenHTTP.Modules.Practices.csproj b/Modules/Practices/GenHTTP.Modules.Practices.csproj index 0286e5cd..456b4dbd 100644 --- a/Modules/Practices/GenHTTP.Modules.Practices.csproj +++ b/Modules/Practices/GenHTTP.Modules.Practices.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Protobuf/GenHTTP.Modules.Protobuf.csproj b/Modules/Protobuf/GenHTTP.Modules.Protobuf.csproj index ec46c4d9..4a642ff4 100644 --- a/Modules/Protobuf/GenHTTP.Modules.Protobuf.csproj +++ b/Modules/Protobuf/GenHTTP.Modules.Protobuf.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli, Iulian Caluian diff --git a/Modules/Razor/GenHTTP.Modules.Razor.csproj b/Modules/Razor/GenHTTP.Modules.Razor.csproj index 7d73ac43..e1e03582 100644 --- a/Modules/Razor/GenHTTP.Modules.Razor.csproj +++ b/Modules/Razor/GenHTTP.Modules.Razor.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Reflection/GenHTTP.Modules.Reflection.csproj b/Modules/Reflection/GenHTTP.Modules.Reflection.csproj index f29a6eb2..57caaba8 100644 --- a/Modules/Reflection/GenHTTP.Modules.Reflection.csproj +++ b/Modules/Reflection/GenHTTP.Modules.Reflection.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/ReverseProxy/GenHTTP.Modules.ReverseProxy.csproj b/Modules/ReverseProxy/GenHTTP.Modules.ReverseProxy.csproj index 79d2bc1e..a17d2030 100644 --- a/Modules/ReverseProxy/GenHTTP.Modules.ReverseProxy.csproj +++ b/Modules/ReverseProxy/GenHTTP.Modules.ReverseProxy.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Robots/GenHTTP.Modules.Robots.csproj b/Modules/Robots/GenHTTP.Modules.Robots.csproj index 5754a485..89038215 100644 --- a/Modules/Robots/GenHTTP.Modules.Robots.csproj +++ b/Modules/Robots/GenHTTP.Modules.Robots.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Scriban/GenHTTP.Modules.Scriban.csproj b/Modules/Scriban/GenHTTP.Modules.Scriban.csproj index 57bc6374..82f82668 100644 --- a/Modules/Scriban/GenHTTP.Modules.Scriban.csproj +++ b/Modules/Scriban/GenHTTP.Modules.Scriban.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Security/GenHTTP.Modules.Security.csproj b/Modules/Security/GenHTTP.Modules.Security.csproj index 5a95a866..da547506 100644 --- a/Modules/Security/GenHTTP.Modules.Security.csproj +++ b/Modules/Security/GenHTTP.Modules.Security.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/ServerCaching/GenHTTP.Modules.ServerCaching.csproj b/Modules/ServerCaching/GenHTTP.Modules.ServerCaching.csproj index dc5789e5..6db7b907 100644 --- a/Modules/ServerCaching/GenHTTP.Modules.ServerCaching.csproj +++ b/Modules/ServerCaching/GenHTTP.Modules.ServerCaching.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/SinglePageApplications/GenHTTP.Modules.SinglePageApplications.csproj b/Modules/SinglePageApplications/GenHTTP.Modules.SinglePageApplications.csproj index feec71ac..1a32b041 100644 --- a/Modules/SinglePageApplications/GenHTTP.Modules.SinglePageApplications.csproj +++ b/Modules/SinglePageApplications/GenHTTP.Modules.SinglePageApplications.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Sitemaps/GenHTTP.Modules.Sitemaps.csproj b/Modules/Sitemaps/GenHTTP.Modules.Sitemaps.csproj index 55183942..54abd981 100644 --- a/Modules/Sitemaps/GenHTTP.Modules.Sitemaps.csproj +++ b/Modules/Sitemaps/GenHTTP.Modules.Sitemaps.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/StaticWebsites/GenHTTP.Modules.StaticWebsites.csproj b/Modules/StaticWebsites/GenHTTP.Modules.StaticWebsites.csproj index a45e4c99..86f854ff 100644 --- a/Modules/StaticWebsites/GenHTTP.Modules.StaticWebsites.csproj +++ b/Modules/StaticWebsites/GenHTTP.Modules.StaticWebsites.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/VirtualHosting/GenHTTP.Modules.VirtualHosting.csproj b/Modules/VirtualHosting/GenHTTP.Modules.VirtualHosting.csproj index 450b4e5c..b0c59b3d 100644 --- a/Modules/VirtualHosting/GenHTTP.Modules.VirtualHosting.csproj +++ b/Modules/VirtualHosting/GenHTTP.Modules.VirtualHosting.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Webservices/GenHTTP.Modules.Webservices.csproj b/Modules/Webservices/GenHTTP.Modules.Webservices.csproj index 4f0acb5d..5a0e6a62 100644 --- a/Modules/Webservices/GenHTTP.Modules.Webservices.csproj +++ b/Modules/Webservices/GenHTTP.Modules.Webservices.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Modules/Websites/GenHTTP.Modules.Websites.csproj b/Modules/Websites/GenHTTP.Modules.Websites.csproj index 9dfe56bd..843fb771 100644 --- a/Modules/Websites/GenHTTP.Modules.Websites.csproj +++ b/Modules/Websites/GenHTTP.Modules.Websites.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli diff --git a/Packages/GenHTTP.Core.nuspec b/Packages/GenHTTP.Core.nuspec index 2c3f3587..8a21c0da 100644 --- a/Packages/GenHTTP.Core.nuspec +++ b/Packages/GenHTTP.Core.nuspec @@ -3,7 +3,7 @@ GenHTTP.Core - 8.5.0 + 8.6.0 Basic dependencies for projects using the GenHTTP framework, including the engine itself diff --git a/Testing/Testing/GenHTTP.Testing.csproj b/Testing/Testing/GenHTTP.Testing.csproj index 0ae667ab..1a5e77ae 100644 --- a/Testing/Testing/GenHTTP.Testing.csproj +++ b/Testing/Testing/GenHTTP.Testing.csproj @@ -8,9 +8,9 @@ enable true - 8.5.0.0 - 8.5.0.0 - 8.5.0 + 8.6.0.0 + 8.6.0.0 + 8.6.0 Andreas Nägeli From 2c6afaf5d3409e06fdc64971a43a84f55206118a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Tue, 4 Jun 2024 11:16:03 +0200 Subject: [PATCH 02/14] Improve parser tests and tracability (#499) --- API/Infrastructure/IServerCompanion.cs | 5 +- Engine/Hosting/ServerHost.cs | 2 +- Engine/Infrastructure/ConsoleCompanion.cs | 5 +- Engine/Infrastructure/Endpoints/EndPoint.cs | 4 +- .../Endpoints/EndpointCollection.cs | 2 +- .../Endpoints/SecureEndPoint.cs | 5 +- Engine/Infrastructure/ThreadedServer.cs | 2 +- Engine/Protocol/ChunkedStream.cs | 2 - Engine/Protocol/ClientHandler.cs | 11 +- Engine/Protocol/Parser/RequestParser.cs | 5 + Engine/Protocol/ResponseHandler.cs | 2 +- Engine/Protocol/SocketExtensions.cs | 14 ++ Modules/IO/ResponseBuilderExtensions.cs | 1 - Modules/Reflection/MethodCollection.cs | 2 +- Testing/Acceptance/Engine/CompanionTests.cs | 143 +++++++++--------- Testing/Acceptance/Engine/WireTests.cs | 28 ++++ 16 files changed, 139 insertions(+), 94 deletions(-) create mode 100644 Engine/Protocol/SocketExtensions.cs diff --git a/API/Infrastructure/IServerCompanion.cs b/API/Infrastructure/IServerCompanion.cs index a172f62c..2464d24e 100644 --- a/API/Infrastructure/IServerCompanion.cs +++ b/API/Infrastructure/IServerCompanion.cs @@ -1,5 +1,5 @@ using System; - +using System.Net; using GenHTTP.Api.Protocol; namespace GenHTTP.Api.Infrastructure @@ -77,8 +77,9 @@ public interface IServerCompanion /// Will be invoked if an error occurred within the server engine. /// /// The scope of the error + /// The endpoint of the client which caused this error (if any) /// The actual exception which occurred - void OnServerError(ServerErrorScope scope, Exception error); + void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error); } diff --git a/Engine/Hosting/ServerHost.cs b/Engine/Hosting/ServerHost.cs index da2f0d79..fd25d2c5 100644 --- a/Engine/Hosting/ServerHost.cs +++ b/Engine/Hosting/ServerHost.cs @@ -94,7 +94,7 @@ public int Run() if (companion is not null) { - companion.OnServerError(ServerErrorScope.General, e); + companion.OnServerError(ServerErrorScope.General, null, e); } else { diff --git a/Engine/Infrastructure/ConsoleCompanion.cs b/Engine/Infrastructure/ConsoleCompanion.cs index 578b28ba..c6a84012 100644 --- a/Engine/Infrastructure/ConsoleCompanion.cs +++ b/Engine/Infrastructure/ConsoleCompanion.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using GenHTTP.Api.Infrastructure; using GenHTTP.Api.Protocol; @@ -14,9 +15,9 @@ public void OnRequestHandled(IRequest request, IResponse response) Console.WriteLine($"REQ - {request.Client.IPAddress} - {request.Method.RawMethod} {request.Target.Path} - {response.Status.RawStatus} - {response.ContentLength ?? 0}"); } - public void OnServerError(ServerErrorScope scope, Exception error) + public void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error) { - Console.WriteLine($"ERR - {scope} - {error}"); + Console.WriteLine($"ERR - {client : 'n/a'} - {scope} - {error}"); } } diff --git a/Engine/Infrastructure/Endpoints/EndPoint.cs b/Engine/Infrastructure/Endpoints/EndPoint.cs index d7d3a139..8ae4e351 100644 --- a/Engine/Infrastructure/Endpoints/EndPoint.cs +++ b/Engine/Infrastructure/Endpoints/EndPoint.cs @@ -86,7 +86,7 @@ private async Task Listen() { if (!shuttingDown) { - Server.Companion?.OnServerError(ServerErrorScope.ServerConnection, e); + Server.Companion?.OnServerError(ServerErrorScope.ServerConnection, null, e); } } } @@ -130,7 +130,7 @@ protected virtual void Dispose(bool disposing) } catch (Exception e) { - Server.Companion?.OnServerError(ServerErrorScope.ServerConnection, e); + Server.Companion?.OnServerError(ServerErrorScope.ServerConnection, null, e); } } diff --git a/Engine/Infrastructure/Endpoints/EndpointCollection.cs b/Engine/Infrastructure/Endpoints/EndpointCollection.cs index ecb13029..837c8307 100644 --- a/Engine/Infrastructure/Endpoints/EndpointCollection.cs +++ b/Engine/Infrastructure/Endpoints/EndpointCollection.cs @@ -68,7 +68,7 @@ public void Dispose() } catch (Exception e) { - Server.Companion?.OnServerError(ServerErrorScope.ServerConnection, e); + Server.Companion?.OnServerError(ServerErrorScope.ServerConnection, null, e); } } diff --git a/Engine/Infrastructure/Endpoints/SecureEndPoint.cs b/Engine/Infrastructure/Endpoints/SecureEndPoint.cs index f1277202..9c326097 100644 --- a/Engine/Infrastructure/Endpoints/SecureEndPoint.cs +++ b/Engine/Infrastructure/Endpoints/SecureEndPoint.cs @@ -9,6 +9,7 @@ using GenHTTP.Api.Infrastructure; using GenHTTP.Engine.Infrastructure.Configuration; +using GenHTTP.Engine.Protocol; using GenHTTP.Engine.Utilities; using PooledAwait; @@ -69,7 +70,7 @@ protected override async PooledValueTask Accept(Socket client) } catch (Exception e) { - Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, e); + Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, client.GetAddress(), e); } } } @@ -86,7 +87,7 @@ protected override async PooledValueTask Accept(Socket client) } catch (Exception e) { - Server.Companion?.OnServerError(ServerErrorScope.Security, e); + Server.Companion?.OnServerError(ServerErrorScope.Security, client.GetAddress(), e); return null; } diff --git a/Engine/Infrastructure/ThreadedServer.cs b/Engine/Infrastructure/ThreadedServer.cs index 83c6471f..2658fe1b 100644 --- a/Engine/Infrastructure/ThreadedServer.cs +++ b/Engine/Infrastructure/ThreadedServer.cs @@ -57,7 +57,7 @@ private static async ValueTask PrepareHandlerAsync(IHandler handler, IServerComp } catch (Exception e) { - companion?.OnServerError(ServerErrorScope.General, e); + companion?.OnServerError(ServerErrorScope.General, null, e); } } diff --git a/Engine/Protocol/ChunkedStream.cs b/Engine/Protocol/ChunkedStream.cs index d659ccb8..3906ab64 100644 --- a/Engine/Protocol/ChunkedStream.cs +++ b/Engine/Protocol/ChunkedStream.cs @@ -1,7 +1,5 @@ using System; -using System.Buffers; using System.IO; -using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/Engine/Protocol/ClientHandler.cs b/Engine/Protocol/ClientHandler.cs index 358a9f08..4c6d8aa5 100644 --- a/Engine/Protocol/ClientHandler.cs +++ b/Engine/Protocol/ClientHandler.cs @@ -2,7 +2,6 @@ using System.Buffers; using System.IO; using System.IO.Pipelines; -using System.Net; using System.Net.Sockets; using GenHTTP.Api.Infrastructure; @@ -77,7 +76,7 @@ internal async PooledValueTask Run() } catch (Exception e) { - Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, e); + Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, Connection.GetAddress(), e); } finally { @@ -87,7 +86,7 @@ internal async PooledValueTask Run() } catch (Exception e) { - Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, e); + Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, Connection.GetAddress(), e); } try @@ -100,7 +99,7 @@ internal async PooledValueTask Run() } catch (Exception e) { - Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, e); + Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, Connection.GetAddress(), e); } } } @@ -146,9 +145,7 @@ private async PooledValueTask HandlePipe(PipeReader reader) private async PooledValueTask HandleRequest(RequestBuilder builder, bool dataRemaining) { - var address = (Connection.RemoteEndPoint as IPEndPoint)?.Address; - - using var request = builder.Connection(Server, EndPoint, address).Build(); + using var request = builder.Connection(Server, EndPoint, Connection.GetAddress()).Build(); KeepAlive ??= request["Connection"]?.Equals("Keep-Alive", StringComparison.InvariantCultureIgnoreCase) ?? (request.ProtocolType == HttpProtocol.Http_1_1); diff --git a/Engine/Protocol/Parser/RequestParser.cs b/Engine/Protocol/Parser/RequestParser.cs index b03ef1ec..d58b8496 100644 --- a/Engine/Protocol/Parser/RequestParser.cs +++ b/Engine/Protocol/Parser/RequestParser.cs @@ -47,6 +47,11 @@ internal RequestParser(NetworkConfiguration configuration) { if (!await Type(buffer)) { + if (!buffer.Data.IsEmpty) + { + throw new ProtocolException("Unable to read HTTP verb from request line."); + } + return null; } diff --git a/Engine/Protocol/ResponseHandler.cs b/Engine/Protocol/ResponseHandler.cs index 0114d5be..20609eb0 100644 --- a/Engine/Protocol/ResponseHandler.cs +++ b/Engine/Protocol/ResponseHandler.cs @@ -79,7 +79,7 @@ internal async ValueTask Handle(IRequest? request, IResponse response, boo } catch (Exception e) { - Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, e); + Server.Companion?.OnServerError(ServerErrorScope.ClientConnection, request?.Client.IPAddress, e); return false; } } diff --git a/Engine/Protocol/SocketExtensions.cs b/Engine/Protocol/SocketExtensions.cs new file mode 100644 index 00000000..3202cc6d --- /dev/null +++ b/Engine/Protocol/SocketExtensions.cs @@ -0,0 +1,14 @@ +using System.Net; +using System.Net.Sockets; + +namespace GenHTTP.Engine.Protocol +{ + + internal static class SocketExtensions + { + + public static IPAddress? GetAddress(this Socket socket) => (socket.RemoteEndPoint as IPEndPoint)?.Address; + + } + +} diff --git a/Modules/IO/ResponseBuilderExtensions.cs b/Modules/IO/ResponseBuilderExtensions.cs index b9bf5c59..f5e0f3b9 100644 --- a/Modules/IO/ResponseBuilderExtensions.cs +++ b/Modules/IO/ResponseBuilderExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Buffers; using System.IO; using System.Threading.Tasks; diff --git a/Modules/Reflection/MethodCollection.cs b/Modules/Reflection/MethodCollection.cs index 9a55929e..90c7fd78 100644 --- a/Modules/Reflection/MethodCollection.cs +++ b/Modules/Reflection/MethodCollection.cs @@ -46,7 +46,7 @@ public MethodCollection(IHandler parent, IEnumerable 1) { // if there is only one non-wildcard, use this one - var nonWildcards = methods.Where(m => m.Routing.IsWildcard == false).ToList(); + var nonWildcards = methods.Where(m => !m.Routing.IsWildcard).ToList(); if (nonWildcards.Count == 1) { diff --git a/Testing/Acceptance/Engine/CompanionTests.cs b/Testing/Acceptance/Engine/CompanionTests.cs index 64206bf2..b0fb43b9 100644 --- a/Testing/Acceptance/Engine/CompanionTests.cs +++ b/Testing/Acceptance/Engine/CompanionTests.cs @@ -1,71 +1,72 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using GenHTTP.Api.Infrastructure; -using GenHTTP.Api.Protocol; -using GenHTTP.Modules.Layouting; - -namespace GenHTTP.Testing.Acceptance.Engine -{ - - [TestClass] - public sealed class CompanionTests - { - - private class CustomCompanion : IServerCompanion - { - - public bool Called { get; private set; } - - public void OnRequestHandled(IRequest request, IResponse response) - { - Called = true; - } - - public void OnServerError(ServerErrorScope scope, Exception error) - { - Called = true; - } - - } - - /// - /// As a developer, I want to configure the server to easily log to the console. - /// - [TestMethod] - public async Task TestConsole() - { - using var runner = new TestHost(Layout.Create()); - - runner.Host.Console().Start(); - - using var __ = await runner.GetResponseAsync(); - } - - /// - /// As a developer, I want to add custom companions to get notified by server actions. - /// - [TestMethod] - public async Task TestCustom() - { - using var runner = new TestHost(Layout.Create()); - - var companion = new CustomCompanion(); - - runner.Host.Companion(companion).Start(); - - using var __ = await runner.GetResponseAsync(); - - // the companion is called _after_ the response has been sent - // bad hack, reconsider - Thread.Sleep(50); - - Assert.IsTrue(companion.Called); - } - - } - -} +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using GenHTTP.Api.Infrastructure; +using GenHTTP.Api.Protocol; +using GenHTTP.Modules.Layouting; + +namespace GenHTTP.Testing.Acceptance.Engine +{ + + [TestClass] + public sealed class CompanionTests + { + + private class CustomCompanion : IServerCompanion + { + + public bool Called { get; private set; } + + public void OnRequestHandled(IRequest request, IResponse response) + { + Called = true; + } + + public void OnServerError(ServerErrorScope scope, IPAddress? client, Exception error) + { + Called = true; + } + + } + + /// + /// As a developer, I want to configure the server to easily log to the console. + /// + [TestMethod] + public async Task TestConsole() + { + using var runner = new TestHost(Layout.Create()); + + runner.Host.Console().Start(); + + using var __ = await runner.GetResponseAsync(); + } + + /// + /// As a developer, I want to add custom companions to get notified by server actions. + /// + [TestMethod] + public async Task TestCustom() + { + using var runner = new TestHost(Layout.Create()); + + var companion = new CustomCompanion(); + + runner.Host.Companion(companion).Start(); + + using var __ = await runner.GetResponseAsync(); + + // the companion is called _after_ the response has been sent + // bad hack, reconsider + Thread.Sleep(50); + + Assert.IsTrue(companion.Called); + } + + } + +} diff --git a/Testing/Acceptance/Engine/WireTests.cs b/Testing/Acceptance/Engine/WireTests.cs index e21a71b1..dcf97c88 100644 --- a/Testing/Acceptance/Engine/WireTests.cs +++ b/Testing/Acceptance/Engine/WireTests.cs @@ -110,6 +110,34 @@ public async Task TestNoKeepAliveForConnectionClose() AssertX.DoesNotContain("Keep-Alive", result); } + [TestMethod] + public async Task TestNonHttp() + { + using var host = TestHost.Run(Layout.Create()); + + var result = await SendAsync(host, (w) => + { + w.Write($"{{abc}}"); + }); + + AssertX.Contains("400 Bad Request", result); + AssertX.Contains("Unable to read HTTP verb from request line", result); + } + + [TestMethod] + public async Task TestNonHttpButText() + { + using var host = TestHost.Run(Layout.Create()); + + var result = await SendAsync(host, (w) => + { + w.Write($"This is no HTTP request but text"); + }); + + AssertX.Contains("400 Bad Request", result); + AssertX.Contains("HTTP protocol version expected", result); + } + #endregion #region Helpers From ff9a9adc870d3195eec6ea8054bf69c017222c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Tue, 4 Jun 2024 11:28:20 +0200 Subject: [PATCH 03/14] Mark web functionality as deprecated in the readme --- README.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3546226a..6de17a7a 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,16 @@ # GenHTTP Webserver -GenHTTP is a lightweight web server written in pure C# with only a few dependencies to 3rd-party libraries. The main purpose of this project is to quickly create feature rich web applications and REST web services written in .NET 6/7/8, allowing developers to concentrate on the functionality rather than on messing around with configuration files, CSS or bundling and minifying JS files. Projects are mainly written in .NET, which allows C# developers to use their familiar toolset in web application development as well. - -![Creating a web application with GenHTTP](https://github.com/Kaliumhexacyanoferrat/GenHTTP/assets/4992119/ca811aac-d598-4a49-80bc-11797ad75ecb) - -As an example, the website of this project is hosted on a Raspberry Pi: [genhttp.org](https://genhttp.org/) +GenHTTP is a lightweight web server written in pure C# with only a few dependencies to 3rd-party libraries. The main purpose of this project is to quickly create web services written in .NET 6/7/8, allowing developers to concentrate on the functionality rather than on messing around with configuration files. [![CI](https://github.com/Kaliumhexacyanoferrat/GenHTTP/actions/workflows/ci.yml/badge.svg)](https://github.com/Kaliumhexacyanoferrat/GenHTTP/actions/workflows/ci.yml) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=GenHTTP&metric=coverage)](https://sonarcloud.io/dashboard?id=GenHTTP) [![nuget Package](https://img.shields.io/nuget/v/GenHTTP.Core.svg)](https://www.nuget.org/packages/GenHTTP.Core/) [](https://discord.gg/cW6tPJS7nt) [![Discord](https://discordapp.com/api/guilds/1177529388229734410/widget.png?style=shield)](https://discord.gg/GwtDyUpkpV) ## 🚀 Features -- Setup new webservices or websites in a couple of minutes using [project templates](https://genhttp.org/documentation/content/templates) -- Embed web services and applications into your existing console, service, WPF, WinForms, WinUI, MAUI or Uno application +- Setup new webservices in a couple of minutes using [project templates](https://genhttp.org/documentation/content/templates) +- Embed web services into your existing console, service, WPF, WinForms, WinUI, MAUI or Uno application - Projects are fully described in code - no configuration files needed, no magical behavior you need to learn -- [Optimized](https://genhttp.org/features) out of the box (e.g. by bundling resources or compressing results) +- [Optimized](https://genhttp.org/features) out of the box - Small memory and storage [footprint](https://genhttp.org/features#footprint) -- Several [themes](https://github.com/Kaliumhexacyanoferrat/GenHTTP.Themes) available to be chosen from - Grade A+ security level according to SSL Labs ## 📖 Getting Started @@ -34,10 +29,10 @@ To create a project by using the terminal, create a new folder for your app and |---|---|---| | REST Webservice | `dotnet new genhttp-webservice` | [Webservices](https://genhttp.org/documentation/content/webservices) | | REST Webservice (single file) | `dotnet new genhttp-webservice-minimal` | [Functional Handlers](https://genhttp.org/documentation/content/functional) | -| Website | `dotnet new genhttp-website` | [Websites](https://genhttp.org/documentation/content/websites) | +| Website \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | `dotnet new genhttp-website` | [Websites](https://genhttp.org/documentation/content/websites) | | Website (Static HTML) | `dotnet new genhttp-website-static` | [Statics Websites](https://genhttp.org/documentation/content/static-websites) | -| Website (MVC + Razor) | `dotnet new genhttp-website-mvc-razor` | [Controllers (MVC)](https://genhttp.org/documentation/content/controllers) | -| Website (MVC + Scriban) | `dotnet new genhttp-website-mvc-scriban` | [Controllers (MVC)](https://genhttp.org/documentation/content/controllers) | +| Website (MVC + Razor) \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | `dotnet new genhttp-website-mvc-razor` | [Controllers (MVC)](https://genhttp.org/documentation/content/controllers) | +| Website (MVC + Scriban) \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | `dotnet new genhttp-website-mvc-scriban` | [Controllers (MVC)](https://genhttp.org/documentation/content/controllers) | | Single Page Application (SPA) | `dotnet new genhttp-spa` | [Single Page Applications (SPA)](https://genhttp.org/documentation/content/single-page-applications) | After the project has been created, you can run it via `dotnet run` and access the server via http://localhost:8080. @@ -59,7 +54,7 @@ When you run this sample it can be accessed in the browser via http://localhost: ### Next Steps -The [documentation](https://genhttp.org/documentation/) provides a step-by-step starting guide as well as additional information on how to implement [webservices](https://genhttp.org/documentation/content/webservices), [minimal webservices](https://genhttp.org/documentation/content/functional), [websites](https://genhttp.org/documentation/content/websites), [MVC style projects](https://genhttp.org/documentation/content/controllers), or [single page applications](https://genhttp.org/documentation/content/single-page-applications) and how to [host your application](https://genhttp.org/documentation/hosting/) via Docker. +The [documentation](https://genhttp.org/documentation/) provides a step-by-step starting guide as well as additional information on how to implement [webservices](https://genhttp.org/documentation/content/webservices), [minimal webservices](https://genhttp.org/documentation/content/functional), [static websites](https://genhttp.org/documentation/content/static-websites), or [single page applications](https://genhttp.org/documentation/content/single-page-applications) and how to [host your application](https://genhttp.org/documentation/hosting/) via Docker. If you encounter issues implementing your application, feel free to [join our Discord community](https://discord.gg/GwtDyUpkpV) to get help. @@ -80,7 +75,6 @@ This will build the playground project launcher with all the server dependencies Writing a general purpose web application server is a tremendous task, so any contribution is very welcome. Besides extending the server core, you might want to - Extend the content capabilities of the server (e.g. by adding a new serialization format or rendering engine) -- Add a new [theme](https://github.com/Kaliumhexacyanoferrat/GenHTTP.Themes) - Refine our [project templates](https://genhttp.org/documentation/content/templates) - Perform code reviews - Analyze the performance or security of the server From d2c4efc472f00cb9205de16820bb5d19190902d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Wed, 5 Jun 2024 12:04:38 +0200 Subject: [PATCH 04/14] Revert to old advancing mode (#501) --- Engine/Protocol/RequestBuffer.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Engine/Protocol/RequestBuffer.cs b/Engine/Protocol/RequestBuffer.cs index f974271a..4523271a 100644 --- a/Engine/Protocol/RequestBuffer.cs +++ b/Engine/Protocol/RequestBuffer.cs @@ -57,11 +57,6 @@ internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration) { if (ReadRequired || force) { - if (_Data != null) - { - Reader.AdvanceTo(_Data.Value.Start); - } - if (Cancellation is null) { Cancellation = new(); @@ -87,14 +82,10 @@ internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration) return Data.Length; } - internal void Advance(SequencePosition position) - { - _Data = Data.Slice(position); - } - internal void Advance(long bytes) { _Data = Data.Slice(bytes); + Reader.AdvanceTo(_Data.Value.Start); } #endregion From 8ad0c0c6fcb5c23813c9e4d3498b5388b1930500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Wed, 5 Jun 2024 15:38:30 +0200 Subject: [PATCH 05/14] Simplify and clarify reverse proxy tests (#502) --- Testing/Acceptance/Modules/ReverseProxyTests.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Testing/Acceptance/Modules/ReverseProxyTests.cs b/Testing/Acceptance/Modules/ReverseProxyTests.cs index e5dd57a4..e1b6533f 100644 --- a/Testing/Acceptance/Modules/ReverseProxyTests.cs +++ b/Testing/Acceptance/Modules/ReverseProxyTests.cs @@ -41,11 +41,10 @@ private TestSetup(TestHost source, TestHost target) public static TestSetup Create(Func response) { - // server hosting the actuall web app - var testServer = new TestHost(Layout.Create()); + // server hosting the actual web app + var testServer = new TestHost(Layout.Create(), defaults: false); testServer.Host.Handler(new ProxiedRouter(response).Wrap()) - .Development() .Start(); // proxying server @@ -57,8 +56,6 @@ public static TestSetup Create(Func response) var runner = new TestHost(Layout.Create()); runner.Host.Handler(proxy) - .Development() - .Defaults() .Start(); return new TestSetup(runner, testServer); From 55a5c46f9b53a1f3445e07b03b80ec0209a42138 Mon Sep 17 00:00:00 2001 From: AKaliumhexacyanoferrat Date: Wed, 5 Jun 2024 18:54:12 +0200 Subject: [PATCH 06/14] Remove unused code --- API/Infrastructure/NetworkException.cs | 20 -------------------- Engine/Protocol/Pattern.cs | 16 ---------------- 2 files changed, 36 deletions(-) delete mode 100644 API/Infrastructure/NetworkException.cs delete mode 100644 Engine/Protocol/Pattern.cs diff --git a/API/Infrastructure/NetworkException.cs b/API/Infrastructure/NetworkException.cs deleted file mode 100644 index 0202a6d7..00000000 --- a/API/Infrastructure/NetworkException.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace GenHTTP.Api.Infrastructure -{ - - /// - /// Thrown if a network-level exception occurs. - /// - [Serializable] - public class NetworkException : Exception - { - - public NetworkException(string reason, Exception? inner = null) : base(reason, inner) - { - - } - - } - -} diff --git a/Engine/Protocol/Pattern.cs b/Engine/Protocol/Pattern.cs deleted file mode 100644 index 5bf9cd5b..00000000 --- a/Engine/Protocol/Pattern.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.RegularExpressions; - -namespace GenHTTP.Engine -{ - - /// - /// Regular expressions used by this server. - /// - internal static class Pattern - { - - public static readonly Regex GET_PARAMETER = new("([^&=]+)=([^&]*)", RegexOptions.Compiled); - - } - -} From 4c3228ab7d9c87925c3794bdd3c4891bf3fdee04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Wed, 19 Jun 2024 09:06:52 +0200 Subject: [PATCH 07/14] Pick up forwardings from legacy headers as well (#504) --- API/Protocol/IRequest.cs | 4 + Engine/Protocol/ForwardingCollection.cs | 64 ++++++++++++-- Engine/Protocol/RequestBuilder.cs | 5 ++ Testing/Acceptance/Engine/ForwardingTests.cs | 88 ++++++++++++++++++++ 4 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 Testing/Acceptance/Engine/ForwardingTests.cs diff --git a/API/Protocol/IRequest.cs b/API/Protocol/IRequest.cs index e5a8cd3e..3be74196 100644 --- a/API/Protocol/IRequest.cs +++ b/API/Protocol/IRequest.cs @@ -96,6 +96,10 @@ public interface IRequest : IDisposable /// If the request has been forwarded by one or more proxies, this collection may contain /// additional information about the initial request by the originating client. /// + /// + /// Use to quickly access the requesting client without the need + /// of scrolling through the forwardings. + /// IForwardingCollection Forwardings { get; } /// diff --git a/Engine/Protocol/ForwardingCollection.cs b/Engine/Protocol/ForwardingCollection.cs index 2a6715ec..4d8cc0d0 100644 --- a/Engine/Protocol/ForwardingCollection.cs +++ b/Engine/Protocol/ForwardingCollection.cs @@ -15,6 +15,10 @@ internal sealed class ForwardingCollection : List, IForwardingCollec { private const int DEFAULT_SIZE = 1; + private const string HEADER_FOR = "X-Forwarded-For"; + private const string HEADER_HOST = "X-Forwarded-Host"; + private const string HEADER_PROTO = "X-Forwarded-Proto"; + internal ForwardingCollection() : base(DEFAULT_SIZE) { @@ -22,6 +26,29 @@ internal ForwardingCollection() : base(DEFAULT_SIZE) internal void Add(string header) => AddRange(Parse(header)); + internal void TryAddLegacy(RequestHeaderCollection headers) + { + IPAddress? address = null; + ClientProtocol? protocol = null; + + headers.TryGetValue(HEADER_HOST, out var host); + + if (headers.TryGetValue(HEADER_FOR, out var stringAddress)) + { + address = ParseAddress(stringAddress); + } + + if (headers.TryGetValue(HEADER_PROTO, out var stringProtocol)) + { + protocol = ParseProtocol(stringProtocol); + } + + if ((address is not null) || (host is not null) || (protocol is not null)) + { + Add(new Forwarding(address, host, protocol)); + } + } + private static IEnumerable Parse(string value) { var forwardings = value.Split(',', StringSplitOptions.RemoveEmptyEntries); @@ -46,10 +73,7 @@ private static IEnumerable Parse(string value) if (key == "for") { - if (!IPAddress.TryParse(val, out address)) - { - address = null; - } + address = ParseAddress(val); } else if (key == "host") { @@ -57,7 +81,7 @@ private static IEnumerable Parse(string value) } else if (key == "proto") { - protocol = string.Equals(val, "https", StringComparison.OrdinalIgnoreCase) ? ClientProtocol.HTTPS : ClientProtocol.HTTP; + protocol = ParseProtocol(val); } } } @@ -69,6 +93,36 @@ private static IEnumerable Parse(string value) } } + private static ClientProtocol? ParseProtocol(string? protocol) + { + if (protocol != null) + { + if (string.Equals(protocol, "https", StringComparison.OrdinalIgnoreCase)) + { + return ClientProtocol.HTTPS; + } + else if (string.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) + { + return ClientProtocol.HTTP; + } + } + + return null; + } + + private static IPAddress? ParseAddress(string? address) + { + if (address != null) + { + if (IPAddress.TryParse(address, out var ip)) + { + return ip; + } + } + + return null; + } + } } diff --git a/Engine/Protocol/RequestBuilder.cs b/Engine/Protocol/RequestBuilder.cs index 041d51ab..8f0b804f 100644 --- a/Engine/Protocol/RequestBuilder.cs +++ b/Engine/Protocol/RequestBuilder.cs @@ -154,6 +154,11 @@ public IRequest Build() throw new ProtocolException("Mandatory 'Host' header is missing from the request"); } + if (_Forwardings is null) + { + Forwardings.TryAddLegacy(Headers); + } + var localClient = new ClientConnection(_Address, protocol, host); var client = DetermineClient() ?? localClient; diff --git a/Testing/Acceptance/Engine/ForwardingTests.cs b/Testing/Acceptance/Engine/ForwardingTests.cs new file mode 100644 index 00000000..370774c8 --- /dev/null +++ b/Testing/Acceptance/Engine/ForwardingTests.cs @@ -0,0 +1,88 @@ +using System.Linq; +using System.Threading.Tasks; + +using GenHTTP.Api.Protocol; +using GenHTTP.Modules.Functional; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GenHTTP.Testing.Acceptance.Engine +{ + + [TestClass] + public sealed class ForwardingTests + { + + [TestMethod] + public async Task TestModern() + { + var responder = Inline.Create().Get((IRequest request) => $"{request.Client}"); + + using var host = TestHost.Run(responder); + + var request = host.GetRequest(); + + request.Headers.Add("Forwarded", "for=85.192.1.5; host=google.com; proto=https"); + + using var response = await host.GetResponseAsync(request); + + Assert.AreEqual("ClientConnection { IPAddress = 85.192.1.5, Protocol = HTTPS, Host = google.com }", await response.GetContentAsync()); + } + + [TestMethod] + public async Task TestLegacy() + { + var responder = Inline.Create().Get((IRequest request) => $"{request.Client}"); + + using var host = TestHost.Run(responder); + + var request = host.GetRequest(); + + request.Headers.Add("X-Forwarded-For", "85.192.1.5"); + request.Headers.Add("X-Forwarded-Host", "google.com"); + request.Headers.Add("X-Forwarded-Proto", "http"); + + using var response = await host.GetResponseAsync(request); + + Assert.AreEqual("ClientConnection { IPAddress = 85.192.1.5, Protocol = HTTP, Host = google.com }", await response.GetContentAsync()); + } + + [TestMethod] + public async Task TestBoth() + { + var responder = Inline.Create().Get((IRequest request) => $"{request.Client}"); + + using var host = TestHost.Run(responder); + + var request = host.GetRequest(); + + request.Headers.Add("Forwarded", "for=85.192.1.1; host=google.com; proto=https"); + + request.Headers.Add("X-Forwarded-For", "85.192.1.2"); + request.Headers.Add("X-Forwarded-Host", "google2.com"); + request.Headers.Add("X-Forwarded-Proto", "http"); + + using var response = await host.GetResponseAsync(request); + + Assert.AreEqual("ClientConnection { IPAddress = 85.192.1.1, Protocol = HTTPS, Host = google.com }", await response.GetContentAsync()); + } + + [TestMethod] + public async Task TestInvalid() + { + var responder = Inline.Create().Get((IRequest request) => $"{request.Forwardings.First().ToString()}"); + + using var host = TestHost.Run(responder); + + var request = host.GetRequest(); + + request.Headers.Add("Forwarded", "for=google.com; host=google.com; proto=google.com"); + + using var response = await host.GetResponseAsync(request); + + Assert.AreEqual("Forwarding { For = , Host = google.com, Protocol = }", await response.GetContentAsync()); + } + + } + +} From ead6303fb062cb821914102694539dbddeb42496 Mon Sep 17 00:00:00 2001 From: AKaliumhexacyanoferrat Date: Thu, 27 Jun 2024 15:55:49 +0200 Subject: [PATCH 08/14] Bump dependencies --- Modules/Minification/GenHTTP.Modules.Minification.csproj | 2 +- Modules/Pages/GenHTTP.Modules.Pages.csproj | 2 +- Testing/Acceptance/GenHTTP.Testing.Acceptance.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/Minification/GenHTTP.Modules.Minification.csproj b/Modules/Minification/GenHTTP.Modules.Minification.csproj index 793b2c37..20727de8 100644 --- a/Modules/Minification/GenHTTP.Modules.Minification.csproj +++ b/Modules/Minification/GenHTTP.Modules.Minification.csproj @@ -45,7 +45,7 @@ - + diff --git a/Modules/Pages/GenHTTP.Modules.Pages.csproj b/Modules/Pages/GenHTTP.Modules.Pages.csproj index d13c1797..5c580a1c 100644 --- a/Modules/Pages/GenHTTP.Modules.Pages.csproj +++ b/Modules/Pages/GenHTTP.Modules.Pages.csproj @@ -45,7 +45,7 @@ - + diff --git a/Testing/Acceptance/GenHTTP.Testing.Acceptance.csproj b/Testing/Acceptance/GenHTTP.Testing.Acceptance.csproj index 141546a3..9f61dd15 100644 --- a/Testing/Acceptance/GenHTTP.Testing.Acceptance.csproj +++ b/Testing/Acceptance/GenHTTP.Testing.Acceptance.csproj @@ -37,10 +37,10 @@ - + - - + + all From 33a10df2cbb45accd2afbaec986fc9e036759754 Mon Sep 17 00:00:00 2001 From: AKaliumhexacyanoferrat Date: Thu, 27 Jun 2024 16:02:37 +0200 Subject: [PATCH 09/14] Fix links to website --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6de17a7a..d813ff6d 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,10 @@ To create a project by using the terminal, create a new folder for your app and | Template | Command | Documentation | |---|---|---| -| REST Webservice | `dotnet new genhttp-webservice` | [Webservices](https://genhttp.org/documentation/content/webservices) | -| REST Webservice (single file) | `dotnet new genhttp-webservice-minimal` | [Functional Handlers](https://genhttp.org/documentation/content/functional) | -| Website \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | `dotnet new genhttp-website` | [Websites](https://genhttp.org/documentation/content/websites) | -| Website (Static HTML) | `dotnet new genhttp-website-static` | [Statics Websites](https://genhttp.org/documentation/content/static-websites) | -| Website (MVC + Razor) \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | `dotnet new genhttp-website-mvc-razor` | [Controllers (MVC)](https://genhttp.org/documentation/content/controllers) | -| Website (MVC + Scriban) \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | `dotnet new genhttp-website-mvc-scriban` | [Controllers (MVC)](https://genhttp.org/documentation/content/controllers) | -| Single Page Application (SPA) | `dotnet new genhttp-spa` | [Single Page Applications (SPA)](https://genhttp.org/documentation/content/single-page-applications) | +| REST Webservice | `dotnet new genhttp-webservice` | [Webservices](https://genhttp.org/documentation/content/frameworks/webservices/) | +| REST Webservice (single file) | `dotnet new genhttp-webservice-minimal` | [Functional Handlers](https://genhttp.org/documentation/content/frameworks/functional/) | +| Website (Static HTML) | `dotnet new genhttp-website-static` | [Statics Websites](https://genhttp.org/documentation/content/frameworks/static-websites/) | +| Single Page Application (SPA) | `dotnet new genhttp-spa` | [Single Page Applications (SPA)](https://genhttp.org/documentation/content/frameworks/single-page-applications/) | After the project has been created, you can run it via `dotnet run` and access the server via http://localhost:8080. From 9db057da39319864e26762a03084fd1a124d7c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Thu, 27 Jun 2024 16:03:16 +0200 Subject: [PATCH 10/14] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d813ff6d..85d56fc2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ GenHTTP is a lightweight web server written in pure C# with only a few dependenc This section shows how to create a new project from scratch using project templates and how to extend your existing application by embedding the GenHTTP engine. +> [!NOTE] +> This is a brief overview to get you running. You might want to have a look at the [tutorials](https://genhttp.org/documentation/tutorials/) for detailed step-by-step guides. + ### New Project Project templates can be used to create apps for typical use cases with little effort. After installing the [.NET SDK](https://dotnet.microsoft.com/en-us/download) and the templates via `dotnet new -i GenHTTP.Templates` in the terminal, the templates are available via the console or directly in Visual Studio: From 9dd3e310de716fe86127c5c46fa4edf66fd41bdf Mon Sep 17 00:00:00 2001 From: AKaliumhexacyanoferrat Date: Fri, 28 Jun 2024 15:49:30 +0200 Subject: [PATCH 11/14] Add link to controller template --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 85d56fc2..45d6aeb2 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ To create a project by using the terminal, create a new folder for your app and |---|---|---| | REST Webservice | `dotnet new genhttp-webservice` | [Webservices](https://genhttp.org/documentation/content/frameworks/webservices/) | | REST Webservice (single file) | `dotnet new genhttp-webservice-minimal` | [Functional Handlers](https://genhttp.org/documentation/content/frameworks/functional/) | +| REST Webservice (controllers) | `dotnet new genhttp-webservice-controllers` | [Controllers](https://genhttp.org/documentation/content/frameworks/controllers) | | Website (Static HTML) | `dotnet new genhttp-website-static` | [Statics Websites](https://genhttp.org/documentation/content/frameworks/static-websites/) | | Single Page Application (SPA) | `dotnet new genhttp-spa` | [Single Page Applications (SPA)](https://genhttp.org/documentation/content/frameworks/single-page-applications/) | From 938e5599ea9dcdeab06b6c8c31f606c9816970d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Mon, 1 Jul 2024 09:01:14 +0200 Subject: [PATCH 12/14] Add test extensions to easily deserialize responses (#505) --- .../Serializers/SerializationRegistry.cs | 152 ++++++++++-------- Testing/Acceptance/Testing/ContentTests.cs | 63 ++++++++ Testing/Testing/ContentExtensions.cs | 71 ++++++++ Testing/Testing/GenHTTP.Testing.csproj | 2 + ...{TestExtensions.cs => HeaderExtensions.cs} | 72 ++++----- 5 files changed, 249 insertions(+), 111 deletions(-) create mode 100644 Testing/Acceptance/Testing/ContentTests.cs create mode 100644 Testing/Testing/ContentExtensions.cs rename Testing/Testing/{TestExtensions.cs => HeaderExtensions.cs} (60%) diff --git a/Modules/Conversion/Serializers/SerializationRegistry.cs b/Modules/Conversion/Serializers/SerializationRegistry.cs index 206d5771..ab5dd9c4 100644 --- a/Modules/Conversion/Serializers/SerializationRegistry.cs +++ b/Modules/Conversion/Serializers/SerializationRegistry.cs @@ -1,71 +1,81 @@ -using System.Collections.Generic; - -using GenHTTP.Api.Protocol; - -namespace GenHTTP.Modules.Conversion.Providers -{ - - /// - /// Registers formats that can be used to serialize and - /// deserialize objects sent to or received from a - /// service oriented handler. - /// - public sealed class SerializationRegistry - { - - #region Get-/Setters - - private FlexibleContentType Default { get; } - - private Dictionary Formats { get; } - - #endregion - - #region Initialization - - public SerializationRegistry(FlexibleContentType defaultType, - Dictionary formats) - { - Default = defaultType; - Formats = formats; - } - - #endregion - - #region Functionality - - public ISerializationFormat? GetDeserialization(IRequest request) - { - if (request.Headers.TryGetValue("Content-Type", out string? requested)) - { - return GetFormat(FlexibleContentType.Parse(requested)); - } - - return GetFormat(Default); - } - - public ISerializationFormat? GetSerialization(IRequest request) - { - if (request.Headers.TryGetValue("Accept", out string? accepted)) - { - return GetFormat(FlexibleContentType.Parse(accepted)) ?? GetFormat(Default); - } - - return GetFormat(Default); - } - - private ISerializationFormat? GetFormat(FlexibleContentType contentType) - { - if (Formats.TryGetValue(contentType, out var format)) - { - return format; - } - - return null; - } - - #endregion - - } - -} +using System.Collections.Generic; + +using GenHTTP.Api.Protocol; + +namespace GenHTTP.Modules.Conversion.Providers +{ + + /// + /// Registers formats that can be used to serialize and + /// deserialize objects sent to or received from a + /// service oriented handler. + /// + public sealed class SerializationRegistry + { + + #region Get-/Setters + + private FlexibleContentType Default { get; } + + private Dictionary Formats { get; } + + #endregion + + #region Initialization + + public SerializationRegistry(FlexibleContentType defaultType, + Dictionary formats) + { + Default = defaultType; + Formats = formats; + } + + #endregion + + #region Functionality + + public ISerializationFormat? GetDeserialization(IRequest request) + { + if (request.Headers.TryGetValue("Content-Type", out string? requested)) + { + return GetFormat(FlexibleContentType.Parse(requested)); + } + + return GetFormat(Default); + } + + public ISerializationFormat? GetSerialization(IRequest request) + { + if (request.Headers.TryGetValue("Accept", out string? accepted)) + { + return GetFormat(FlexibleContentType.Parse(accepted)) ?? GetFormat(Default); + } + + return GetFormat(Default); + } + + public ISerializationFormat? GetFormat(string? contentType) + { + if (contentType != null) + { + return GetFormat(FlexibleContentType.Parse(contentType)); + } + + return GetFormat(Default); + } + + private ISerializationFormat? GetFormat(FlexibleContentType contentType) + { + if (Formats.TryGetValue(contentType, out var format)) + { + return format; + } + + return null; + } + + #endregion + + } + +} diff --git a/Testing/Acceptance/Testing/ContentTests.cs b/Testing/Acceptance/Testing/ContentTests.cs new file mode 100644 index 00000000..0f41b058 --- /dev/null +++ b/Testing/Acceptance/Testing/ContentTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading.Tasks; +using GenHTTP.Api.Protocol; +using GenHTTP.Modules.Functional; +using GenHTTP.Modules.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GenHTTP.Testing.Acceptance.Testing +{ + + [TestClass] + public sealed class ContentTests + { + + public record class MyType(int ID); + + [TestMethod] + public async Task TestDeserialization() + { + var expectation = new MyType(42); + + var handler = Inline.Create() + .Get(() => expectation); + + using var host = TestHost.Run(handler); + + using var response = await host.GetResponseAsync(); + + Assert.AreEqual(expectation, await response.GetContentAsync()); + } + + [TestMethod] + public async Task TestNull() + { + var handler = Inline.Create() + .Get(() => (MyType?)null); + + using var host = TestHost.Run(handler); + + using var response = await host.GetResponseAsync(); + + Assert.IsNull(await response.GetOptionalContentAsync()); + } + + [TestMethod] + public async Task TestUnsupported() + { + var handler = Inline.Create() + .Get(() => new Result("Nah").Type(FlexibleContentType.Get("text/html"))); + + using var host = TestHost.Run(handler); + + using var response = await host.GetResponseAsync(); + + await Assert.ThrowsExceptionAsync(async () => + { + await response.GetOptionalContentAsync(); + }); + } + + } + +} diff --git a/Testing/Testing/ContentExtensions.cs b/Testing/Testing/ContentExtensions.cs new file mode 100644 index 00000000..990d4688 --- /dev/null +++ b/Testing/Testing/ContentExtensions.cs @@ -0,0 +1,71 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +using GenHTTP.Modules.Conversion; +using GenHTTP.Modules.Protobuf; + +namespace GenHTTP.Testing +{ + + public static class ContentExtensions + { + + /// + /// Reads the response body as a string. + /// + /// The response to read + /// The content of the HTTP response + public static async ValueTask GetContentAsync(this HttpResponseMessage response) => await response.Content.ReadAsStringAsync(); + + /// + /// Deserializes the payload of the HTTP response into the given type. + /// + /// The type of the payload to be read + /// The response to read the payload from + /// The deserialized payload of the response + /// Thrown if the format returned by the server is not supported + /// + /// This method supports all formats that ship with the GenHTTP framework (JSON, XML, form encoded, Protobuf) + /// and falls back to JSON if the server does not indicate a content type. + /// + public static async ValueTask GetContentAsync(this HttpResponseMessage response) + { + return await response.GetOptionalContentAsync() ?? throw new InvalidOperationException("Server did not return a result"); + } + + /// + /// Attempts to deserialize the payload of the HTTP response into the given type. + /// + /// The type of the payload to be read + /// The response to read the payload from + /// The deserialized payload of the response or null, if the server did not return data + /// Thrown if the format returned by the server is not supported + /// + /// This method supports all formats that ship with the GenHTTP framework (JSON, XML, form encoded, Protobuf) + /// and falls back to JSON if the server does not indicate a content type. + /// + public static async ValueTask GetOptionalContentAsync(this HttpResponseMessage response) + { + if (response.StatusCode == HttpStatusCode.NoContent) + { + return default; + } + + var type = response.GetContentHeader("Content-Type"); + + var registry = Serialization.Default() + .AddProtobuf() + .Build(); + + var format = registry.GetFormat(type) ?? throw new InvalidOperationException($"Unable to find deserializer for content type '{type}'"); + + using var body = await response.Content.ReadAsStreamAsync(); + + return (T?)await format.DeserializeAsync(body, typeof(T)); + } + + } + +} diff --git a/Testing/Testing/GenHTTP.Testing.csproj b/Testing/Testing/GenHTTP.Testing.csproj index 1a5e77ae..d40c8c24 100644 --- a/Testing/Testing/GenHTTP.Testing.csproj +++ b/Testing/Testing/GenHTTP.Testing.csproj @@ -48,6 +48,8 @@ + + diff --git a/Testing/Testing/TestExtensions.cs b/Testing/Testing/HeaderExtensions.cs similarity index 60% rename from Testing/Testing/TestExtensions.cs rename to Testing/Testing/HeaderExtensions.cs index 9844c72d..bce03541 100644 --- a/Testing/Testing/TestExtensions.cs +++ b/Testing/Testing/HeaderExtensions.cs @@ -1,40 +1,32 @@ -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace GenHTTP.Testing -{ - - public static class TestExtensions - { - - /// - /// Reads the response body as a string. - /// - /// The response to read - /// The content of the HTTP response - public static async ValueTask GetContentAsync(this HttpResponseMessage response) => await response.Content.ReadAsStringAsync(); - - public static string? GetHeader(this HttpResponseMessage response, string key) - { - if (response.Headers.TryGetValues(key, out var values)) - { - return values.FirstOrDefault(); - } - - return null; - } - - public static string? GetContentHeader(this HttpResponseMessage response, string key) - { - if (response.Content.Headers.TryGetValues(key, out var values)) - { - return values.FirstOrDefault(); - } - - return null; - } - - } - -} +using System.Linq; +using System.Net.Http; + +namespace GenHTTP.Testing +{ + + public static class HeaderExtensions + { + + public static string? GetHeader(this HttpResponseMessage response, string key) + { + if (response.Headers.TryGetValues(key, out var values)) + { + return values.FirstOrDefault(); + } + + return null; + } + + public static string? GetContentHeader(this HttpResponseMessage response, string key) + { + if (response.Content.Headers.TryGetValues(key, out var values)) + { + return values.FirstOrDefault(); + } + + return null; + } + + } + +} From 0c9a8f9b2f1df9e14b7e4ad3b42ca30ac2bf93f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Mon, 1 Jul 2024 12:42:43 +0200 Subject: [PATCH 13/14] Add bearer authentication concern (#506) * First draft for bearer auth * Additional coverage and documentation * Fix test --- .../Bearer/BearerAuthenticationConcern.cs | 167 ++++++++++++++++++ .../BearerAuthenticationConcernBuilder.cs | 98 ++++++++++ .../Bearer/TokenValidationOptions.cs | 26 +++ .../Authentication/BearerAuthentication.cs | 19 ++ .../GenHTTP.Modules.Authentication.csproj | 4 + .../BearerAuthenticationTests.cs | 125 +++++++++++++ 6 files changed, 439 insertions(+) create mode 100644 Modules/Authentication/Bearer/BearerAuthenticationConcern.cs create mode 100644 Modules/Authentication/Bearer/BearerAuthenticationConcernBuilder.cs create mode 100644 Modules/Authentication/Bearer/TokenValidationOptions.cs create mode 100644 Modules/Authentication/BearerAuthentication.cs create mode 100644 Testing/Acceptance/Modules/Authentication/BearerAuthenticationTests.cs diff --git a/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs b/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs new file mode 100644 index 00000000..d93d6578 --- /dev/null +++ b/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +using GenHTTP.Api.Content; +using GenHTTP.Api.Protocol; + +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; + +namespace GenHTTP.Modules.Authentication.Bearer +{ + + #region Supporting data structures + + internal class OpenIDConfiguration + { + + [JsonPropertyName("jwks_uri")] + public string? KeySetUrl { get; set; } + + } + + #endregion + + internal sealed class BearerAuthenticationConcern : IConcern + { + private ICollection? _IssuerKeys = null; + + #region Get-/Setters + + public IHandler Content { get; } + + public IHandler Parent { get; } + + private TokenValidationOptions ValidationOptions { get; } + + #endregion + + #region Initialization + + internal BearerAuthenticationConcern(IHandler parent, Func contentFactory, TokenValidationOptions validationOptions) + { + Parent = parent; + Content = contentFactory(this); + + ValidationOptions = validationOptions; + } + + #endregion + + #region Functionality + + public ValueTask PrepareAsync() => Content.PrepareAsync(); + + public IAsyncEnumerable GetContentAsync(IRequest request) => Content.GetContentAsync(request); + + public async ValueTask HandleAsync(IRequest request) + { + IdentityModelEventSource.LogCompleteSecurityArtifact = true; + + if (!request.Headers.TryGetValue("Authorization", out var authHeader)) + { + throw new ProviderException(ResponseStatus.Unauthorized, "This endpoint requires authorization"); + } + + if (!authHeader.StartsWith("Bearer ")) + { + throw new ProviderException(ResponseStatus.Unauthorized, "This endpoint requires bearer token authentication"); + } + + var tokenString = authHeader[7..]; + + var tokenHandler = new JwtSecurityTokenHandler(); + + if (!tokenHandler.CanReadToken(tokenString)) + { + throw new ProviderException(ResponseStatus.BadRequest, "Malformed authentication token"); + } + + var issuer = ValidationOptions.Issuer; + + var audience = ValidationOptions.Audience; + + if ((issuer != null) && (_IssuerKeys == null)) + { + _IssuerKeys = await FetchSigningKeys(issuer); + } + + var validationParameters = new TokenValidationParameters() + { + ValidIssuer = issuer, + ValidateIssuer = (issuer != null), + IssuerSigningKeys = _IssuerKeys, + ValidAudience = audience, + ValidateAudience = (audience != null), + ValidateLifetime = ValidationOptions.Lifetime + }; + + if (issuer == null) + { + validationParameters.SignatureValidator = (string token, TokenValidationParameters p) => new JwtSecurityToken(tokenString); + } + + try + { + tokenHandler.ValidateToken(tokenString, validationParameters, out var validated); + + var jwt = (JwtSecurityToken)validated; + + if (ValidationOptions.CustomValidator != null) + { + await ValidationOptions.CustomValidator.Invoke(jwt); + } + + if (ValidationOptions.UserMapping != null) + { + var user = await ValidationOptions.UserMapping.Invoke(request, jwt); + + if (user != null) + { + request.SetUser(user); + } + } + + return await Content.HandleAsync(request); + } + catch (SecurityTokenValidationException ex) + { + throw new ProviderException(ResponseStatus.Unauthorized, $"Authorization failed: {ex.Message}", ex); + } + } + + private static async Task> FetchSigningKeys(string issuer) + { + try + { + var configUrl = $"{issuer}/.well-known/openid-configuration"; + + using var httpClient = new HttpClient(); + + var configResponse = await httpClient.GetStringAsync(configUrl); + + var config = JsonSerializer.Deserialize(configResponse) + ?? throw new InvalidOperationException($"Unable to discover configuration via '{configUrl}'"); + + var keyResponse = await httpClient.GetStringAsync(config.KeySetUrl); + + var keySet = new JsonWebKeySet(keyResponse); + + return keySet.GetSigningKeys(); + } + catch (Exception ex) + { + throw new ProviderException(ResponseStatus.InternalServerError, "Unable to fetch signing issuer signing keys", ex); + } + } + + } + + #endregion + +} diff --git a/Modules/Authentication/Bearer/BearerAuthenticationConcernBuilder.cs b/Modules/Authentication/Bearer/BearerAuthenticationConcernBuilder.cs new file mode 100644 index 00000000..cdb1b9ef --- /dev/null +++ b/Modules/Authentication/Bearer/BearerAuthenticationConcernBuilder.cs @@ -0,0 +1,98 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Threading.Tasks; + +using GenHTTP.Api.Content; +using GenHTTP.Api.Content.Authentication; +using GenHTTP.Api.Protocol; + +namespace GenHTTP.Modules.Authentication.Bearer +{ + + public sealed class BearerAuthenticationConcernBuilder : IConcernBuilder + { + private readonly TokenValidationOptions _Options = new(); + + #region Functionality + + /// + /// Sets the expected issuer. Tokens that are not issued by this + /// party will be declined. + /// + /// The URL of the exepcted issuer + /// + /// Setting the issuer will cause the concern to download and cache + /// the signing keys that are used to ensure that the party actually + /// issued a token. + /// + public BearerAuthenticationConcernBuilder Issuer(string issuer) + { + _Options.Issuer = issuer; + return this; + } + + /// + /// Sets the expected audience that should be accepted. + /// + /// The audience to check for + public BearerAuthenticationConcernBuilder Audience(string audience) + { + _Options.Audience = audience; + return this; + } + + /// + /// Adds a custom validator that can analyze the token read from the + /// request and can perform additional checks. + /// + /// The custom validator to be used + /// + /// This validator will be invoked after the regular checks (such as the + /// issuer) have been performed. + /// + /// If you would like to deny user access within a custom validator, + /// you can throw a . + /// + public BearerAuthenticationConcernBuilder Validation(Func validator) + { + _Options.CustomValidator = validator; + return this; + } + + /// + /// Optionally register a function that will compute the user that + /// should be set for the request. + /// + /// The user mapping to be used + /// + /// The usage of this mechanism allows to inject the user into + /// service methods via the user injector class. Returning null + /// within the delegate will not deny user access - if you would + /// like to prevent such user, you can throw a . + /// + public BearerAuthenticationConcernBuilder UserMapping(Func> userMapping) + { + _Options.UserMapping = userMapping; + return this; + } + + /// + /// If enabled, tokens that have expired or are not valid yet are + /// still accepted. This should be used for testing purposes only. + /// + public BearerAuthenticationConcernBuilder AllowExpired() + { + _Options.Lifetime = false; + return this; + } + + public IConcern Build(IHandler parent, Func contentFactory) + { + return new BearerAuthenticationConcern(parent, contentFactory, _Options); + } + + #endregion + + } + +} diff --git a/Modules/Authentication/Bearer/TokenValidationOptions.cs b/Modules/Authentication/Bearer/TokenValidationOptions.cs new file mode 100644 index 00000000..d1aab100 --- /dev/null +++ b/Modules/Authentication/Bearer/TokenValidationOptions.cs @@ -0,0 +1,26 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Threading.Tasks; + +using GenHTTP.Api.Content.Authentication; +using GenHTTP.Api.Protocol; + +namespace GenHTTP.Modules.Authentication.Bearer +{ + + internal sealed class TokenValidationOptions + { + + internal string? Audience { get; set; } + + internal string? Issuer { get; set; } + + internal bool Lifetime { get; set; } = true; + + internal Func? CustomValidator { get; set; } + + internal Func>? UserMapping { get; set; } + + } + +} diff --git a/Modules/Authentication/BearerAuthentication.cs b/Modules/Authentication/BearerAuthentication.cs new file mode 100644 index 00000000..1a92a6dd --- /dev/null +++ b/Modules/Authentication/BearerAuthentication.cs @@ -0,0 +1,19 @@ +using GenHTTP.Modules.Authentication.Bearer; + +namespace GenHTTP.Modules.Authentication +{ + + public static class BearerAuthentication + { + + /// + /// Creates a concern that will read an access token from + /// the authorization headers and validate it according to + /// its configuration. + /// + /// The newly created concern + public static BearerAuthenticationConcernBuilder Create() => new(); + + } + +} diff --git a/Modules/Authentication/GenHTTP.Modules.Authentication.csproj b/Modules/Authentication/GenHTTP.Modules.Authentication.csproj index 4a0b9bfa..06218c0d 100644 --- a/Modules/Authentication/GenHTTP.Modules.Authentication.csproj +++ b/Modules/Authentication/GenHTTP.Modules.Authentication.csproj @@ -42,8 +42,12 @@ + + + + diff --git a/Testing/Acceptance/Modules/Authentication/BearerAuthenticationTests.cs b/Testing/Acceptance/Modules/Authentication/BearerAuthenticationTests.cs new file mode 100644 index 00000000..b23c5125 --- /dev/null +++ b/Testing/Acceptance/Modules/Authentication/BearerAuthenticationTests.cs @@ -0,0 +1,125 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +using GenHTTP.Api.Content; +using GenHTTP.Api.Content.Authentication; +using GenHTTP.Api.Protocol; + +using GenHTTP.Modules.Authentication; +using GenHTTP.Modules.Authentication.Bearer; +using GenHTTP.Modules.Functional; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GenHTTP.Testing.Acceptance.Modules.Authentication +{ + + [TestClass] + public sealed class BearerAuthenticationTests + { + private const string VALID_TOKEN = @"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; + + #region Supporting data structures + + internal class MyUser : IUser + { + + public string DisplayName { get; set; } = ""; + + } + + #endregion + + [TestMethod] + public async Task TestValidToken() + { + var auth = BearerAuthentication.Create() + .AllowExpired(); + + using var response = await Execute(auth, VALID_TOKEN); + + await response.AssertStatusAsync(HttpStatusCode.OK); + + Assert.AreEqual("Secured", await response.GetContentAsync()); + } + + [TestMethod] + public async Task TestCustomValidator() + { + var auth = BearerAuthentication.Create() + .Validation((token) => throw new ProviderException(ResponseStatus.Forbidden, "Nah")) + .AllowExpired(); + + using var response = await Execute(auth, VALID_TOKEN); + + await response.AssertStatusAsync(HttpStatusCode.Forbidden); + } + + [TestMethod] + public async Task TestNoUser() + { + var auth = BearerAuthentication.Create() + .UserMapping((r, t) => new()) + .AllowExpired(); + + using var response = await Execute(auth, VALID_TOKEN); + + await response.AssertStatusAsync(HttpStatusCode.OK); + } + + [TestMethod] + public async Task TestUser() + { + var auth = BearerAuthentication.Create() + .UserMapping((r, t) => new(new MyUser() { DisplayName = "User Name" })) + .AllowExpired(); + + using var response = await Execute(auth, VALID_TOKEN); + + await response.AssertStatusAsync(HttpStatusCode.OK); + } + + [TestMethod] + public async Task TestNoToken() + { + var auth = BearerAuthentication.Create() + .AllowExpired(); + + using var response = await Execute(auth); + + await response.AssertStatusAsync(HttpStatusCode.Unauthorized); + } + + [TestMethod] + public async Task TestMalformedToken() + { + var auth = BearerAuthentication.Create() + .AllowExpired(); + + using var response = await Execute(auth, "Lorem Ipsum"); + + await response.AssertStatusAsync(HttpStatusCode.BadRequest); + } + + private static async Task Execute(BearerAuthenticationConcernBuilder builder, string? token = null) + { + var handler = Inline.Create() + .Get(() => "Secured") + .Add(builder); + + using var host = TestHost.Run(handler); + + var request = host.GetRequest(); + + if (token != null) + { + request.Headers.Authorization = new("Bearer", token); + } + + return await host.GetResponseAsync(request); + } + + } + +} From 98bd0cb602a90bdb635c1406b085cd681edb6b7a Mon Sep 17 00:00:00 2001 From: AKaliumhexacyanoferrat Date: Mon, 1 Jul 2024 13:07:13 +0200 Subject: [PATCH 14/14] Set correct nuget versions --- Packages/GenHTTP.Core.nuspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Packages/GenHTTP.Core.nuspec b/Packages/GenHTTP.Core.nuspec index 8a21c0da..e7cba584 100644 --- a/Packages/GenHTTP.Core.nuspec +++ b/Packages/GenHTTP.Core.nuspec @@ -22,13 +22,13 @@ - + - + - - - + + +