diff --git a/example/Helpers/RealisticContentSecurityPolicyGenerators.cs b/example/Helpers/RealisticContentSecurityPolicyGenerators.cs new file mode 100644 index 0000000..de1ef6b --- /dev/null +++ b/example/Helpers/RealisticContentSecurityPolicyGenerators.cs @@ -0,0 +1,151 @@ +using OwaspHeaders.Core.Enums; +using OwaspHeaders.Core.Extensions; +using OwaspHeaders.Core.Models; + +namespace example.Helpers; + +public static class RealisticContentSecurityPolicyGenerators +{ + /// + /// Represents an instance of the with + /// the Content-Security Policy from the OWASP homepage. + /// + /// + /// The instance of hte that this method + /// returns, DOES NOT contain any other header values. The return value from this method is + /// provided as a way of testing the CSP generation code. And should NOT be used in a live + /// environment (unless you are replacing the OWASP website with ASP .NET Core 😛) + /// + /// + /// An instance of the with headers which + /// represent the Content-Security Policy taken from the OWASP website homepage on May 15th, 2023 + /// + public static SecureHeadersMiddlewareConfiguration GenerateOwaspHomePageCsp() => + SecureHeadersMiddlewareExtensions.BuildDefaultConfiguration() + .UseContentSecurityPolicy() + .SetCspUris( + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.github.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, + new() + { + CommandType = CspCommandType.Uri, + DirectiveOrUri = "https://owaspadmin.azurewebsites.net" + }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.doubleclick.net" } + }, CspUriType.DefaultUri) + .SetCspUris( + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.github.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://owaspadmin.azurewebsites.net" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.doubleclick.net" }, + }, CspUriType.FrameAncestors) + .SetCspUris( + new List + { + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vuejs.org" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.stripe.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.wufoo.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.sched.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://w.soundcloud.com" }, + }, CspUriType.Frame) + .SetCspUris( + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-inline" }, + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-eval" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://fonts.googleapis.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://app.diagrams.net" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cdnjs.cloudflare.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cse.google.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vuejs.org" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.stripe.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.wufoo.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.youtube.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.meetup.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.sched.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://unpkg.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://buttons.github.io" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.google.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + }, CspUriType.Script) + .SetCspUris( + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-inline" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cdnjs.cloudflare.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.google.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://fonts.googleapis.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "data:" }, + }, CspUriType.Style) + .SetCspUris( + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "fonts.gstatic.com" } + }, CspUriType.Font) + .SetCspUris( + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://pay.google.com" } + }, CspUriType.Manifest) + .SetCspUris( + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.globalappsec.org" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "data:" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "www.w3.org" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://licensebuttons.net" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://img.shields.io" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://github.githubassets.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vercel.app" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.cloudfront.net" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.coreinfrastructure.org" }, + new() + { + CommandType = CspCommandType.Uri, + DirectiveOrUri = "https://*.securityknowledgeframework.org" + }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://badges.gitter.im" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://travis-ci.org" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.travis-ci.org" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://s3.amazonaws.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://snyk.io" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://coveralls.io" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://requires.io" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://github.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.googleapis.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google.com" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" }, + }, CspUriType.Img); +} diff --git a/example/Program.cs b/example/Program.cs index 8cbf298..91874db 100644 --- a/example/Program.cs +++ b/example/Program.cs @@ -1,4 +1,5 @@ -using OwaspHeaders.Core.Extensions; +using example.Helpers; +using OwaspHeaders.Core.Extensions; var builder = WebApplication.CreateBuilder(args); @@ -22,7 +23,7 @@ app.UseAuthorization(); -app.UseSecureHeadersMiddleware(SecureHeadersMiddlewareExtensions.BuildDefaultConfiguration()); +app.UseSecureHeadersMiddleware(RealisticContentSecurityPolicyGenerators.GenerateOwaspHomePageCsp()); app.MapControllers(); diff --git a/src/Extensions/StringBuilderExtensions.cs b/src/Extensions/StringBuilderExtensions.cs index 2c18e17..82839b7 100644 --- a/src/Extensions/StringBuilderExtensions.cs +++ b/src/Extensions/StringBuilderExtensions.cs @@ -17,7 +17,7 @@ public static class StringBuilderExtensions /// /// /// - private static StringBuilder TrimEnd(this StringBuilder sb) + public static StringBuilder TrimEnd(this StringBuilder sb) { if (sb == null || sb.Length == 0) return sb; @@ -52,8 +52,8 @@ public static StringBuilder BuildValuesForDirective(this StringBuilder stringBui if (directives.Any()) { - stringBuilder.Append(string.Join(EmptySpace, directives.Select(directive => $"'{directive.DirectiveOrUri}'"))); - stringBuilder.Append(EmptySpace); + stringBuilder.Append(string.Join(EmptySpace, + directives.Select(directive => $"'{directive.DirectiveOrUri}'"))); } } diff --git a/src/OwaspHeaders.Core.csproj b/src/OwaspHeaders.Core.csproj index 2a80cdd..7ae864a 100755 --- a/src/OwaspHeaders.Core.csproj +++ b/src/OwaspHeaders.Core.csproj @@ -1,7 +1,7 @@ An ASP.NET Core Middleware which adds the OWASP recommended HTTP headers for enhanced security. - 6.0.5 + 6.1.0 Jamie Taylor OwaspHeaders.Core netstandard2.0 diff --git a/src/OwaspHeadersCore.nuspec b/src/OwaspHeadersCore.nuspec index 286fdf0..ec288ce 100644 --- a/src/OwaspHeadersCore.nuspec +++ b/src/OwaspHeadersCore.nuspec @@ -2,7 +2,7 @@ OwaspHeaders.Core - 6.0.5 + 6.1.0 GaProgMan GaProgMan docs\README-NuGet.md diff --git a/src/SecureHeadersMiddleware.cs b/src/SecureHeadersMiddleware.cs index de6adbc..b4b3af3 100755 --- a/src/SecureHeadersMiddleware.cs +++ b/src/SecureHeadersMiddleware.cs @@ -14,6 +14,7 @@ public class SecureHeadersMiddleware { private readonly RequestDelegate _next; private readonly SecureHeadersMiddlewareConfiguration _config; + private string _calculatedContentSecurityPolicy; public SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewareConfiguration config) { @@ -59,20 +60,27 @@ public async Task InvokeAsync(HttpContext httpContext) if (_config.UseContentSecurityPolicyReportOnly) { - + if (string.IsNullOrWhiteSpace(_calculatedContentSecurityPolicy)) + { + _calculatedContentSecurityPolicy = _config.ContentSecurityPolicyReportOnlyConfiguration.BuildHeaderValue(); + } httpContext.TryAddHeader(Constants.ContentSecurityPolicyReportOnlyHeaderName, - _config.ContentSecurityPolicyReportOnlyConfiguration.BuildHeaderValue()); + _calculatedContentSecurityPolicy); } else if (_config.UseContentSecurityPolicy) { + if (string.IsNullOrWhiteSpace(_calculatedContentSecurityPolicy)) + { + _calculatedContentSecurityPolicy = _config.ContentSecurityPolicyConfiguration.BuildHeaderValue(); + } httpContext.TryAddHeader(Constants.ContentSecurityPolicyHeaderName, - _config.ContentSecurityPolicyConfiguration.BuildHeaderValue()); + _calculatedContentSecurityPolicy); } if (_config.UseXContentSecurityPolicy) { httpContext.TryAddHeader(Constants.XContentSecurityPolicyHeaderName, - _config.ContentSecurityPolicyConfiguration.BuildHeaderValue()); + _config.ContentSecurityPolicyConfiguration.BuildHeaderValue()); } if (_config.UsePermittedCrossDomainPolicy) diff --git a/tests/RegressionTests.cs b/tests/RegressionTests.cs deleted file mode 100644 index 0da1569..0000000 --- a/tests/RegressionTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using OwaspHeaders.Core; -using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Extensions; -using OwaspHeaders.Core.Models; -using Xunit; - -namespace tests; - -/// -/// This class contains a number of regression tests against bugs which were reported -/// using GitHub issues. Each test will link to the issue in question. Please make sure -/// that these tests still pass whenever making changes to this codebase -/// -[ExcludeFromCodeCoverage] -public class RegressionTests -{ - private int _onNextCalledTimes; - private readonly Task _onNextResult = Task.FromResult(0); - private readonly RequestDelegate _onNext; - private readonly DefaultHttpContext _context; - - public RegressionTests() - { - _onNext = _ => - { - Interlocked.Increment(ref _onNextCalledTimes); - return _onNextResult; - }; - _context = new DefaultHttpContext(); - } - - /// - /// This test exercises and provides regression against https://github.com/GaProgMan/OwaspHeaders.Core/issues/61 - /// - [Fact] - public async Task ContentSecurityPolicy_Adds_MultipleSameValue() - { - // arrange - var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder().UseContentSecurityPolicy() - .SetCspUris( - new List - { - new() - { - CommandType = CspCommandType.Directive, DirectiveOrUri = "self" - }, - new() - { - CommandType = CspCommandType.Uri, DirectiveOrUri = "cdnjs.cloudflare.com" - } - }, CspUriType.Style).Build(); - var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig); - - // act - await secureHeadersMiddleware.InvokeAsync(_context); - - // assert - Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName)); - - var headerValue = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList(); - Assert.Equal(1, - headerValue.First() - .Split(" ") - .Count(hv => hv.Contains("cdnjs.cloudflare.com", StringComparison.InvariantCultureIgnoreCase))); - } -} - diff --git a/tests/RegressionTests/CspRegressionTests.cs b/tests/RegressionTests/CspRegressionTests.cs new file mode 100644 index 0000000..944ca33 --- /dev/null +++ b/tests/RegressionTests/CspRegressionTests.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using OwaspHeaders.Core; +using OwaspHeaders.Core.Enums; +using OwaspHeaders.Core.Extensions; +using OwaspHeaders.Core.Models; +using Xunit; + +namespace tests.RegressionTests; + +/// +/// This class contains a number of regression tests against bugs which were reported +/// using GitHub issues. Each test will link to the issue in question. Please make sure +/// that these tests still pass whenever making changes to this codebase +/// +[ExcludeFromCodeCoverage] +public class CspRegressionTests +{ + private int _onNextCalledTimes; + private readonly Task _onNextResult = Task.FromResult(0); + private readonly RequestDelegate _onNext; + private readonly DefaultHttpContext _context; + + public CspRegressionTests() + { + _onNext = _ => + { + Interlocked.Increment(ref _onNextCalledTimes); + return _onNextResult; + }; + _context = new DefaultHttpContext(); + } + + /// + /// This test exercises and provides regression against https://github.com/GaProgMan/OwaspHeaders.Core/issues/61 + /// + [Fact] + public async Task ContentSecurityPolicy_Adds_MultipleSameValue() + { + // arrange + var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder().UseContentSecurityPolicy() + .SetCspUris( + new List + { + new() + { + CommandType = CspCommandType.Directive, DirectiveOrUri = "self" + }, + new() + { + CommandType = CspCommandType.Uri, DirectiveOrUri = "cdnjs.cloudflare.com" + } + }, CspUriType.Style).Build(); + var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig); + + // act + await secureHeadersMiddleware.InvokeAsync(_context); + + // assert + Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName)); + + var headerValue = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList(); + Assert.Equal(1, + headerValue.First() + .Split(" ") + .Count(hv => hv.Contains("cdnjs.cloudflare.com", StringComparison.InvariantCultureIgnoreCase))); + } + + /// + /// This test exercises and proves regression against unnecessary spaces being added between the final + /// directive and first URI in a CSP + /// + [Fact] + public async Task ContentSecurityPolicy_Adds_UnnecessarySpace_Between_FinalDirective_And_First_Uri() + { + // arrange + const string targetCsp = "style-src 'self' cdnjs.cloudflare.com;"; + var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder() + .UseContentSecurityPolicy(blockAllMixedContent: false, upgradeInsecureRequests: false) + .SetCspUris( + // originally PRODUCES: style-src 'self' cdnjs.cloudflare.com; + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "cdnjs.cloudflare.com" } + }, CspUriType.Style).Build(); + var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig); + + // act + await secureHeadersMiddleware.InvokeAsync(_context); + + // assert + Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName)); + + var headerStrings = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList(); + Assert.NotNull(headerStrings); + + var actualCsp = headerStrings.First(); + var actualCharCount = actualCsp.Length; + var targetCharCount = targetCsp.Length; + + Assert.True(string.Equals(targetCsp, actualCsp, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal(targetCharCount, actualCharCount); + } + + /// + /// This test exercises and proves regression against unnecessary spaces being added after a + /// directive in a generated CSP, when no URIs are provided + /// + [Fact] + public async Task ContentSecurityPolicy_Adds_UnnecessarySpace_After_FinalDirective_When_OnlyDirectivesProvided() + { + // arrange + const string targetCsp = "style-src 'self';"; + var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder() + .UseContentSecurityPolicy(blockAllMixedContent: false, upgradeInsecureRequests: false) + .SetCspUris( + // originally PRODUCES: style-src 'self' ; + new List + { + new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" } + }, CspUriType.Style).Build(); + var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig); + + // act + await secureHeadersMiddleware.InvokeAsync(_context); + + // assert + Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName)); + + var headerStrings = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList(); + Assert.NotNull(headerStrings); + + var actualCsp = headerStrings.First(); + var actualCharCount = actualCsp.Length; + var targetCharCount = targetCsp.Length; + + Assert.True(string.Equals(targetCsp, actualCsp, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal(targetCharCount, actualCharCount); + } + + /// + /// This test exercises and proves regression against unnecessary spaces being added before the first + /// URI in a CSP, when no Directives are provided + /// + [Fact] + public async Task ContentSecurityPolicy_Adds_UnnecessarySpace_Before_First_Uri_When_NoDirectivesProvided() + { + // arrange + const string targetCsp = "style-src cdnjs.cloudflare.com;"; + var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder() + .UseContentSecurityPolicy(blockAllMixedContent: false, upgradeInsecureRequests: false) + .SetCspUris( + // originally PRODUCES: style-src cdnjs.cloudflare.com; + new List + { + new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "cdnjs.cloudflare.com" } + }, CspUriType.Style).Build(); + var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig); + + // act + await secureHeadersMiddleware.InvokeAsync(_context); + + // assert + Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName)); + + var headerStrings = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList(); + Assert.NotNull(headerStrings); + + var actualCsp = headerStrings.First(); + var actualCharCount = actualCsp.Length; + var targetCharCount = targetCsp.Length; + + Assert.True(string.Equals(targetCsp, actualCsp, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal(targetCharCount, actualCharCount); + } +} + diff --git a/tests/SecureHeadersInjectedTest.cs b/tests/SecureHeadersInjectedTest.cs index c8ba9f5..f34d13f 100755 --- a/tests/SecureHeadersInjectedTest.cs +++ b/tests/SecureHeadersInjectedTest.cs @@ -456,16 +456,16 @@ public async Task Invoke_CacheControl_HeaderIsPresent() Assert.True(_context.Response.Headers.ContainsKey(Constants.CacheControlHeaderName)); Assert.Equal(headerPresentConfig.CacheControl.BuildHeaderValue(), _context.Response.Headers[Constants.CacheControlHeaderName]); - } - + } + [Fact] public async Task Invoke_CacheControl_HeaderIsNotPresent() { // arrange var headerNotPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder(); headerNotPresentConfig.UseCacheControl = false; - headerNotPresentConfig.Build(); - + headerNotPresentConfig.Build(); + var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerNotPresentConfig); // act diff --git a/tests/StringBuilderExtensionTests.cs b/tests/StringBuilderExtensionTests.cs new file mode 100644 index 0000000..c770e2d --- /dev/null +++ b/tests/StringBuilderExtensionTests.cs @@ -0,0 +1,34 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; +using OwaspHeaders.Core.Extensions; +using Xunit; + +namespace tests; + +[ExcludeFromCodeCoverage] +public class StringBuilderExtensionTests +{ + [Fact] + public void TrimEnd_StringBuilder_Is_Null_Return_StringBuilder() + { + // arrange & act + var builder = ((StringBuilder)null).TrimEnd(); + + // assert + Assert.Null(builder); + } + + [Fact] + public void TrimEnd_StringBuilder_Is_Empty_Return_StringBuilder() + { + // arrange + StringBuilder builder = new StringBuilder(); + + // act + builder = builder.TrimEnd(); + + // assert + Assert.NotNull(builder); + Assert.True(builder.Length == 0); + } +}