Repository: aspnet/BasicMiddleware Branch: master Commit: 87d4df52fa20 Files: 283 Total size: 814.0 KB Directory structure: gitextract_jlb0z59u/ ├── .appveyor.yml ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .vsts-pipelines/ │ └── builds/ │ ├── ci-internal.yml │ └── ci-public.yml ├── BasicMiddleware.sln ├── CONTRIBUTING.md ├── Directory.Build.props ├── Directory.Build.targets ├── LICENSE.txt ├── NuGet.config ├── NuGetPackageVerifier.json ├── README.md ├── benchmarks/ │ └── Microsoft.AspNetCore.ResponseCompression.Benchmarks/ │ ├── AssemblyInfo.cs │ ├── Microsoft.AspNetCore.ResponseCompression.Benchmarks.csproj │ └── ResponseCompressionProviderBenchmark.cs ├── build/ │ ├── Key.snk │ ├── dependencies.props │ ├── repo.props │ └── sources.props ├── build.cmd ├── build.sh ├── korebuild-lock.txt ├── korebuild.json ├── run.cmd ├── run.ps1 ├── run.sh ├── samples/ │ ├── HostFilteringSample/ │ │ ├── HostFilteringSample.csproj │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ ├── appsettings.Development.json │ │ ├── appsettings.Production.json │ │ └── appsettings.json │ ├── HttpOverridesSample/ │ │ ├── HttpOverridesSample.csproj │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ └── Startup.cs │ ├── HttpsPolicySample/ │ │ ├── HttpsPolicySample.csproj │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ └── testCert.pfx │ ├── ResponseBufferingSample/ │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── ResponseBufferingSample.csproj │ │ └── Startup.cs │ ├── ResponseCompressionSample/ │ │ ├── CustomCompressionProvider.cs │ │ ├── LoremIpsum.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── ResponseCompressionSample.csproj │ │ ├── Startup.cs │ │ └── testfile1kb.txt │ └── RewriteSample/ │ ├── Properties/ │ │ └── launchSettings.json │ ├── Rewrite.txt │ ├── RewriteSample.csproj │ ├── Startup.cs │ ├── UrlRewrite.xml │ └── testCert.pfx ├── src/ │ ├── Directory.Build.props │ ├── Microsoft.AspNetCore.Buffering/ │ │ ├── BufferingWriteStream.cs │ │ ├── HttpBufferingFeature.cs │ │ ├── Microsoft.AspNetCore.Buffering.csproj │ │ ├── ResponseBufferingMiddleware.cs │ │ ├── ResponseBufferingMiddlewareExtensions.cs │ │ └── SendFileFeatureWrapper.cs │ ├── Microsoft.AspNetCore.HostFiltering/ │ │ ├── HostFilteringBuilderExtensions.cs │ │ ├── HostFilteringMiddleware.cs │ │ ├── HostFilteringOptions.cs │ │ ├── HostFilteringServicesExtensions.cs │ │ ├── Microsoft.AspNetCore.HostFiltering.csproj │ │ └── baseline.netcore.json │ ├── Microsoft.AspNetCore.HttpOverrides/ │ │ ├── ForwardedHeaders.cs │ │ ├── ForwardedHeadersDefaults.cs │ │ ├── ForwardedHeadersExtensions.cs │ │ ├── ForwardedHeadersMiddleware.cs │ │ ├── ForwardedHeadersOptions.cs │ │ ├── HttpMethodOverrideExtensions.cs │ │ ├── HttpMethodOverrideMiddleware.cs │ │ ├── HttpMethodOverrideOptions.cs │ │ ├── IPNetwork.cs │ │ ├── Internal/ │ │ │ └── IPEndPointParser.cs │ │ ├── Microsoft.AspNetCore.HttpOverrides.csproj │ │ ├── baseline.net45.json │ │ └── baseline.netcore.json │ ├── Microsoft.AspNetCore.HttpsPolicy/ │ │ ├── HstsBuilderExtensions.cs │ │ ├── HstsMiddleware.cs │ │ ├── HstsOptions.cs │ │ ├── HstsServicesExtensions.cs │ │ ├── HttpsRedirectionBuilderExtensions.cs │ │ ├── HttpsRedirectionMiddleware.cs │ │ ├── HttpsRedirectionOptions.cs │ │ ├── HttpsRedirectionServicesExtensions.cs │ │ ├── Microsoft.AspNetCore.HttpsPolicy.csproj │ │ ├── baseline.netcore.json │ │ └── internal/ │ │ ├── HstsLoggingExtensions.cs │ │ └── HttpsLoggingExtensions.cs │ ├── Microsoft.AspNetCore.ResponseCompression/ │ │ ├── BodyWrapperStream.cs │ │ ├── BrotliCompressionProvider.cs │ │ ├── BrotliCompressionProviderOptions.cs │ │ ├── CompressionProviderCollection.cs │ │ ├── CompressionProviderFactory.cs │ │ ├── GzipCompressionProvider.cs │ │ ├── GzipCompressionProviderOptions.cs │ │ ├── ICompressionProvider.cs │ │ ├── IResponseCompressionProvider.cs │ │ ├── Microsoft.AspNetCore.ResponseCompression.csproj │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── ResponseCompressionBuilderExtensions.cs │ │ ├── ResponseCompressionDefaults.cs │ │ ├── ResponseCompressionMiddleware.cs │ │ ├── ResponseCompressionOptions.cs │ │ ├── ResponseCompressionProvider.cs │ │ ├── ResponseCompressionServicesExtensions.cs │ │ ├── baseline.netcore.json │ │ ├── baseline.netframework.json │ │ └── internal/ │ │ └── ResponseCompressionLoggingExtensions.cs │ └── Microsoft.AspNetCore.Rewrite/ │ ├── ApacheModRewriteOptionsExtensions.cs │ ├── Extensions/ │ │ └── RewriteMiddlewareLoggingExtensions.cs │ ├── IISUrlRewriteOptionsExtensions.cs │ ├── IRule.cs │ ├── Internal/ │ │ ├── ApacheModRewrite/ │ │ │ ├── ApacheModRewriteRule.cs │ │ │ ├── Condition.cs │ │ │ ├── ConditionEvaluator.cs │ │ │ ├── ConditionPatternParser.cs │ │ │ ├── ConditionType.cs │ │ │ ├── CookieActionFactory.cs │ │ │ ├── FileParser.cs │ │ │ ├── FlagParser.cs │ │ │ ├── FlagType.cs │ │ │ ├── Flags.cs │ │ │ ├── OperationType.cs │ │ │ ├── ParsedModRewriteCondition.cs │ │ │ ├── RuleBuilder.cs │ │ │ ├── RuleRegexParser.cs │ │ │ ├── SegmentType.cs │ │ │ ├── ServerVariables.cs │ │ │ ├── TestStringParser.cs │ │ │ └── Tokenizer.cs │ │ ├── BackReferenceCollection.cs │ │ ├── DelegateRule.cs │ │ ├── IISUrlRewrite/ │ │ │ ├── ActionType.cs │ │ │ ├── Condition.cs │ │ │ ├── ConditionCollection.cs │ │ │ ├── ConditionEvaluator.cs │ │ │ ├── IISRewriteMap.cs │ │ │ ├── IISRewriteMapCollection.cs │ │ │ ├── IISUrlRewriteRule.cs │ │ │ ├── InputParser.cs │ │ │ ├── InvalidUrlRewriteFormatException.cs │ │ │ ├── LogicalGrouping.cs │ │ │ ├── MatchType.cs │ │ │ ├── PatternSyntax.cs │ │ │ ├── RedirectType.cs │ │ │ ├── RewriteMapParser.cs │ │ │ ├── RewriteTags.cs │ │ │ ├── ServerVariables.cs │ │ │ ├── UriMatchCondition.cs │ │ │ ├── UriMatchPart.cs │ │ │ ├── UrlRewriteFileParser.cs │ │ │ └── UrlRewriteRuleBuilder.cs │ │ ├── MatchResults.cs │ │ ├── ParserContext.cs │ │ ├── Pattern.cs │ │ ├── PatternSegment.cs │ │ ├── PatternSegments/ │ │ │ ├── ConditionMatchSegment.cs │ │ │ ├── DateTimeSegment.cs │ │ │ ├── HeaderSegment.cs │ │ │ ├── IsHttpsModSegment.cs │ │ │ ├── IsHttpsUrlSegment.cs │ │ │ ├── IsIPV6Segment.cs │ │ │ ├── LiteralSegment.cs │ │ │ ├── LocalAddressSegment.cs │ │ │ ├── LocalPortSegment.cs │ │ │ ├── QueryStringSegment.cs │ │ │ ├── RemoteAddressSegment.cs │ │ │ ├── RemotePortSegment.cs │ │ │ ├── RequestFilenameSegment.cs │ │ │ ├── RequestMethodSegment.cs │ │ │ ├── RewriteMapSegment.cs │ │ │ ├── RuleMatchSegment.cs │ │ │ ├── SchemeSegment.cs │ │ │ ├── ServerProtocolSegment.cs │ │ │ ├── ToLowerSegment.cs │ │ │ ├── UrlEncodeSegment.cs │ │ │ └── UrlSegment.cs │ │ ├── RedirectRule.cs │ │ ├── RedirectToHttpsRule.cs │ │ ├── RedirectToWwwRule.cs │ │ ├── RewriteRule.cs │ │ ├── UrlAction.cs │ │ ├── UrlActions/ │ │ │ ├── AbortAction.cs │ │ │ ├── ChangeCookieAction.cs │ │ │ ├── CustomResponseAction.cs │ │ │ ├── ForbiddenAction.cs │ │ │ ├── GoneAction.cs │ │ │ ├── NoneAction.cs │ │ │ ├── RedirectAction.cs │ │ │ └── RewriteAction.cs │ │ ├── UrlMatch.cs │ │ └── UrlMatches/ │ │ ├── ExactMatch.cs │ │ ├── FileSizeMatch.cs │ │ ├── IntegerMatch.cs │ │ ├── IntegerOperation.cs │ │ ├── IsDirectoryMatch.cs │ │ ├── IsFileMatch.cs │ │ ├── RegexMatch.cs │ │ ├── StringMatch.cs │ │ └── StringOperation.cs │ ├── Microsoft.AspNetCore.Rewrite.csproj │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ └── Resources.Designer.cs │ ├── Resources.resx │ ├── RewriteBuilderExtensions.cs │ ├── RewriteContext.cs │ ├── RewriteMiddleware.cs │ ├── RewriteOptions.cs │ ├── RewriteOptionsExtensions.cs │ ├── RuleResult.cs │ └── baseline.netcore.json ├── test/ │ ├── Directory.Build.props │ ├── Microsoft.AspNetCore.Buffering.Tests/ │ │ ├── Microsoft.AspNetCore.Buffering.Tests.csproj │ │ └── ResponseBufferingMiddlewareTests.cs │ ├── Microsoft.AspNetCore.HostFiltering.Tests/ │ │ ├── HostFilteringMiddlewareTests.cs │ │ └── Microsoft.AspNetCore.HostFiltering.Tests.csproj │ ├── Microsoft.AspNetCore.HttpOverrides.Tests/ │ │ ├── ForwardedHeadersMiddlewareTest.cs │ │ ├── HttpMethodOverrideMiddlewareTest.cs │ │ ├── IPEndPointParserTest.cs │ │ ├── IPNetworkTest.cs │ │ └── Microsoft.AspNetCore.HttpOverrides.Tests.csproj │ ├── Microsoft.AspNetCore.HttpsPolicy.Tests/ │ │ ├── HstsMiddlewareTests.cs │ │ ├── HttpsPolicyTests.cs │ │ ├── HttpsRedirectionMiddlewareTests.cs │ │ └── Microsoft.AspNetCore.HttpsPolicy.Tests.csproj │ ├── Microsoft.AspNetCore.ResponseCompression.Tests/ │ │ ├── BodyWrapperStreamTests.cs │ │ ├── Microsoft.AspNetCore.ResponseCompression.Tests.csproj │ │ ├── ResponseCompressionMiddlewareTest.cs │ │ └── testfile1kb.txt │ └── Microsoft.AspNetCore.Rewrite.Tests/ │ ├── ApacheModRewrite/ │ │ ├── ConditionPatternParserTest.cs │ │ ├── CookieActionFactoryTest.cs │ │ ├── FlagParserTest.cs │ │ ├── FormatExceptionTests.cs │ │ ├── ModRewriteMiddlewareTest.cs │ │ ├── RewriteTokenizerTest.cs │ │ ├── RuleBuilderTest.cs │ │ ├── RuleRegexParserTest.cs │ │ └── TestStringParserTests.cs │ ├── IISUrlRewrite/ │ │ ├── FileParserTests.cs │ │ ├── FormatExceptionHandlingTests.cs │ │ ├── InputParserTests.cs │ │ ├── InvalidUrlRewriteFormatExceptionHandlingTests.cs │ │ ├── MiddleWareTests.cs │ │ ├── RewriteMapParserTests.cs │ │ ├── ServerVariableTests.cs │ │ └── UrlRewriteApplicationTests.cs │ ├── Microsoft.AspNetCore.Rewrite.Tests.csproj │ ├── MiddlewareTests.cs │ ├── PatternSegments/ │ │ ├── ConditionMatchSegmentTests.cs │ │ ├── DateTimeSegmentTests.cs │ │ ├── HeaderSegmentTests.cs │ │ ├── IsHttpsModSegmentTests.cs │ │ ├── IsHttpsSegmentTests.cs │ │ ├── IsIPV6SegmentTests.cs │ │ ├── LIteralSegmentTests.cs │ │ ├── LocalAddressSegmentTests.cs │ │ ├── LocalPortSegmentTests.cs │ │ ├── QueryStringSegmentTests.cs │ │ ├── RemoteAddressSegmentTests.cs │ │ ├── RemotePortSegmentTests.cs │ │ ├── RequestFilenameSegmentTests.cs │ │ ├── RequestMethodSegmentTests.cs │ │ ├── RuleMatchSegmentTests.cs │ │ ├── SchemeSegmentTests.cs │ │ ├── ServerProtocolSegmentTests.cs │ │ ├── ToLowerSegmentTests.cs │ │ ├── UrlEncodeSegmentTests.cs │ │ └── UrlSegmentTests.cs │ ├── UrlActions/ │ │ ├── AbortActionTests.cs │ │ ├── ChangeCookieActionTests.cs │ │ ├── ForbiddenActionTests.cs │ │ └── GoneActionTests.cs │ └── UrlMatches/ │ ├── ExactMatchTests.cs │ ├── IntegerMatchTests.cs │ └── StringMatchTests.cs └── version.props ================================================ FILE CONTENTS ================================================ ================================================ FILE: .appveyor.yml ================================================ init: - git config --global core.autocrlf true branches: only: - master - /^release\/.*$/ - /^(.*\/)?ci-.*$/ build_script: - ps: .\run.ps1 default-build clone_depth: 1 environment: global: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: 1 test: 'off' deploy: 'off' os: Visual Studio 2017 ================================================ FILE: .gitattributes ================================================ *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain *.jpg binary *.png binary *.gif binary *.cs text=auto diff=csharp *.vb text=auto *.resx text=auto *.c text=auto *.cpp text=auto *.cxx text=auto *.h text=auto *.hxx text=auto *.py text=auto *.rb text=auto *.java text=auto *.html text=auto *.htm text=auto *.css text=auto *.scss text=auto *.sass text=auto *.less text=auto *.js text=auto *.lisp text=auto *.clj text=auto *.sql text=auto *.php text=auto *.lua text=auto *.m text=auto *.asm text=auto *.erl text=auto *.fs text=auto *.fsx text=auto *.hs text=auto *.csproj text=auto *.vbproj text=auto *.fsproj text=auto *.dbproj text=auto *.sln text=auto eol=crlf *.sh eol=lf ================================================ FILE: .gitignore ================================================ [Oo]bj/ [Bb]in/ TestResults/ .nuget/ *.sln.ide/ _ReSharper.*/ packages/ artifacts/ PublishProfiles/ *.user *.suo *.cache *.docstates _ReSharper.* nuget.exe project.lock.json *net45.csproj *net451.csproj *k10.csproj *.psess *.vsp *.pidb *.userprefs *DS_Store *.ncrunchsolution *.*sdf *.ipch .vs/ .build/ .testPublish/ .idea/ .vscode/ *.nuget.props *.nuget.targets global.json ================================================ FILE: .travis.yml ================================================ language: csharp sudo: false dist: trusty env: global: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: 1 mono: none os: - linux - osx osx_image: xcode8.2 addons: apt: packages: - libunwind8 branches: only: - master - /^release\/.*$/ - /^(.*\/)?ci-.*$/ before_install: - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi script: - ./build.sh ================================================ FILE: .vsts-pipelines/builds/ci-internal.yml ================================================ trigger: - master - release/* resources: repositories: - repository: buildtools type: git name: aspnet-BuildTools ref: refs/heads/master phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools ================================================ FILE: .vsts-pipelines/builds/ci-public.yml ================================================ trigger: - master - release/* # See https://github.com/aspnet/BuildTools resources: repositories: - repository: buildtools type: github endpoint: DotNet-Bot GitHub Connection name: aspnet/BuildTools ref: refs/heads/master phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools ================================================ FILE: BasicMiddleware.sln ================================================ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 15.0.26730.03 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOverrides", "src\Microsoft.AspNetCore.HttpOverrides\Microsoft.AspNetCore.HttpOverrides.csproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A5076D28-FA7E-4606-9410-FEDD0D603527}" ProjectSection(SolutionItems) = preProject src\Directory.Build.props = src\Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8437B0F3-3894-4828-A945-A9187F37631D}" ProjectSection(SolutionItems) = preProject test\Directory.Build.props = test\Directory.Build.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOverrides.Tests", "test\Microsoft.AspNetCore.HttpOverrides.Tests\Microsoft.AspNetCore.HttpOverrides.Tests.csproj", "{D6341B92-3416-4F11-8DF4-CB274296175F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Buffering", "src\Microsoft.AspNetCore.Buffering\Microsoft.AspNetCore.Buffering.csproj", "{2363D0DD-A3BF-437E-9B64-B33AE132D875}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Buffering.Tests", "test\Microsoft.AspNetCore.Buffering.Tests\Microsoft.AspNetCore.Buffering.Tests.csproj", "{F5F1D123-9C81-4A9E-8644-AA46B8E578FB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9587FE9F-5A17-42C4-8021-E87F59CECB98}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResponseBufferingSample", "samples\ResponseBufferingSample\ResponseBufferingSample.csproj", "{E5C55B80-7827-40EB-B661-32B0E0E431CA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpOverridesSample", "samples\HttpOverridesSample\HttpOverridesSample.csproj", "{7F95478D-E1D4-4A64-BA42-B041591A96EB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Rewrite", "src\Microsoft.AspNetCore.Rewrite\Microsoft.AspNetCore.Rewrite.csproj", "{0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RewriteSample", "samples\RewriteSample\RewriteSample.csproj", "{9E049645-13BC-4598-89E1-5B43D36E5D14}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Rewrite.Tests", "test\Microsoft.AspNetCore.Rewrite.Tests\Microsoft.AspNetCore.Rewrite.Tests.csproj", "{31794F9E-A1AA-4535-B03C-A3233737CD1A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ResponseCompression", "src\Microsoft.AspNetCore.ResponseCompression\Microsoft.AspNetCore.ResponseCompression.csproj", "{45308A9D-F4C6-46A8-A24F-E73D995CC223}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ResponseCompression.Tests", "test\Microsoft.AspNetCore.ResponseCompression.Tests\Microsoft.AspNetCore.ResponseCompression.Tests.csproj", "{3360A5D1-70C0-49EE-9051-04A6A6B836DC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResponseCompressionSample", "samples\ResponseCompressionSample\ResponseCompressionSample.csproj", "{B2A3CE38-51B2-4486-982C-98C380AF140E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{59A9B64C-E9BE-409E-89A2-58D72E2918F5}" ProjectSection(SolutionItems) = preProject .appveyor.yml = .appveyor.yml .gitattributes = .gitattributes .gitignore = .gitignore .travis.yml = .travis.yml build.cmd = build.cmd build.ps1 = build.ps1 build.sh = build.sh CONTRIBUTING.md = CONTRIBUTING.md Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets LICENSE.txt = LICENSE.txt NuGet.config = NuGet.config NuGetPackageVerifier.json = NuGetPackageVerifier.json README.md = README.md version.xml = version.xml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy", "src\Microsoft.AspNetCore.HttpsPolicy\Microsoft.AspNetCore.HttpsPolicy.csproj", "{4D39C29B-4EC8-497C-B411-922DA494D71B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpsPolicySample", "samples\HttpsPolicySample\HttpsPolicySample.csproj", "{AC424AEE-4883-49C6-945F-2FC916B8CA1C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy.Tests", "test\Microsoft.AspNetCore.HttpsPolicy.Tests\Microsoft.AspNetCore.HttpsPolicy.Tests.csproj", "{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostFilteringSample", "samples\HostFilteringSample\HostFilteringSample.csproj", "{368B00A2-992A-4B0E-9085-A8136A22922D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5CEA6F31-A829-4A02-8CD5-EC3DDD4CC1EA}" ProjectSection(SolutionItems) = preProject build\dependencies.props = build\dependencies.props build\repo.props = build\repo.props build\sources.props = build\sources.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HostFiltering.Tests", "test\Microsoft.AspNetCore.HostFiltering.Tests\Microsoft.AspNetCore.HostFiltering.Tests.csproj", "{4BC947ED-13B8-4BE6-82A4-96A48D86980B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HostFiltering", "src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj", "{762F7276-C916-4111-A6C0-41668ABB3823}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{C6DA6317-30FC-42FE-891C-64E75D88FF12}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.ResponseCompression.Benchmarks", "benchmarks\Microsoft.AspNetCore.ResponseCompression.Benchmarks\Microsoft.AspNetCore.ResponseCompression.Benchmarks.csproj", "{5AF10E85-5076-40B9-84CF-9830B585ABE5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {517308C3-B477-4B01-B461-CAB9C10B6928}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {517308C3-B477-4B01-B461-CAB9C10B6928}.Debug|Any CPU.Build.0 = Debug|Any CPU {517308C3-B477-4B01-B461-CAB9C10B6928}.Release|Any CPU.ActiveCfg = Release|Any CPU {517308C3-B477-4B01-B461-CAB9C10B6928}.Release|Any CPU.Build.0 = Release|Any CPU {D6341B92-3416-4F11-8DF4-CB274296175F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D6341B92-3416-4F11-8DF4-CB274296175F}.Debug|Any CPU.Build.0 = Debug|Any CPU {D6341B92-3416-4F11-8DF4-CB274296175F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D6341B92-3416-4F11-8DF4-CB274296175F}.Release|Any CPU.Build.0 = Release|Any CPU {2363D0DD-A3BF-437E-9B64-B33AE132D875}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2363D0DD-A3BF-437E-9B64-B33AE132D875}.Debug|Any CPU.Build.0 = Debug|Any CPU {2363D0DD-A3BF-437E-9B64-B33AE132D875}.Release|Any CPU.ActiveCfg = Release|Any CPU {2363D0DD-A3BF-437E-9B64-B33AE132D875}.Release|Any CPU.Build.0 = Release|Any CPU {F5F1D123-9C81-4A9E-8644-AA46B8E578FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F5F1D123-9C81-4A9E-8644-AA46B8E578FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {F5F1D123-9C81-4A9E-8644-AA46B8E578FB}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5F1D123-9C81-4A9E-8644-AA46B8E578FB}.Release|Any CPU.Build.0 = Release|Any CPU {E5C55B80-7827-40EB-B661-32B0E0E431CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E5C55B80-7827-40EB-B661-32B0E0E431CA}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5C55B80-7827-40EB-B661-32B0E0E431CA}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5C55B80-7827-40EB-B661-32B0E0E431CA}.Release|Any CPU.Build.0 = Release|Any CPU {7F95478D-E1D4-4A64-BA42-B041591A96EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F95478D-E1D4-4A64-BA42-B041591A96EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F95478D-E1D4-4A64-BA42-B041591A96EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F95478D-E1D4-4A64-BA42-B041591A96EB}.Release|Any CPU.Build.0 = Release|Any CPU {0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}.Release|Any CPU.Build.0 = Release|Any CPU {9E049645-13BC-4598-89E1-5B43D36E5D14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E049645-13BC-4598-89E1-5B43D36E5D14}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E049645-13BC-4598-89E1-5B43D36E5D14}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E049645-13BC-4598-89E1-5B43D36E5D14}.Release|Any CPU.Build.0 = Release|Any CPU {31794F9E-A1AA-4535-B03C-A3233737CD1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31794F9E-A1AA-4535-B03C-A3233737CD1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {31794F9E-A1AA-4535-B03C-A3233737CD1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {31794F9E-A1AA-4535-B03C-A3233737CD1A}.Release|Any CPU.Build.0 = Release|Any CPU {45308A9D-F4C6-46A8-A24F-E73D995CC223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {45308A9D-F4C6-46A8-A24F-E73D995CC223}.Debug|Any CPU.Build.0 = Debug|Any CPU {45308A9D-F4C6-46A8-A24F-E73D995CC223}.Release|Any CPU.ActiveCfg = Release|Any CPU {45308A9D-F4C6-46A8-A24F-E73D995CC223}.Release|Any CPU.Build.0 = Release|Any CPU {3360A5D1-70C0-49EE-9051-04A6A6B836DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3360A5D1-70C0-49EE-9051-04A6A6B836DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {3360A5D1-70C0-49EE-9051-04A6A6B836DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {3360A5D1-70C0-49EE-9051-04A6A6B836DC}.Release|Any CPU.Build.0 = Release|Any CPU {B2A3CE38-51B2-4486-982C-98C380AF140E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B2A3CE38-51B2-4486-982C-98C380AF140E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2A3CE38-51B2-4486-982C-98C380AF140E}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2A3CE38-51B2-4486-982C-98C380AF140E}.Release|Any CPU.Build.0 = Release|Any CPU {4D39C29B-4EC8-497C-B411-922DA494D71B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4D39C29B-4EC8-497C-B411-922DA494D71B}.Debug|Any CPU.Build.0 = Debug|Any CPU {4D39C29B-4EC8-497C-B411-922DA494D71B}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D39C29B-4EC8-497C-B411-922DA494D71B}.Release|Any CPU.Build.0 = Release|Any CPU {AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Release|Any CPU.Build.0 = Release|Any CPU {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.Build.0 = Release|Any CPU {368B00A2-992A-4B0E-9085-A8136A22922D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {368B00A2-992A-4B0E-9085-A8136A22922D}.Debug|Any CPU.Build.0 = Debug|Any CPU {368B00A2-992A-4B0E-9085-A8136A22922D}.Release|Any CPU.ActiveCfg = Release|Any CPU {368B00A2-992A-4B0E-9085-A8136A22922D}.Release|Any CPU.Build.0 = Release|Any CPU {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Release|Any CPU.Build.0 = Release|Any CPU {762F7276-C916-4111-A6C0-41668ABB3823}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {762F7276-C916-4111-A6C0-41668ABB3823}.Debug|Any CPU.Build.0 = Debug|Any CPU {762F7276-C916-4111-A6C0-41668ABB3823}.Release|Any CPU.ActiveCfg = Release|Any CPU {762F7276-C916-4111-A6C0-41668ABB3823}.Release|Any CPU.Build.0 = Release|Any CPU {5AF10E85-5076-40B9-84CF-9830B585ABE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5AF10E85-5076-40B9-84CF-9830B585ABE5}.Debug|Any CPU.Build.0 = Debug|Any CPU {5AF10E85-5076-40B9-84CF-9830B585ABE5}.Release|Any CPU.ActiveCfg = Release|Any CPU {5AF10E85-5076-40B9-84CF-9830B585ABE5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {517308C3-B477-4B01-B461-CAB9C10B6928} = {A5076D28-FA7E-4606-9410-FEDD0D603527} {D6341B92-3416-4F11-8DF4-CB274296175F} = {8437B0F3-3894-4828-A945-A9187F37631D} {2363D0DD-A3BF-437E-9B64-B33AE132D875} = {A5076D28-FA7E-4606-9410-FEDD0D603527} {F5F1D123-9C81-4A9E-8644-AA46B8E578FB} = {8437B0F3-3894-4828-A945-A9187F37631D} {E5C55B80-7827-40EB-B661-32B0E0E431CA} = {9587FE9F-5A17-42C4-8021-E87F59CECB98} {7F95478D-E1D4-4A64-BA42-B041591A96EB} = {9587FE9F-5A17-42C4-8021-E87F59CECB98} {0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1} = {A5076D28-FA7E-4606-9410-FEDD0D603527} {9E049645-13BC-4598-89E1-5B43D36E5D14} = {9587FE9F-5A17-42C4-8021-E87F59CECB98} {31794F9E-A1AA-4535-B03C-A3233737CD1A} = {8437B0F3-3894-4828-A945-A9187F37631D} {45308A9D-F4C6-46A8-A24F-E73D995CC223} = {A5076D28-FA7E-4606-9410-FEDD0D603527} {3360A5D1-70C0-49EE-9051-04A6A6B836DC} = {8437B0F3-3894-4828-A945-A9187F37631D} {B2A3CE38-51B2-4486-982C-98C380AF140E} = {9587FE9F-5A17-42C4-8021-E87F59CECB98} {4D39C29B-4EC8-497C-B411-922DA494D71B} = {A5076D28-FA7E-4606-9410-FEDD0D603527} {AC424AEE-4883-49C6-945F-2FC916B8CA1C} = {9587FE9F-5A17-42C4-8021-E87F59CECB98} {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE} = {8437B0F3-3894-4828-A945-A9187F37631D} {368B00A2-992A-4B0E-9085-A8136A22922D} = {9587FE9F-5A17-42C4-8021-E87F59CECB98} {5CEA6F31-A829-4A02-8CD5-EC3DDD4CC1EA} = {59A9B64C-E9BE-409E-89A2-58D72E2918F5} {4BC947ED-13B8-4BE6-82A4-96A48D86980B} = {8437B0F3-3894-4828-A945-A9187F37631D} {762F7276-C916-4111-A6C0-41668ABB3823} = {A5076D28-FA7E-4606-9410-FEDD0D603527} {5AF10E85-5076-40B9-84CF-9830B585ABE5} = {C6DA6317-30FC-42FE-891C-64E75D88FF12} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158} EndGlobalSection EndGlobal ================================================ FILE: CONTRIBUTING.md ================================================ Contributing ====== Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. ================================================ FILE: Directory.Build.props ================================================  Microsoft ASP.NET Core https://github.com/aspnet/BasicMiddleware git $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)build\Key.snk true true ================================================ FILE: Directory.Build.targets ================================================ $(MicrosoftNETCoreApp21PackageVersion) $(MicrosoftNETCoreApp22PackageVersion) $(NETStandardLibrary20PackageVersion) 99.9 ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright (c) .NET Foundation and Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: NuGet.config ================================================  ================================================ FILE: NuGetPackageVerifier.json ================================================ { "Default": { "rules": [ "DefaultCompositeRule" ] } } ================================================ FILE: README.md ================================================ ASP.NET Core Basic Middleware Components [Archived] =================================================== **This GitHub project has been archived.** Ongoing development on this project can be found in . This repo hosts a collection of basic middleware components for ASP.NET Core. This includes Buffering, HTTP Overrides, Response Compression, and URL Rewriting. The Rewrite middleware can import rules from IIS's UrlRewrite and Apache's mod_rewrite. This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo. ================================================ FILE: benchmarks/Microsoft.AspNetCore.ResponseCompression.Benchmarks/AssemblyInfo.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. [assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark] ================================================ FILE: benchmarks/Microsoft.AspNetCore.ResponseCompression.Benchmarks/Microsoft.AspNetCore.ResponseCompression.Benchmarks.csproj ================================================  Exe netcoreapp2.1 ================================================ FILE: benchmarks/Microsoft.AspNetCore.ResponseCompression.Benchmarks/ResponseCompressionProviderBenchmark.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.ResponseCompression.Benchmarks { public class ResponseCompressionProviderBenchmark { [GlobalSetup] public void GlobalSetup() { var services = new ServiceCollection() .AddOptions() .AddResponseCompression() .BuildServiceProvider(); var options = new ResponseCompressionOptions(); Provider = new ResponseCompressionProvider(services, Options.Create(options)); } [ParamsSource(nameof(EncodingStrings))] public string AcceptEncoding { get; set; } public static IEnumerable EncodingStrings() { return new[] { "gzip;q=0.8, compress;q=0.6, br;q=0.4", "gzip, compress, br", "br, compress, gzip", "gzip, compress", "identity", "*" }; } public ResponseCompressionProvider Provider { get; set; } [Benchmark] public ICompressionProvider GetCompressionProvider() { var context = new DefaultHttpContext(); context.Request.Headers[HeaderNames.AcceptEncoding] = AcceptEncoding; return Provider.GetCompressionProvider(context); } } } ================================================ FILE: build/dependencies.props ================================================  $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 0.10.13 3.0.0-alpha1-20181011.3 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 3.0.0-alpha1-10605 2.1.3 2.2.0-preview2-26905-02 3.0.0-alpha1-10605 15.6.1 4.10.0 2.0.3 0.10.0 2.3.1 2.4.0 ================================================ FILE: build/repo.props ================================================ Internal.AspNetCore.Universe.Lineup https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json ================================================ FILE: build/sources.props ================================================ $(DotNetRestoreSources) $(RestoreSources); https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; $(RestoreSources); https://api.nuget.org/v3/index.json; ================================================ FILE: build.cmd ================================================ @ECHO OFF PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" ================================================ FILE: build.sh ================================================ #!/usr/bin/env bash set -euo pipefail DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs) chmod +x "$DIR/run.sh"; sync "$DIR/run.sh" default-build "$@" ================================================ FILE: korebuild-lock.txt ================================================ version:3.0.0-alpha1-20181011.3 commithash:e7569d931e994629267ab2646e9926140962b4ac ================================================ FILE: korebuild.json ================================================ { "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", "channel": "master" } ================================================ FILE: run.cmd ================================================ @ECHO OFF PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" ================================================ FILE: run.ps1 ================================================ #!/usr/bin/env powershell #requires -version 4 <# .SYNOPSIS Executes KoreBuild commands. .DESCRIPTION Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. .PARAMETER Command The KoreBuild command to run. .PARAMETER Path The folder to build. Defaults to the folder containing this script. .PARAMETER Channel The channel of KoreBuild to download. Overrides the value from the config file. .PARAMETER DotNetHome The directory where .NET Core tools will be stored. .PARAMETER ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file. .PARAMETER Update Updates KoreBuild to the latest version even if a lock file is present. .PARAMETER Reinstall Re-installs KoreBuild .PARAMETER ConfigFile The path to the configuration file that stores values. Defaults to korebuild.json. .PARAMETER ToolsSourceSuffix The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. .PARAMETER CI Sets up CI specific settings and variables. .PARAMETER Arguments Arguments to be passed to the command .NOTES This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set in the file are overridden by command line parameters. .EXAMPLE Example config file: ```json { "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", "channel": "master", "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" } ``` #> [CmdletBinding(PositionalBinding = $false)] param( [Parameter(Mandatory = $true, Position = 0)] [string]$Command, [string]$Path = $PSScriptRoot, [Alias('c')] [string]$Channel, [Alias('d')] [string]$DotNetHome, [Alias('s')] [string]$ToolsSource, [Alias('u')] [switch]$Update, [switch]$Reinstall, [string]$ToolsSourceSuffix, [string]$ConfigFile = $null, [switch]$CI, [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Arguments ) Set-StrictMode -Version 2 $ErrorActionPreference = 'Stop' # # Functions # function Get-KoreBuild { $lockFile = Join-Path $Path 'korebuild-lock.txt' if (!(Test-Path $lockFile) -or $Update) { Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix } $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 if (!$version) { Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" } $version = $version.TrimStart('version:').Trim() $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) if ($Reinstall -and (Test-Path $korebuildPath)) { Remove-Item -Force -Recurse $korebuildPath } if (!(Test-Path $korebuildPath)) { Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" New-Item -ItemType Directory -Path $korebuildPath | Out-Null $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" try { $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { # Use built-in commands where possible as they are cross-plat compatible Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath } else { # Fallback to old approach for old installations of PowerShell Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) } } catch { Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore throw } finally { Remove-Item $tmpfile -ErrorAction Ignore } } return $korebuildPath } function Join-Paths([string]$path, [string[]]$childPaths) { $childPaths | ForEach-Object { $path = Join-Path $path $_ } return $path } function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { if ($RemotePath -notlike 'http*') { Copy-Item $RemotePath $LocalPath return } $retries = 10 while ($retries -gt 0) { $retries -= 1 try { Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath return } catch { Write-Verbose "Request failed. $retries retries remaining" } } Write-Error "Download failed: '$RemotePath'." } # # Main # # Load configuration or set defaults $Path = Resolve-Path $Path if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } if (Test-Path $ConfigFile) { try { $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json if ($config) { if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} } } catch { Write-Host -ForegroundColor Red $Error[0] Write-Error "$ConfigFile contains invalid JSON." exit 1 } } if (!$DotNetHome) { $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` else { Join-Path $PSScriptRoot '.dotnet'} } if (!$Channel) { $Channel = 'master' } if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } # Execute $korebuildPath = Get-KoreBuild Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') try { Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI Invoke-KoreBuildCommand $Command @Arguments } finally { Remove-Module 'KoreBuild' -ErrorAction Ignore } ================================================ FILE: run.sh ================================================ #!/usr/bin/env bash set -euo pipefail # # variables # RESET="\033[0m" RED="\033[0;31m" YELLOW="\033[0;33m" MAGENTA="\033[0;95m" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" verbose=false update=false reinstall=false repo_path="$DIR" channel='' tools_source='' tools_source_suffix='' ci=false # # Functions # __usage() { echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]" echo "" echo "Arguments:" echo " command The command to be run." echo " ... Arguments passed to the command. Variable number of arguments allowed." echo "" echo "Options:" echo " --verbose Show verbose output." echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.." echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json." echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." echo " --path The directory to build. Defaults to the directory containing the script." echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file." echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings." echo " -u|--update Update to the latest KoreBuild even if the lock file is present." echo " --reinstall Reinstall KoreBuild." echo " --ci Apply CI specific settings and environment variables." echo "" echo "Description:" echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." if [[ "${1:-}" != '--no-exit' ]]; then exit 2 fi } get_korebuild() { local version local lock_file="$repo_path/korebuild-lock.txt" if [ ! -f "$lock_file" ] || [ "$update" = true ]; then __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix" fi version="$(grep 'version:*' -m 1 "$lock_file")" if [[ "$version" == '' ]]; then __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" return 1 fi version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then rm -rf "$korebuild_path" fi { if [ ! -d "$korebuild_path" ]; then mkdir -p "$korebuild_path" local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" tmpfile="$(mktemp)" echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then unzip -q -d "$korebuild_path" "$tmpfile" fi rm "$tmpfile" || true fi source "$korebuild_path/KoreBuild.sh" } || { if [ -d "$korebuild_path" ]; then echo "Cleaning up after failed installation" rm -rf "$korebuild_path" || true fi return 1 } } __error() { echo -e "${RED}error: $*${RESET}" 1>&2 } __warn() { echo -e "${YELLOW}warning: $*${RESET}" } __machine_has() { hash "$1" > /dev/null 2>&1 return $? } __get_remote_file() { local remote_path=$1 local local_path=$2 local remote_path_suffix=$3 if [[ "$remote_path" != 'http'* ]]; then cp "$remote_path" "$local_path" return 0 fi local failed=false if __machine_has wget; then wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true else failed=true fi if [ "$failed" = true ] && __machine_has curl; then failed=false curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true fi if [ "$failed" = true ]; then __error "Download failed: $remote_path" 1>&2 return 1 fi } # # main # command="${1:-}" shift while [[ $# -gt 0 ]]; do case $1 in -\?|-h|--help) __usage --no-exit exit 0 ;; -c|--channel|-Channel) shift channel="${1:-}" [ -z "$channel" ] && __usage ;; --config-file|-ConfigFile) shift config_file="${1:-}" [ -z "$config_file" ] && __usage if [ ! -f "$config_file" ]; then __error "Invalid value for --config-file. $config_file does not exist." exit 1 fi ;; -d|--dotnet-home|-DotNetHome) shift DOTNET_HOME="${1:-}" [ -z "$DOTNET_HOME" ] && __usage ;; --path|-Path) shift repo_path="${1:-}" [ -z "$repo_path" ] && __usage ;; -s|--tools-source|-ToolsSource) shift tools_source="${1:-}" [ -z "$tools_source" ] && __usage ;; --tools-source-suffix|-ToolsSourceSuffix) shift tools_source_suffix="${1:-}" [ -z "$tools_source_suffix" ] && __usage ;; -u|--update|-Update) update=true ;; --reinstall|-[Rr]einstall) reinstall=true ;; --ci|-[Cc][Ii]) ci=true ;; --verbose|-Verbose) verbose=true ;; --) shift break ;; *) break ;; esac shift done if ! __machine_has unzip; then __error 'Missing required command: unzip' exit 1 fi if ! __machine_has curl && ! __machine_has wget; then __error 'Missing required command. Either wget or curl is required.' exit 1 fi [ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json" if [ -f "$config_file" ]; then if __machine_has jq ; then if jq '.' "$config_file" >/dev/null ; then config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" else __error "$config_file contains invalid JSON." exit 1 fi elif __machine_has python ; then if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else __error "$config_file contains invalid JSON." exit 1 fi elif __machine_has python3 ; then if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else __error "$config_file contains invalid JSON." exit 1 fi else __error 'Missing required command: jq or python. Could not parse the JSON file.' exit 1 fi [ ! -z "${config_channel:-}" ] && channel="$config_channel" [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" fi [ -z "$channel" ] && channel='master' [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' get_korebuild set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci" invoke_korebuild_command "$command" "$@" ================================================ FILE: samples/HostFilteringSample/HostFilteringSample.csproj ================================================ netcoreapp2.2;net461 PreserveNewest PreserveNewest PreserveNewest ================================================ FILE: samples/HostFilteringSample/Program.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace HostFilteringSample { public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) { var hostBuilder = new WebHostBuilder() .ConfigureLogging((_, factory) => { factory.SetMinimumLevel(LogLevel.Debug); factory.AddConsole(); }) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); }) .UseKestrel() .UseStartup(); return hostBuilder.Build(); } } } ================================================ FILE: samples/HostFilteringSample/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:14124/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "HostFilteringSample": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:14125/" } } } ================================================ FILE: samples/HostFilteringSample/Startup.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HostFiltering; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace HostFilteringSample { public class Startup { public IConfiguration Config { get; } public Startup(IConfiguration config) { Config = config; } public void ConfigureServices(IServiceCollection services) { services.AddHostFiltering(options => { }); // Fallback services.PostConfigure(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = Config["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification services.AddSingleton>(new ConfigurationChangeTokenSource(Config)); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseHostFiltering(); app.Run(context => { return context.Response.WriteAsync("Hello World! " + context.Request.Host); }); } } } ================================================ FILE: samples/HostFilteringSample/appsettings.Development.json ================================================ { "AllowedHosts": "localhost;127.0.0.1;[::1]" } ================================================ FILE: samples/HostFilteringSample/appsettings.Production.json ================================================ { "AllowedHosts": "example.com;localhost" } ================================================ FILE: samples/HostFilteringSample/appsettings.json ================================================ { } ================================================ FILE: samples/HttpOverridesSample/HttpOverridesSample.csproj ================================================  netcoreapp2.2;net461 ================================================ FILE: samples/HttpOverridesSample/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:1658/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "web": { "commandName": "web", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: samples/HttpOverridesSample/Startup.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; namespace HttpOverridesSample { public class Startup { // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); app.UseHttpMethodOverride(); app.Run(async (context) => { foreach (var header in context.Request.Headers) { await context.Response.WriteAsync($"{header.Key}: {header.Value}\r\n"); } await context.Response.WriteAsync($"Method: {context.Request.Method}\r\n"); await context.Response.WriteAsync($"Scheme: {context.Request.Scheme}\r\n"); await context.Response.WriteAsync($"RemoteIP: {context.Connection.RemoteIpAddress}\r\n"); await context.Response.WriteAsync($"RemotePort: {context.Connection.RemotePort}\r\n"); }); } // Entry point for the application. public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() // .UseIIS() // This repo can no longer reference IIS because IISIntegration depends on it. .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: samples/HttpsPolicySample/HttpsPolicySample.csproj ================================================  net461;netcoreapp2.2 netcoreapp2.2 ================================================ FILE: samples/HttpsPolicySample/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:31894/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "HttpsSample": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5000/" } } } ================================================ FILE: samples/HttpsPolicySample/Startup.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace HttpsSample { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddHttpsRedirection(options => { options.RedirectStatusCode = StatusCodes.Status301MovedPermanently; options.HttpsPort = 5001; }); services.AddHsts(options => { options.MaxAge = TimeSpan.FromDays(30); options.Preload = true; options.IncludeSubDomains = true; }); } public void Configure(IApplicationBuilder app, IHostingEnvironment environment) { if (!environment.IsDevelopment()) { app.UseHsts(); } app.UseHttpsRedirection(); app.Run(async context => { await context.Response.WriteAsync("Hello world!"); }); } // Entry point for the application. public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel( options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 5001), listenOptions => { listenOptions.UseHttps("testCert.pfx", "testPassword"); }); options.Listen(new IPEndPoint(IPAddress.Loopback, 5000), listenOptions => { }); }) .UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file .ConfigureLogging(factory => { factory.SetMinimumLevel(LogLevel.Debug); factory.AddConsole(); }) .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: samples/ResponseBufferingSample/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:49657/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNET_ENV": "Development" } }, "web": { "commandName": "web", "environmentVariables": { "Hosting:Environment": "Development" } } } } ================================================ FILE: samples/ResponseBufferingSample/ResponseBufferingSample.csproj ================================================  netcoreapp2.2;net461 ================================================ FILE: samples/ResponseBufferingSample/Startup.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace ResponseBufferingSample { public class Startup { // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app) { app.UseResponseBuffering(); app.Run(async (context) => { // Write some stuff context.Response.ContentType = "text/other"; await context.Response.WriteAsync("Hello World!"); // ... more work ... // Something went wrong and we want to replace the response context.Response.StatusCode = 200; context.Response.Headers.Clear(); context.Response.Body.SetLength(0); // Try again context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("Hi Bob!"); }); } // Entry point for the application. public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() // .UseIIS() // This repo can no longer reference IIS because IISIntegration depends on it. .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: samples/ResponseCompressionSample/CustomCompressionProvider.cs ================================================ using System.IO; using Microsoft.AspNetCore.ResponseCompression; namespace ResponseCompressionSample { public class CustomCompressionProvider : ICompressionProvider { public string EncodingName => "custom"; public bool SupportsFlush => true; public Stream CreateStream(Stream outputStream) { // Create a custom compression stream wrapper here return outputStream; } } } ================================================ FILE: samples/ResponseCompressionSample/LoremIpsum.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace ResponseCompressionSample { internal static class LoremIpsum { internal const string Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit." + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar.Nulla sollicitudin.Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula.Pellentesque rhoncus nunc et augue.Integer id felis.Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas.Lorem ipsum dolor sit amet, consectetuer adipiscing elit.Morbi vel erat non mauris convallis vehicula.Nulla et sapien.Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam.Mauris ullamcorper felis vitae erat.Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna." + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien.Vivamus leo. Aliquam euismod libero eu enim.Nulla nec felis sed leo placerat imperdiet.Aenean suscipit nulla in justo.Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet."; } } ================================================ FILE: samples/ResponseCompressionSample/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:6164/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "ResponseCompressionSample": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: samples/ResponseCompressionSample/ResponseCompressionSample.csproj ================================================  netcoreapp2.2;net461 ================================================ FILE: samples/ResponseCompressionSample/Startup.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace ResponseCompressionSample { public class Startup { public void ConfigureServices(IServiceCollection services) { services.Configure(options => options.Level = CompressionLevel.Fastest); services.AddResponseCompression(options => { options.Providers.Add(); options.Providers.Add(); // .Append(TItem) is only available on Core. options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "image/svg+xml" }); ////Example of using excluded and wildcard MIME types: ////Compress all MIME types except various media types, but do compress SVG images. //options.MimeTypes = new[] { "*/*", "image/svg+xml" }; //options.ExcludedMimeTypes = new[] { "image/*", "audio/*", "video/*" }; }); } public void Configure(IApplicationBuilder app) { app.UseResponseCompression(); app.Map("/testfile1kb.txt", fileApp => { fileApp.Run(context => { context.Response.ContentType = "text/plain"; return context.Response.SendFileAsync("testfile1kb.txt"); }); }); app.Map("/trickle", trickleApp => { trickleApp.Run(async context => { context.Response.ContentType = "text/plain"; // Disables compression on net451 because that GZipStream does not implement Flush. context.Features.Get()?.DisableResponseBuffering(); for (int i = 0; i < 100; i++) { await context.Response.WriteAsync("a"); await context.Response.Body.FlushAsync(); await Task.Delay(TimeSpan.FromSeconds(1)); } }); }); app.Run(async context => { context.Response.ContentType = "text/plain"; await context.Response.WriteAsync(LoremIpsum.Text); }); } public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .ConfigureLogging(factory => { factory.AddConsole() .SetMinimumLevel(LogLevel.Debug); }) .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: samples/ResponseCompressionSample/testfile1kb.txt ================================================ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ================================================ FILE: samples/RewriteSample/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:6156/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "RewriteSample": { "commandName": "Project" } } } ================================================ FILE: samples/RewriteSample/Rewrite.txt ================================================ # Rewrite path with additional sub directory RewriteCond %{HTTP_HOST} !^www\.example\.com [NC,OR] RewriteCond %{SERVER_PORT} !^5000$ RewriteRule ^/(.*) http://www.example.com/$1 [L,R=302] ================================================ FILE: samples/RewriteSample/RewriteSample.csproj ================================================  netcoreapp2.2;net461 ================================================ FILE: samples/RewriteSample/Startup.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.DependencyInjection; namespace RewriteSample { public class Startup { public Startup(IHostingEnvironment environment) { Environment = environment; } public IHostingEnvironment Environment { get; private set; } public void ConfigureServices(IServiceCollection services) { services.Configure(options => { options.AddRedirect("(.*)/$", "$1") .AddRewrite(@"app/(\d+)", "app?id=$1", skipRemainingRules: false) .AddRedirectToHttps(302, 5001) .AddIISUrlRewrite(Environment.ContentRootFileProvider, "UrlRewrite.xml") .AddApacheModRewrite(Environment.ContentRootFileProvider, "Rewrite.txt"); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseRewriter(); app.Run(context => { return context.Response.WriteAsync($"Rewritten Url: {context.Request.Path + context.Request.QueryString}"); }); } public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel(options => { options.Listen(IPAddress.Loopback, 5000); options.Listen(IPAddress.Loopback, 5001, listenOptions => { // Configure SSL listenOptions.UseHttps("testCert.pfx", "testPassword"); }); }) .UseStartup() .UseContentRoot(Directory.GetCurrentDirectory()) .Build(); host.Run(); } } } ================================================ FILE: samples/RewriteSample/UrlRewrite.xml ================================================  ================================================ FILE: src/Directory.Build.props ================================================ ================================================ FILE: src/Microsoft.AspNetCore.Buffering/BufferingWriteStream.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNetCore.Buffering { internal class BufferingWriteStream : Stream { private readonly Stream _innerStream; private readonly MemoryStream _buffer = new MemoryStream(); private bool _isBuffering = true; public BufferingWriteStream(Stream innerStream) { _innerStream = innerStream; } public override bool CanRead { get { return false; } } public override bool CanSeek { get { return _isBuffering; } } public override bool CanWrite { get { return _innerStream.CanWrite; } } public override long Length { get { if (_isBuffering) { return _buffer.Length; } // May throw return _innerStream.Length; } } // Clear/Reset the buffer by setting Position, Seek, or SetLength to 0. Random access is not supported. public override long Position { get { if (_isBuffering) { return _buffer.Position; } // May throw return _innerStream.Position; } set { if (_isBuffering) { if (value != 0) { throw new ArgumentOutOfRangeException(nameof(value), value, nameof(Position) + " can only be set to 0."); } _buffer.Position = value; _buffer.SetLength(value); } else { // May throw _innerStream.Position = value; } } } // Clear/Reset the buffer by setting Position, Seek, or SetLength to 0. Random access is not supported. public override void SetLength(long value) { if (_isBuffering) { if (value != 0) { throw new ArgumentOutOfRangeException(nameof(value), value, nameof(Length) + " can only be set to 0."); } _buffer.Position = value; _buffer.SetLength(value); } else { // May throw _innerStream.SetLength(value); } } // Clear/Reset the buffer by setting Position, Seek, or SetLength to 0. Random access is not supported. public override long Seek(long offset, SeekOrigin origin) { if (_isBuffering) { if (origin != SeekOrigin.Begin) { throw new ArgumentException(nameof(origin), nameof(Seek) + " can only be set to " + nameof(SeekOrigin.Begin) + "."); } if (offset != 0) { throw new ArgumentOutOfRangeException(nameof(offset), offset, nameof(Seek) + " can only be set to 0."); } _buffer.SetLength(offset); return _buffer.Seek(offset, origin); } // Try the inner stream instead, but this will usually fail. return _innerStream.Seek(offset, origin); } internal void DisableBuffering() { _isBuffering = false; if (_buffer.Length > 0) { Flush(); } } internal Task DisableBufferingAsync(CancellationToken cancellationToken) { _isBuffering = false; if (_buffer.Length > 0) { return FlushAsync(cancellationToken); } return Task.CompletedTask; } public override void Write(byte[] buffer, int offset, int count) { if (_isBuffering) { _buffer.Write(buffer, offset, count); } else { _innerStream.Write(buffer, offset, count); } } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { if (_isBuffering) { return _buffer.WriteAsync(buffer, offset, count, cancellationToken); } else { return _innerStream.WriteAsync(buffer, offset, count, cancellationToken); } } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { if (_isBuffering) { return _buffer.BeginWrite(buffer, offset, count, callback, state); } else { return _innerStream.BeginWrite(buffer, offset, count, callback, state); } } public override void EndWrite(IAsyncResult asyncResult) { if (_isBuffering) { _buffer.EndWrite(asyncResult); } else { _innerStream.EndWrite(asyncResult); } } public override void Flush() { _isBuffering = false; if (_buffer.Length > 0) { _buffer.Seek(0, SeekOrigin.Begin); _buffer.CopyTo(_innerStream); _buffer.Seek(0, SeekOrigin.Begin); _buffer.SetLength(0); } _innerStream.Flush(); } public override async Task FlushAsync(CancellationToken cancellationToken) { _isBuffering = false; if (_buffer.Length > 0) { _buffer.Seek(0, SeekOrigin.Begin); await _buffer.CopyToAsync(_innerStream, 1024 * 16, cancellationToken); _buffer.Seek(0, SeekOrigin.Begin); _buffer.SetLength(0); } await _innerStream.FlushAsync(cancellationToken); } public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException("This Stream only supports Write operations."); } } } ================================================ FILE: src/Microsoft.AspNetCore.Buffering/HttpBufferingFeature.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Buffering { internal class HttpBufferingFeature : IHttpBufferingFeature { private readonly BufferingWriteStream _buffer; private readonly IHttpBufferingFeature _innerFeature; internal HttpBufferingFeature(BufferingWriteStream buffer, IHttpBufferingFeature innerFeature) { _buffer = buffer; _innerFeature = innerFeature; } public void DisableRequestBuffering() { _innerFeature?.DisableRequestBuffering(); } public void DisableResponseBuffering() { _buffer.DisableBuffering(); _innerFeature?.DisableResponseBuffering(); } } } ================================================ FILE: src/Microsoft.AspNetCore.Buffering/Microsoft.AspNetCore.Buffering.csproj ================================================  $(ExperimentalVersionPrefix) $(ExperimentalVersionSuffix) false $(ExperimentalPackageVersion) ASP.NET Core middleware for buffering response bodies. netstandard2.0 $(NoWarn);CS1591 true aspnetcore;buffer;buffering ================================================ FILE: src/Microsoft.AspNetCore.Buffering/ResponseBufferingMiddleware.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Buffering { public class ResponseBufferingMiddleware { private readonly RequestDelegate _next; public ResponseBufferingMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext) { var originalResponseBody = httpContext.Response.Body; // no-op if buffering is already available. if (originalResponseBody.CanSeek) { await _next(httpContext); return; } var originalBufferingFeature = httpContext.Features.Get(); var originalSendFileFeature = httpContext.Features.Get(); try { // Shim the response stream var bufferStream = new BufferingWriteStream(originalResponseBody); httpContext.Response.Body = bufferStream; httpContext.Features.Set(new HttpBufferingFeature(bufferStream, originalBufferingFeature)); if (originalSendFileFeature != null) { httpContext.Features.Set(new SendFileFeatureWrapper(originalSendFileFeature, bufferStream)); } await _next(httpContext); // If we're still buffered, set the content-length header and flush the buffer. // Only if the content-length header is not already set, and some content was buffered. if (!httpContext.Response.HasStarted && bufferStream.CanSeek && bufferStream.Length > 0) { if (!httpContext.Response.ContentLength.HasValue) { httpContext.Response.ContentLength = bufferStream.Length; } await bufferStream.FlushAsync(); } } finally { // undo everything httpContext.Features.Set(originalBufferingFeature); httpContext.Features.Set(originalSendFileFeature); httpContext.Response.Body = originalResponseBody; } } } } ================================================ FILE: src/Microsoft.AspNetCore.Buffering/ResponseBufferingMiddlewareExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Buffering; namespace Microsoft.AspNetCore.Builder { public static class ResponseBufferingMiddlewareExtensions { /// /// Enables full buffering of response bodies. This can be disabled on a per request basis using IHttpBufferingFeature. /// /// /// public static IApplicationBuilder UseResponseBuffering(this IApplicationBuilder builder) { return builder.UseMiddleware(); } } } ================================================ FILE: src/Microsoft.AspNetCore.Buffering/SendFileFeatureWrapper.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Buffering { internal class SendFileFeatureWrapper : IHttpSendFileFeature { private readonly IHttpSendFileFeature _originalSendFileFeature; private readonly BufferingWriteStream _bufferStream; public SendFileFeatureWrapper(IHttpSendFileFeature originalSendFileFeature, BufferingWriteStream bufferStream) { _originalSendFileFeature = originalSendFileFeature; _bufferStream = bufferStream; } // Flush and disable the buffer if anyone tries to call the SendFile feature. public async Task SendFileAsync(string path, long offset, long? length, CancellationToken cancellation) { await _bufferStream.DisableBufferingAsync(cancellation); await _originalSendFileFeature.SendFileAsync(path, offset, length, cancellation); } } } ================================================ FILE: src/Microsoft.AspNetCore.HostFiltering/HostFilteringBuilderExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.HostFiltering; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the HostFiltering middleware. /// public static class HostFilteringBuilderExtensions { /// /// Adds middleware for filtering requests by allowed host headers. Invalid requests will be rejected with a /// 400 status code. /// /// The instance this method extends. /// The original . public static IApplicationBuilder UseHostFiltering(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } app.UseMiddleware(); return app; } } } ================================================ FILE: src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.HostFiltering { /// /// A middleware used to filter requests by their Host header. /// public class HostFilteringMiddleware { // Matches Http.Sys. private static readonly byte[] DefaultResponse = Encoding.ASCII.GetBytes( "\r\n" + "Bad Request\r\n" + "\r\n" + "

Bad Request - Invalid Hostname

\r\n" + "

HTTP Error 400. The request hostname is invalid.

\r\n" + ""); private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly IOptionsMonitor _optionsMonitor; private HostFilteringOptions _options; private IList _allowedHosts; private bool? _allowAnyNonEmptyHost; /// /// A middleware used to filter requests by their Host header. /// /// /// /// public HostFilteringMiddleware(RequestDelegate next, ILogger logger, IOptionsMonitor optionsMonitor) { _next = next ?? throw new ArgumentNullException(nameof(next)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor)); _options = _optionsMonitor.CurrentValue; _optionsMonitor.OnChange(options => { // Clear the cached settings so the next EnsureConfigured will re-evaluate. _options = options; _allowedHosts = new List(); _allowAnyNonEmptyHost = null; }); } /// /// Processes requests /// /// /// public Task Invoke(HttpContext context) { var allowedHosts = EnsureConfigured(); if (!CheckHost(context, allowedHosts)) { context.Response.StatusCode = 400; if (_options.IncludeFailureMessage) { context.Response.ContentLength = DefaultResponse.Length; context.Response.ContentType = "text/html"; return context.Response.Body.WriteAsync(DefaultResponse, 0, DefaultResponse.Length); } return Task.CompletedTask; } return _next(context); } private IList EnsureConfigured() { if (_allowAnyNonEmptyHost == true || _allowedHosts?.Count > 0) { return _allowedHosts; } var allowedHosts = new List(); if (_options.AllowedHosts?.Count > 0 && !TryProcessHosts(_options.AllowedHosts, allowedHosts)) { _logger.LogDebug("Wildcard detected, all requests with hosts will be allowed."); _allowedHosts = allowedHosts; _allowAnyNonEmptyHost = true; return _allowedHosts; } if (allowedHosts.Count == 0) { throw new InvalidOperationException("No allowed hosts were configured."); } if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Allowed hosts: {Hosts}", string.Join("; ", allowedHosts)); } _allowedHosts = allowedHosts; return _allowedHosts; } // returns false if any wildcards were found private bool TryProcessHosts(IEnumerable incoming, IList results) { foreach (var entry in incoming) { // Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode. var host = new HostString(entry).ToUriComponent(); if (IsTopLevelWildcard(host)) { // Disable filtering return false; } if (!results.Contains(host, StringSegmentComparer.OrdinalIgnoreCase)) { results.Add(host); } } return true; } private bool IsTopLevelWildcard(string host) { return (string.Equals("*", host, StringComparison.Ordinal) // HttpSys wildcard || string.Equals("[::]", host, StringComparison.Ordinal) // Kestrel wildcard, IPv6 Any || string.Equals("0.0.0.0", host, StringComparison.Ordinal)); // IPv4 Any } // This does not duplicate format validations that are expected to be performed by the host. private bool CheckHost(HttpContext context, IList allowedHosts) { var host = new StringSegment(context.Request.Headers[HeaderNames.Host].ToString()).Trim(); if (StringSegment.IsNullOrEmpty(host)) { // Http/1.0 does not require the host header. // Http/1.1 requires the header but the value may be empty. if (!_options.AllowEmptyHosts) { _logger.LogInformation("{Protocol} request rejected due to missing or empty host header.", context.Request.Protocol); return false; } _logger.LogDebug("{Protocol} request allowed with missing or empty host header.", context.Request.Protocol); return true; } if (_allowAnyNonEmptyHost == true) { _logger.LogTrace("All hosts are allowed."); return true; } if (HostString.MatchesAny(host, allowedHosts)) { _logger.LogTrace("The host '{Host}' matches an allowed host.", host); return true; } _logger.LogInformation("The host '{Host}' does not match an allowed host.", host); return false; } } } ================================================ FILE: src/Microsoft.AspNetCore.HostFiltering/HostFilteringOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; namespace Microsoft.AspNetCore.HostFiltering { /// /// Options for the HostFiltering middleware /// public class HostFilteringOptions { /// /// The hosts headers that are allowed to access this site. At least one value is required. /// /// /// /// Port numbers must be excluded. /// A top level wildcard "*" allows all non-empty hosts. /// Subdomain wildcards are permitted. E.g. "*.example.com" matches subdomains like foo.example.com, /// but not the parent domain example.com. /// Unicode host names are allowed but will be converted to punycode for matching. /// IPv6 addresses must include their bounding brackets and be in their normalized form. /// /// public IList AllowedHosts { get; set; } = new List(); /// /// Indicates if requests without hosts are allowed. The default is true. /// /// /// HTTP/1.0 does not require a host header. /// Http/1.1 requires a host header, but says the value may be empty. /// public bool AllowEmptyHosts { get; set; } = true; // Note if this were disabled then things like the status code middleware may try to re-execute // the request. This is a low level protocol violation, pretty error pages should not be required. /// /// Indicates if the 400 response should include a default message or be empty. This is enabled by default. /// public bool IncludeFailureMessage { get; set; } = true; } } ================================================ FILE: src/Microsoft.AspNetCore.HostFiltering/HostFilteringServicesExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.HostFiltering; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the host filtering middleware. /// public static class HostFilteringServicesExtensions { /// /// Adds services and options for the host filtering middleware. /// /// The for adding services. /// A delegate to configure the . /// public static IServiceCollection AddHostFiltering(this IServiceCollection services, Action configureOptions) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (configureOptions == null) { throw new ArgumentNullException(nameof(configureOptions)); } services.Configure(configureOptions); return services; } } } ================================================ FILE: src/Microsoft.AspNetCore.HostFiltering/Microsoft.AspNetCore.HostFiltering.csproj ================================================ ASP.NET Core middleware for filtering out requests with unknown HTTP host headers. netstandard2.0 true aspnetcore ================================================ FILE: src/Microsoft.AspNetCore.HostFiltering/baseline.netcore.json ================================================ { "AssemblyIdentity": "Microsoft.AspNetCore.HostFiltering, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "logger", "Type": "Microsoft.Extensions.Logging.ILogger" }, { "Name": "optionsMonitor", "Type": "Microsoft.Extensions.Options.IOptionsMonitor" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HostFiltering.HostFilteringOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_AllowedHosts", "Parameters": [], "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_AllowedHosts", "Parameters": [ { "Name": "value", "Type": "System.Collections.Generic.IList" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_AllowEmptyHosts", "Parameters": [], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_AllowEmptyHosts", "Parameters": [ { "Name": "value", "Type": "System.Boolean" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_IncludeFailureMessage", "Parameters": [], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_IncludeFailureMessage", "Parameters": [ { "Name": "value", "Type": "System.Boolean" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HostFilteringBuilderExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseHostFiltering", "Parameters": [ { "Name": "app", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HostFilteringServicesExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "AddHostFiltering", "Parameters": [ { "Name": "services", "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" }, { "Name": "configureOptions", "Type": "System.Action" } ], "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] } ] } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeaders.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; namespace Microsoft.AspNetCore.HttpOverrides { [Flags] public enum ForwardedHeaders { None = 0, XForwardedFor = 1 << 0, XForwardedHost = 1 << 1, XForwardedProto = 1 << 2, All = XForwardedFor | XForwardedHost | XForwardedProto } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersDefaults.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.HttpOverrides { /// /// Default values related to middleware /// /// public static class ForwardedHeadersDefaults { /// /// X-Forwarded-For /// public static string XForwardedForHeaderName { get; } = "X-Forwarded-For"; /// /// X-Forwarded-Host /// public static string XForwardedHostHeaderName { get; } = "X-Forwarded-Host"; /// /// X-Forwarded-Proto /// public static string XForwardedProtoHeaderName { get; } = "X-Forwarded-Proto"; /// /// X-Original-For /// public static string XOriginalForHeaderName { get; } = "X-Original-For"; /// /// X-Original-Host /// public static string XOriginalHostHeaderName { get; } = "X-Original-Host"; /// /// X-Original-Proto /// public static string XOriginalProtoHeaderName { get; } = "X-Original-Proto"; } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { public static class ForwardedHeadersExtensions { /// /// Forwards proxied headers onto current request /// /// /// public static IApplicationBuilder UseForwardedHeaders(this IApplicationBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } return builder.UseMiddleware(); } /// /// Forwards proxied headers onto current request /// /// /// Enables the different forwarding options. /// public static IApplicationBuilder UseForwardedHeaders(this IApplicationBuilder builder, ForwardedHeadersOptions options) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } return builder.UseMiddleware(Options.Create(options)); } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.HttpOverrides { public class ForwardedHeadersMiddleware { private static readonly bool[] HostCharValidity = new bool[127]; private static readonly bool[] SchemeCharValidity = new bool[123]; private readonly ForwardedHeadersOptions _options; private readonly RequestDelegate _next; private readonly ILogger _logger; private bool _allowAllHosts; private IList _allowedHosts; static ForwardedHeadersMiddleware() { // RFC 3986 scheme = ALPHA * (ALPHA / DIGIT / "+" / "-" / ".") SchemeCharValidity['+'] = true; SchemeCharValidity['-'] = true; SchemeCharValidity['.'] = true; // Host Matches Http.Sys and Kestrel // Host Matches RFC 3986 except "*" / "+" / "," / ";" / "=" and "%" HEXDIG HEXDIG which are not allowed by Http.Sys HostCharValidity['!'] = true; HostCharValidity['$'] = true; HostCharValidity['&'] = true; HostCharValidity['\''] = true; HostCharValidity['('] = true; HostCharValidity[')'] = true; HostCharValidity['-'] = true; HostCharValidity['.'] = true; HostCharValidity['_'] = true; HostCharValidity['~'] = true; for (var ch = '0'; ch <= '9'; ch++) { SchemeCharValidity[ch] = true; HostCharValidity[ch] = true; } for (var ch = 'A'; ch <= 'Z'; ch++) { SchemeCharValidity[ch] = true; HostCharValidity[ch] = true; } for (var ch = 'a'; ch <= 'z'; ch++) { SchemeCharValidity[ch] = true; HostCharValidity[ch] = true; } } public ForwardedHeadersMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options) { if (next == null) { throw new ArgumentNullException(nameof(next)); } if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } // Make sure required options is not null or whitespace EnsureOptionNotNullorWhitespace(options.Value.ForwardedForHeaderName, nameof(options.Value.ForwardedForHeaderName)); EnsureOptionNotNullorWhitespace(options.Value.ForwardedHostHeaderName, nameof(options.Value.ForwardedHostHeaderName)); EnsureOptionNotNullorWhitespace(options.Value.ForwardedProtoHeaderName, nameof(options.Value.ForwardedProtoHeaderName)); EnsureOptionNotNullorWhitespace(options.Value.OriginalForHeaderName, nameof(options.Value.OriginalForHeaderName)); EnsureOptionNotNullorWhitespace(options.Value.OriginalHostHeaderName, nameof(options.Value.OriginalHostHeaderName)); EnsureOptionNotNullorWhitespace(options.Value.OriginalProtoHeaderName, nameof(options.Value.OriginalProtoHeaderName)); _options = options.Value; _logger = loggerFactory.CreateLogger(); _next = next; PreProcessHosts(); } private static void EnsureOptionNotNullorWhitespace(string value, string propertyName) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException($"options.{propertyName} is required", "options"); } } private void PreProcessHosts() { if (_options.AllowedHosts == null || _options.AllowedHosts.Count == 0) { _allowAllHosts = true; return; } var allowedHosts = new List(); foreach (var entry in _options.AllowedHosts) { // Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode. var host = new HostString(entry).ToUriComponent(); if (IsTopLevelWildcard(host)) { // Disable filtering _allowAllHosts = true; return; } if (!allowedHosts.Contains(host, StringSegmentComparer.OrdinalIgnoreCase)) { allowedHosts.Add(host); } } _allowedHosts = allowedHosts; } private bool IsTopLevelWildcard(string host) { return (string.Equals("*", host, StringComparison.Ordinal) // HttpSys wildcard || string.Equals("[::]", host, StringComparison.Ordinal) // Kestrel wildcard, IPv6 Any || string.Equals("0.0.0.0", host, StringComparison.Ordinal)); // IPv4 Any } public Task Invoke(HttpContext context) { ApplyForwarders(context); return _next(context); } public void ApplyForwarders(HttpContext context) { // Gather expected headers. string[] forwardedFor = null, forwardedProto = null, forwardedHost = null; bool checkFor = false, checkProto = false, checkHost = false; int entryCount = 0; if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedFor) == ForwardedHeaders.XForwardedFor) { checkFor = true; forwardedFor = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedForHeaderName); entryCount = Math.Max(forwardedFor.Length, entryCount); } if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedProto) == ForwardedHeaders.XForwardedProto) { checkProto = true; forwardedProto = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedProtoHeaderName); if (_options.RequireHeaderSymmetry && checkFor && forwardedFor.Length != forwardedProto.Length) { _logger.LogWarning(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto."); return; } entryCount = Math.Max(forwardedProto.Length, entryCount); } if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedHost) == ForwardedHeaders.XForwardedHost) { checkHost = true; forwardedHost = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedHostHeaderName); if (_options.RequireHeaderSymmetry && ((checkFor && forwardedFor.Length != forwardedHost.Length) || (checkProto && forwardedProto.Length != forwardedHost.Length))) { _logger.LogWarning(1, "Parameter count mismatch between X-Forwarded-Host and X-Forwarded-For or X-Forwarded-Proto."); return; } entryCount = Math.Max(forwardedHost.Length, entryCount); } // Apply ForwardLimit, if any if (_options.ForwardLimit.HasValue && entryCount > _options.ForwardLimit) { entryCount = _options.ForwardLimit.Value; } // Group the data together. var sets = new SetOfForwarders[entryCount]; for (int i = 0; i < sets.Length; i++) { // They get processed in reverse order, right to left. var set = new SetOfForwarders(); if (checkFor && i < forwardedFor.Length) { set.IpAndPortText = forwardedFor[forwardedFor.Length - i - 1]; } if (checkProto && i < forwardedProto.Length) { set.Scheme = forwardedProto[forwardedProto.Length - i - 1]; } if (checkHost && i < forwardedHost.Length) { set.Host = forwardedHost[forwardedHost.Length - i - 1]; } sets[i] = set; } // Gather initial values var connection = context.Connection; var request = context.Request; var currentValues = new SetOfForwarders() { RemoteIpAndPort = connection.RemoteIpAddress != null ? new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort) : null, // Host and Scheme initial values are never inspected, no need to set them here. }; var checkKnownIps = _options.KnownNetworks.Count > 0 || _options.KnownProxies.Count > 0; bool applyChanges = false; int entriesConsumed = 0; for ( ; entriesConsumed < sets.Length; entriesConsumed++) { var set = sets[entriesConsumed]; if (checkFor) { // For the first instance, allow remoteIp to be null for servers that don't support it natively. if (currentValues.RemoteIpAndPort != null && checkKnownIps && !CheckKnownAddress(currentValues.RemoteIpAndPort.Address)) { // Stop at the first unknown remote IP, but still apply changes processed so far. _logger.LogDebug(1, "Unknown proxy: {RemoteIpAndPort}", currentValues.RemoteIpAndPort); break; } IPEndPoint parsedEndPoint; if (IPEndPointParser.TryParse(set.IpAndPortText, out parsedEndPoint)) { applyChanges = true; set.RemoteIpAndPort = parsedEndPoint; currentValues.IpAndPortText = set.IpAndPortText; currentValues.RemoteIpAndPort = set.RemoteIpAndPort; } else if (!string.IsNullOrEmpty(set.IpAndPortText)) { // Stop at the first unparsable IP, but still apply changes processed so far. _logger.LogDebug(1, "Unparsable IP: {IpAndPortText}", set.IpAndPortText); break; } else if (_options.RequireHeaderSymmetry) { _logger.LogWarning(2, "Missing forwarded IPAddress."); return; } } if (checkProto) { if (!string.IsNullOrEmpty(set.Scheme) && TryValidateScheme(set.Scheme)) { applyChanges = true; currentValues.Scheme = set.Scheme; } else if (_options.RequireHeaderSymmetry) { _logger.LogWarning(3, $"Forwarded scheme is not present, this is required by {nameof(_options.RequireHeaderSymmetry)}"); return; } } if (checkHost) { if (!string.IsNullOrEmpty(set.Host) && TryValidateHost(set.Host) && (_allowAllHosts || HostString.MatchesAny(set.Host, _allowedHosts))) { applyChanges = true; currentValues.Host = set.Host; } else if (_options.RequireHeaderSymmetry) { _logger.LogWarning(4, $"Incorrect number of x-forwarded-host header values, see {nameof(_options.RequireHeaderSymmetry)}."); return; } } } if (applyChanges) { if (checkFor && currentValues.RemoteIpAndPort != null) { if (connection.RemoteIpAddress != null) { // Save the original request.Headers[_options.OriginalForHeaderName] = new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort).ToString(); } if (forwardedFor.Length > entriesConsumed) { // Truncate the consumed header values request.Headers[_options.ForwardedForHeaderName] = forwardedFor.Take(forwardedFor.Length - entriesConsumed).ToArray(); } else { // All values were consumed request.Headers.Remove(_options.ForwardedForHeaderName); } connection.RemoteIpAddress = currentValues.RemoteIpAndPort.Address; connection.RemotePort = currentValues.RemoteIpAndPort.Port; } if (checkProto && currentValues.Scheme != null) { // Save the original request.Headers[_options.OriginalProtoHeaderName] = request.Scheme; if (forwardedProto.Length > entriesConsumed) { // Truncate the consumed header values request.Headers[_options.ForwardedProtoHeaderName] = forwardedProto.Take(forwardedProto.Length - entriesConsumed).ToArray(); } else { // All values were consumed request.Headers.Remove(_options.ForwardedProtoHeaderName); } request.Scheme = currentValues.Scheme; } if (checkHost && currentValues.Host != null) { // Save the original request.Headers[_options.OriginalHostHeaderName] = request.Host.ToString(); if (forwardedHost.Length > entriesConsumed) { // Truncate the consumed header values request.Headers[_options.ForwardedHostHeaderName] = forwardedHost.Take(forwardedHost.Length - entriesConsumed).ToArray(); } else { // All values were consumed request.Headers.Remove(_options.ForwardedHostHeaderName); } request.Host = HostString.FromUriComponent(currentValues.Host); } } } private bool CheckKnownAddress(IPAddress address) { if (address.IsIPv4MappedToIPv6) { var ipv4Address = address.MapToIPv4(); if (CheckKnownAddress(ipv4Address)) { return true; } } if (_options.KnownProxies.Contains(address)) { return true; } foreach (var network in _options.KnownNetworks) { if (network.Contains(address)) { return true; } } return false; } private struct SetOfForwarders { public string IpAndPortText; public IPEndPoint RemoteIpAndPort; public string Host; public string Scheme; } // Empty was checked for by the caller [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryValidateScheme(string scheme) { for (var i = 0; i < scheme.Length; i++) { if (!IsValidSchemeChar(scheme[i])) { return false; } } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsValidSchemeChar(char ch) { return ch < SchemeCharValidity.Length && SchemeCharValidity[ch]; } // Empty was checked for by the caller [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryValidateHost(string host) { if (host[0] == '[') { return TryValidateIPv6Host(host); } if (host[0] == ':') { // Only a port return false; } var i = 0; for (; i < host.Length; i++) { if (!IsValidHostChar(host[i])) { break; } } return TryValidateHostPort(host, i); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsValidHostChar(char ch) { return ch < HostCharValidity.Length && HostCharValidity[ch]; } // The lead '[' was already checked [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryValidateIPv6Host(string hostText) { for (var i = 1; i < hostText.Length; i++) { var ch = hostText[i]; if (ch == ']') { // [::1] is the shortest valid IPv6 host if (i < 4) { return false; } return TryValidateHostPort(hostText, i + 1); } if (!IsHex(ch) && ch != ':' && ch != '.') { return false; } } // Must contain a ']' return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryValidateHostPort(string hostText, int offset) { if (offset == hostText.Length) { // No port return true; } if (hostText[offset] != ':' || hostText.Length == offset + 1) { // Must have at least one number after the colon if present. return false; } for (var i = offset + 1; i < hostText.Length; i++) { if (!IsNumeric(hostText[i])) { return false; } } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool IsNumeric(char ch) { return '0' <= ch && ch <= '9'; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool IsHex(char ch) { return IsNumeric(ch) || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F'); } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Net; using Microsoft.AspNetCore.HttpOverrides; namespace Microsoft.AspNetCore.Builder { public class ForwardedHeadersOptions { /// /// Use this header instead of /// /// public string ForwardedForHeaderName { get; set; } = ForwardedHeadersDefaults.XForwardedForHeaderName; /// /// Use this header instead of /// /// public string ForwardedHostHeaderName { get; set; } = ForwardedHeadersDefaults.XForwardedHostHeaderName; /// /// Use this header instead of /// /// public string ForwardedProtoHeaderName { get; set; } = ForwardedHeadersDefaults.XForwardedProtoHeaderName; /// /// Use this header instead of /// /// public string OriginalForHeaderName { get; set; } = ForwardedHeadersDefaults.XOriginalForHeaderName; /// /// Use this header instead of /// /// public string OriginalHostHeaderName { get; set; } = ForwardedHeadersDefaults.XOriginalHostHeaderName; /// /// Use this header instead of /// /// public string OriginalProtoHeaderName { get; set; } = ForwardedHeadersDefaults.XOriginalProtoHeaderName; /// /// Identifies which forwarders should be processed. /// public ForwardedHeaders ForwardedHeaders { get; set; } /// /// Limits the number of entries in the headers that will be processed. The default value is 1. /// Set to null to disable the limit, but this should only be done if /// KnownProxies or KnownNetworks are configured. /// public int? ForwardLimit { get; set; } = 1; /// /// Addresses of known proxies to accept forwarded headers from. /// public IList KnownProxies { get; } = new List() { IPAddress.IPv6Loopback }; /// /// Address ranges of known proxies to accept forwarded headers from. /// public IList KnownNetworks { get; } = new List() { new IPNetwork(IPAddress.Loopback, 8) }; /// /// The allowed values from x-forwarded-host. If the list is empty then all hosts are allowed. /// Failing to restrict this these values may allow an attacker to spoof links generated by your service. /// /// /// /// Port numbers must be excluded. /// A top level wildcard "*" allows all non-empty hosts. /// Subdomain wildcards are permitted. E.g. "*.example.com" matches subdomains like foo.example.com, /// but not the parent domain example.com. /// Unicode host names are allowed but will be converted to punycode for matching. /// IPv6 addresses must include their bounding brackets and be in their normalized form. /// /// public IList AllowedHosts { get; set; } = new List(); /// /// Require the number of header values to be in sync between the different headers being processed. /// The default is 'false'. /// public bool RequireHeaderSymmetry { get; set; } = false; } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/HttpMethodOverrideExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { public static class HttpMethodOverrideExtensions { /// /// Allows incoming POST request to override method type with type specified in header. /// /// The instance this method extends. public static IApplicationBuilder UseHttpMethodOverride(this IApplicationBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } return builder.UseMiddleware(); } /// /// Allows incoming POST request to override method type with type specified in form. /// /// The instance this method extends. /// The . public static IApplicationBuilder UseHttpMethodOverride(this IApplicationBuilder builder, HttpMethodOverrideOptions options) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } return builder.UseMiddleware(Options.Create(options)); } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/HttpMethodOverrideMiddleware.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.HttpOverrides { public class HttpMethodOverrideMiddleware { private const string xHttpMethodOverride = "X-Http-Method-Override"; private readonly RequestDelegate _next; private readonly HttpMethodOverrideOptions _options; public HttpMethodOverrideMiddleware(RequestDelegate next, IOptions options) { if (next == null) { throw new ArgumentNullException(nameof(next)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } _next = next; _options = options.Value; } public async Task Invoke(HttpContext context) { if (string.Equals(context.Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { if (_options.FormFieldName != null) { if (context.Request.HasFormContentType) { var form = await context.Request.ReadFormAsync(); var methodType = form[_options.FormFieldName]; if (!string.IsNullOrEmpty(methodType)) { context.Request.Method = methodType; } } } else { var xHttpMethodOverrideValue = context.Request.Headers[xHttpMethodOverride]; if (!string.IsNullOrEmpty(xHttpMethodOverrideValue)) { context.Request.Method = xHttpMethodOverrideValue; } } } await _next(context); } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/HttpMethodOverrideOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Builder { public class HttpMethodOverrideOptions { /// /// Denotes the form element that contains the name of the resulting method type. /// If not set the X-Http-Method-Override header will be used. /// public string FormFieldName { get; set; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/IPNetwork.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; namespace Microsoft.AspNetCore.HttpOverrides { public class IPNetwork { public IPNetwork(IPAddress prefix, int prefixLength) { Prefix = prefix; PrefixLength = prefixLength; PrefixBytes = Prefix.GetAddressBytes(); Mask = CreateMask(); } public IPAddress Prefix { get; } private byte[] PrefixBytes { get; } /// /// The CIDR notation of the subnet mask /// public int PrefixLength { get; } private byte[] Mask { get; } public bool Contains(IPAddress address) { if (Prefix.AddressFamily != address.AddressFamily) { return false; } var addressBytes = address.GetAddressBytes(); for (int i = 0; i < PrefixBytes.Length && Mask[i] != 0; i++) { if (PrefixBytes[i] != (addressBytes[i] & Mask[i])) { return false; } } return true; } private byte[] CreateMask() { var mask = new byte[PrefixBytes.Length]; int remainingBits = PrefixLength; int i = 0; while (remainingBits >= 8) { mask[i] = 0xFF; i++; remainingBits -= 8; } if (remainingBits > 0) { mask[i] = (byte)(0xFF << (8 - remainingBits)); } return mask; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/Internal/IPEndPointParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Globalization; using System.Net; namespace Microsoft.AspNetCore.HttpOverrides.Internal { public static class IPEndPointParser { public static bool TryParse(string addressWithPort, out IPEndPoint endpoint) { string addressPart = null; string portPart = null; IPAddress address; endpoint = null; if (string.IsNullOrEmpty(addressWithPort)) { return false; } var lastColonIndex = addressWithPort.LastIndexOf(':'); if (lastColonIndex > 0) { // IPv4 with port or IPv6 var closingIndex = addressWithPort.LastIndexOf(']'); if (closingIndex > 0) { // IPv6 with brackets addressPart = addressWithPort.Substring(1, closingIndex - 1); if (closingIndex < lastColonIndex) { // IPv6 with port [::1]:80 portPart = addressWithPort.Substring(lastColonIndex + 1); } } else { // IPv6 without port or IPv4 var firstColonIndex = addressWithPort.IndexOf(':'); if (firstColonIndex != lastColonIndex) { // IPv6 ::1 addressPart = addressWithPort; } else { // IPv4 with port 127.0.0.1:123 addressPart = addressWithPort.Substring(0, firstColonIndex); portPart = addressWithPort.Substring(firstColonIndex + 1); } } } else { // IPv4 without port addressPart = addressWithPort; } if (IPAddress.TryParse(addressPart, out address)) { if (portPart != null) { int port; if (int.TryParse(portPart, NumberStyles.None, CultureInfo.InvariantCulture, out port)) { endpoint = new IPEndPoint(address, port); return true; } return false; } endpoint = new IPEndPoint(address, 0); return true; } return false; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/Microsoft.AspNetCore.HttpOverrides.csproj ================================================  ASP.NET Core basic middleware for supporting HTTP method overrides. Includes: * X-Forwarded-* headers to forward headers from a proxy. * HTTP method override header. netstandard2.0 $(NoWarn);CS1591 true aspnetcore;proxy;headers;xforwarded ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/baseline.net45.json ================================================ { "AssemblyIdentity": "Microsoft.AspNetCore.HttpOverrides, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Builder.ForwardedHeadersExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseForwardedHeaders", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "UseForwardedHeaders", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" }, { "Name": "options", "Type": "Microsoft.AspNetCore.Builder.ForwardedHeadersOptions" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.ForwardedHeadersOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_ForwardedHeaders", "Parameters": [], "ReturnType": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_ForwardedHeaders", "Parameters": [ { "Name": "value", "Type": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_ForwardLimit", "Parameters": [], "ReturnType": "System.Nullable", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_ForwardLimit", "Parameters": [ { "Name": "value", "Type": "System.Nullable" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_KnownProxies", "Parameters": [], "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_KnownNetworks", "Parameters": [], "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_RequireHeaderSymmetry", "Parameters": [], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_RequireHeaderSymmetry", "Parameters": [ { "Name": "value", "Type": "System.Boolean" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HttpMethodOverrideExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseHttpMethodOverride", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "UseHttpMethodOverride", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" }, { "Name": "options", "Type": "Microsoft.AspNetCore.Builder.HttpMethodOverrideOptions" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HttpMethodOverrideOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_FormFieldName", "Parameters": [], "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_FormFieldName", "Parameters": [ { "Name": "value", "Type": "System.String" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders", "Visibility": "Public", "Kind": "Enumeration", "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Field", "Name": "None", "Parameters": [], "GenericParameter": [], "Literal": "0" }, { "Kind": "Field", "Name": "XForwardedFor", "Parameters": [], "GenericParameter": [], "Literal": "1" }, { "Kind": "Field", "Name": "XForwardedHost", "Parameters": [], "GenericParameter": [], "Literal": "2" }, { "Kind": "Field", "Name": "XForwardedProto", "Parameters": [], "GenericParameter": [], "Literal": "4" }, { "Kind": "Field", "Name": "All", "Parameters": [], "GenericParameter": [], "Literal": "7" } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "ApplyForwarders", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "loggerFactory", "Type": "Microsoft.Extensions.Logging.ILoggerFactory" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.HttpMethodOverrideMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.IPNetwork", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_Prefix", "Parameters": [], "ReturnType": "System.Net.IPAddress", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_PrefixLength", "Parameters": [], "ReturnType": "System.Int32", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "Contains", "Parameters": [ { "Name": "address", "Type": "System.Net.IPAddress" } ], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "prefix", "Type": "System.Net.IPAddress" }, { "Name": "prefixLength", "Type": "System.Int32" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.Internal.IPEndPointParser", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "TryParse", "Parameters": [ { "Name": "addressWithPort", "Type": "System.String" }, { "Name": "endpoint", "Type": "System.Net.IPEndPoint", "Direction": "Out" } ], "ReturnType": "System.Boolean", "Static": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] } ] } ================================================ FILE: src/Microsoft.AspNetCore.HttpOverrides/baseline.netcore.json ================================================ { "AssemblyIdentity": "Microsoft.AspNetCore.HttpOverrides, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Builder.ForwardedHeadersExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseForwardedHeaders", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "UseForwardedHeaders", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" }, { "Name": "options", "Type": "Microsoft.AspNetCore.Builder.ForwardedHeadersOptions" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.ForwardedHeadersOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_ForwardedForHeaderName", "Parameters": [], "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_ForwardedForHeaderName", "Parameters": [ { "Name": "value", "Type": "System.String" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_ForwardedHostHeaderName", "Parameters": [], "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_ForwardedHostHeaderName", "Parameters": [ { "Name": "value", "Type": "System.String" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_ForwardedProtoHeaderName", "Parameters": [], "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_ForwardedProtoHeaderName", "Parameters": [ { "Name": "value", "Type": "System.String" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_OriginalForHeaderName", "Parameters": [], "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_OriginalForHeaderName", "Parameters": [ { "Name": "value", "Type": "System.String" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_OriginalHostHeaderName", "Parameters": [], "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_OriginalHostHeaderName", "Parameters": [ { "Name": "value", "Type": "System.String" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_OriginalProtoHeaderName", "Parameters": [], "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_OriginalProtoHeaderName", "Parameters": [ { "Name": "value", "Type": "System.String" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_ForwardedHeaders", "Parameters": [], "ReturnType": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_ForwardedHeaders", "Parameters": [ { "Name": "value", "Type": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_ForwardLimit", "Parameters": [], "ReturnType": "System.Nullable", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_ForwardLimit", "Parameters": [ { "Name": "value", "Type": "System.Nullable" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_KnownProxies", "Parameters": [], "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_KnownNetworks", "Parameters": [], "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_AllowedHosts", "Parameters": [], "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_AllowedHosts", "Parameters": [ { "Name": "value", "Type": "System.Collections.Generic.IList" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_RequireHeaderSymmetry", "Parameters": [], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_RequireHeaderSymmetry", "Parameters": [ { "Name": "value", "Type": "System.Boolean" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HttpMethodOverrideExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseHttpMethodOverride", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "UseHttpMethodOverride", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" }, { "Name": "options", "Type": "Microsoft.AspNetCore.Builder.HttpMethodOverrideOptions" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HttpMethodOverrideOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_FormFieldName", "Parameters": [], "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_FormFieldName", "Parameters": [ { "Name": "value", "Type": "System.String" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders", "Visibility": "Public", "Kind": "Enumeration", "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Field", "Name": "None", "Parameters": [], "GenericParameter": [], "Literal": "0" }, { "Kind": "Field", "Name": "XForwardedFor", "Parameters": [], "GenericParameter": [], "Literal": "1" }, { "Kind": "Field", "Name": "XForwardedHost", "Parameters": [], "GenericParameter": [], "Literal": "2" }, { "Kind": "Field", "Name": "XForwardedProto", "Parameters": [], "GenericParameter": [], "Literal": "4" }, { "Kind": "Field", "Name": "All", "Parameters": [], "GenericParameter": [], "Literal": "7" } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersDefaults", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_XForwardedForHeaderName", "Parameters": [], "ReturnType": "System.String", "Static": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_XForwardedHostHeaderName", "Parameters": [], "ReturnType": "System.String", "Static": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_XForwardedProtoHeaderName", "Parameters": [], "ReturnType": "System.String", "Static": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_XOriginalForHeaderName", "Parameters": [], "ReturnType": "System.String", "Static": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_XOriginalHostHeaderName", "Parameters": [], "ReturnType": "System.String", "Static": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_XOriginalProtoHeaderName", "Parameters": [], "ReturnType": "System.String", "Static": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "ApplyForwarders", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "loggerFactory", "Type": "Microsoft.Extensions.Logging.ILoggerFactory" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.HttpMethodOverrideMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpOverrides.IPNetwork", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_Prefix", "Parameters": [], "ReturnType": "System.Net.IPAddress", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_PrefixLength", "Parameters": [], "ReturnType": "System.Int32", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "Contains", "Parameters": [ { "Name": "address", "Type": "System.Net.IPAddress" } ], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "prefix", "Type": "System.Net.IPAddress" }, { "Name": "prefixLength", "Type": "System.Int32" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] } ] } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/HstsBuilderExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the HSTS middleware. /// public static class HstsBuilderExtensions { /// /// Adds middleware for using HSTS, which adds the Strict-Transport-Security header. /// /// The instance this method extends. public static IApplicationBuilder UseHsts(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMiddleware(); } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpsPolicy.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.HttpsPolicy { /// /// Enables HTTP Strict Transport Security (HSTS) /// See https://tools.ietf.org/html/rfc6797. /// public class HstsMiddleware { private const string IncludeSubDomains = "; includeSubDomains"; private const string Preload = "; preload"; private readonly RequestDelegate _next; private readonly StringValues _strictTransportSecurityValue; private readonly IList _excludedHosts; private readonly ILogger _logger; /// /// Initialize the HSTS middleware. /// /// /// /// public HstsMiddleware(RequestDelegate next, IOptions options, ILoggerFactory loggerFactory) { if (options == null) { throw new ArgumentNullException(nameof(options)); } _next = next ?? throw new ArgumentNullException(nameof(next)); var hstsOptions = options.Value; var maxAge = Convert.ToInt64(Math.Floor(hstsOptions.MaxAge.TotalSeconds)) .ToString(CultureInfo.InvariantCulture); var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty; var preload = hstsOptions.Preload ? Preload : StringSegment.Empty; _strictTransportSecurityValue = new StringValues($"max-age={maxAge}{includeSubdomains}{preload}"); _excludedHosts = hstsOptions.ExcludedHosts; _logger = loggerFactory.CreateLogger(); } /// /// Initialize the HSTS middleware. /// /// /// public HstsMiddleware(RequestDelegate next, IOptions options) : this(next, options, NullLoggerFactory.Instance) { } /// /// Invoke the middleware. /// /// The . /// public Task Invoke(HttpContext context) { if (!context.Request.IsHttps) { _logger.SkippingInsecure(); return _next(context); } if (IsHostExcluded(context.Request.Host.Host)) { _logger.SkippingExcludedHost(context.Request.Host.Host); return _next(context); } context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue; _logger.AddingHstsHeader(); return _next(context); } private bool IsHostExcluded(string host) { if (_excludedHosts == null) { return false; } for (var i = 0; i < _excludedHosts.Count; i++) { if (string.Equals(host, _excludedHosts[i], StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/HstsOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; namespace Microsoft.AspNetCore.HttpsPolicy { /// /// Options for the Hsts Middleware /// public class HstsOptions { /// /// Sets the max-age parameter of the Strict-Transport-Security header. /// /// /// Max-age is required; defaults to 30 days. /// See: https://tools.ietf.org/html/rfc6797#section-6.1.1 /// public TimeSpan MaxAge { get; set; } = TimeSpan.FromDays(30); /// /// Enables includeSubDomain parameter of the Strict-Transport-Security header. /// /// /// See: https://tools.ietf.org/html/rfc6797#section-6.1.2 /// public bool IncludeSubDomains { get; set; } /// /// Sets the preload parameter of the Strict-Transport-Security header. /// /// /// Preload is not part of the RFC specification, but is supported by web browsers /// to preload HSTS sites on fresh install. See https://hstspreload.org/. /// public bool Preload { get; set; } /// /// A list of host names that will not add the HSTS header. /// public IList ExcludedHosts { get; } = new List { "localhost", "127.0.0.1", // ipv4 "[::1]" // ipv6 }; } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/HstsServicesExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the HSTS middleware. /// public static class HstsServicesExtensions { /// /// Adds HSTS services. /// /// The for adding services. /// A delegate to configure the . /// public static IServiceCollection AddHsts(this IServiceCollection services, Action configureOptions) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (configureOptions == null) { throw new ArgumentNullException(nameof(configureOptions)); } services.Configure(configureOptions); return services; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.HttpsPolicy; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the HttpsRedirection middleware. /// public static class HttpsPolicyBuilderExtensions { /// /// Adds middleware for redirecting HTTP Requests to HTTPS. /// /// The instance this method extends. /// The for HttpsRedirection. public static IApplicationBuilder UseHttpsRedirection(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } var serverAddressFeature = app.ServerFeatures.Get(); if (serverAddressFeature != null) { app.UseMiddleware(serverAddressFeature); } else { app.UseMiddleware(); } return app; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionMiddleware.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.HttpsPolicy.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.HttpsPolicy { public class HttpsRedirectionMiddleware { private readonly RequestDelegate _next; private bool _portEvaluated = false; private int? _httpsPort; private readonly int _statusCode; private readonly IServerAddressesFeature _serverAddressesFeature; private readonly IConfiguration _config; private readonly ILogger _logger; /// /// Initializes the HttpsRedirectionMiddleware /// /// /// /// /// public HttpsRedirectionMiddleware(RequestDelegate next, IOptions options, IConfiguration config, ILoggerFactory loggerFactory) { _next = next ?? throw new ArgumentNullException(nameof(next)); _config = config ?? throw new ArgumentNullException(nameof(config)); if (options == null) { throw new ArgumentNullException(nameof(options)); } var httpsRedirectionOptions = options.Value; _httpsPort = httpsRedirectionOptions.HttpsPort; _portEvaluated = _httpsPort.HasValue; _statusCode = httpsRedirectionOptions.RedirectStatusCode; _logger = loggerFactory.CreateLogger(); } /// /// Initializes the HttpsRedirectionMiddleware /// /// /// /// /// /// The public HttpsRedirectionMiddleware(RequestDelegate next, IOptions options, IConfiguration config, ILoggerFactory loggerFactory, IServerAddressesFeature serverAddressesFeature) : this(next, options, config, loggerFactory) { _serverAddressesFeature = serverAddressesFeature ?? throw new ArgumentNullException(nameof(serverAddressesFeature)); } /// /// Invokes the HttpsRedirectionMiddleware /// /// /// public Task Invoke(HttpContext context) { if (context.Request.IsHttps || !TryGetHttpsPort(out var port)) { return _next(context); } var host = context.Request.Host; if (port != 443) { host = new HostString(host.Host, port); } else { host = new HostString(host.Host); } var request = context.Request; var redirectUrl = UriHelper.BuildAbsolute( "https", host, request.PathBase, request.Path, request.QueryString); context.Response.StatusCode = _statusCode; context.Response.Headers[HeaderNames.Location] = redirectUrl; _logger.RedirectingToHttps(redirectUrl); return Task.CompletedTask; } private bool TryGetHttpsPort(out int port) { // The IServerAddressesFeature will not be ready until the middleware is Invoked, // Order for finding the HTTPS port: // 1. Set in the HttpsRedirectionOptions // 2. HTTPS_PORT environment variable // 3. IServerAddressesFeature // 4. Fail if not set port = -1; if (_portEvaluated) { port = _httpsPort ?? port; return _httpsPort.HasValue; } _portEvaluated = true; _httpsPort = _config.GetValue("HTTPS_PORT"); if (_httpsPort.HasValue) { port = _httpsPort.Value; _logger.PortLoadedFromConfig(port); return true; } if (_serverAddressesFeature == null) { _logger.FailedToDeterminePort(); return false; } int? httpsPort = null; foreach (var address in _serverAddressesFeature.Addresses) { var bindingAddress = BindingAddress.Parse(address); if (bindingAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { // If we find multiple different https ports specified, throw if (httpsPort.HasValue && httpsPort != bindingAddress.Port) { _logger.FailedMultiplePorts(); return false; } else { httpsPort = bindingAddress.Port; } } } if (httpsPort.HasValue) { _httpsPort = httpsPort; port = _httpsPort.Value; _logger.PortFromServer(port); return true; } _logger.FailedToDeterminePort(); return false; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.HttpsPolicy { /// /// Options for the HttpsRedirection middleware /// public class HttpsRedirectionOptions { /// /// The status code used for the redirect response. The default is 307. /// public int RedirectStatusCode { get; set; } = StatusCodes.Status307TemporaryRedirect; /// /// The HTTPS port to be added to the redirected URL. /// /// /// If the HttpsPort is not set, we will try to get the HttpsPort from the following: /// 1. HTTPS_PORT environment variable /// 2. IServerAddressesFeature /// If that fails then the middleware will log a warning and turn off. /// public int? HttpsPort { get; set; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionServicesExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the HttpsRedirection middleware. /// public static class HttpsRedirectionServicesExtensions { /// /// Adds HTTPS redirection services. /// /// The for adding services. /// A delegate to configure the . /// public static IServiceCollection AddHttpsRedirection(this IServiceCollection services, Action configureOptions) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (configureOptions == null) { throw new ArgumentNullException(nameof(configureOptions)); } services.Configure(configureOptions); return services; } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/Microsoft.AspNetCore.HttpsPolicy.csproj ================================================ ASP.NET Core basic middleware for supporting HTTPS Redirection and HTTP Strict-Transport-Security. netstandard2.0 $(NoWarn);CS1591 true aspnetcore;https;hsts ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/baseline.netcore.json ================================================ { "AssemblyIdentity": "Microsoft.AspNetCore.HttpsPolicy, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.HttpsPolicy.HstsMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpsPolicy.HstsOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_MaxAge", "Parameters": [], "ReturnType": "System.TimeSpan", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_MaxAge", "Parameters": [ { "Name": "value", "Type": "System.TimeSpan" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_IncludeSubDomains", "Parameters": [], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_IncludeSubDomains", "Parameters": [ { "Name": "value", "Type": "System.Boolean" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_Preload", "Parameters": [], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_Preload", "Parameters": [ { "Name": "value", "Type": "System.Boolean" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_ExcludedHosts", "Parameters": [], "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" }, { "Name": "config", "Type": "Microsoft.Extensions.Configuration.IConfiguration" }, { "Name": "loggerFactory", "Type": "Microsoft.Extensions.Logging.ILoggerFactory" } ], "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" }, { "Name": "config", "Type": "Microsoft.Extensions.Configuration.IConfiguration" }, { "Name": "loggerFactory", "Type": "Microsoft.Extensions.Logging.ILoggerFactory" }, { "Name": "serverAddressesFeature", "Type": "Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_RedirectStatusCode", "Parameters": [], "ReturnType": "System.Int32", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_RedirectStatusCode", "Parameters": [ { "Name": "value", "Type": "System.Int32" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_HttpsPort", "Parameters": [], "ReturnType": "System.Nullable", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_HttpsPort", "Parameters": [ { "Name": "value", "Type": "System.Nullable" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HstsBuilderExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseHsts", "Parameters": [ { "Name": "app", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HstsServicesExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "AddHsts", "Parameters": [ { "Name": "services", "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" }, { "Name": "configureOptions", "Type": "System.Action" } ], "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HttpsPolicyBuilderExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseHttpsRedirection", "Parameters": [ { "Name": "app", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.HttpsRedirectionServicesExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "AddHttpsRedirection", "Parameters": [ { "Name": "services", "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" }, { "Name": "configureOptions", "Type": "System.Action" } ], "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] } ] } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/internal/HstsLoggingExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.HttpsPolicy.Internal { internal static class HstsLoggingExtensions { private static readonly Action _notSecure; private static readonly Action _excludedHost; private static readonly Action _addingHstsHeader; static HstsLoggingExtensions() { _notSecure = LoggerMessage.Define(LogLevel.Debug, 1, "The request is insecure. Skipping HSTS header."); _excludedHost = LoggerMessage.Define(LogLevel.Debug, 2, "The host '{host}' is excluded. Skipping HSTS header."); _addingHstsHeader = LoggerMessage.Define(LogLevel.Trace, 3, "Adding HSTS header to response."); } public static void SkippingInsecure(this ILogger logger) { _notSecure(logger, null); } public static void SkippingExcludedHost(this ILogger logger, string host) { _excludedHost(logger, host, null); } public static void AddingHstsHeader(this ILogger logger) { _addingHstsHeader(logger, null); } } } ================================================ FILE: src/Microsoft.AspNetCore.HttpsPolicy/internal/HttpsLoggingExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.HttpsPolicy.Internal { internal static class HttpsLoggingExtensions { private static readonly Action _redirectingToHttps; private static readonly Action _portLoadedFromConfig; private static readonly Action _failedToDeterminePort; private static readonly Action _failedMultiplePorts; private static readonly Action _portFromServer; static HttpsLoggingExtensions() { _redirectingToHttps = LoggerMessage.Define(LogLevel.Debug, 1, "Redirecting to '{redirect}'."); _portLoadedFromConfig = LoggerMessage.Define(LogLevel.Debug, 2, "Https port '{port}' loaded from configuration."); _failedToDeterminePort = LoggerMessage.Define(LogLevel.Warning, 3, "Failed to determine the https port for redirect."); _failedMultiplePorts = LoggerMessage.Define(LogLevel.Warning, 4, "Cannot determine the https port from IServerAddressesFeature, multiple values were found. " + "Please set the desired port explicitly on HttpsRedirectionOptions.HttpsPort."); _portFromServer = LoggerMessage.Define(LogLevel.Debug, 5, "Https port '{httpsPort}' discovered from server endpoints."); } public static void RedirectingToHttps(this ILogger logger, string redirect) { _redirectingToHttps(logger, redirect, null); } public static void PortLoadedFromConfig(this ILogger logger, int port) { _portLoadedFromConfig(logger, port, null); } public static void FailedToDeterminePort(this ILogger logger) { _failedToDeterminePort(logger, null); } public static void FailedMultiplePorts(this ILogger logger) { _failedMultiplePorts(logger, null); } public static void PortFromServer(this ILogger logger, int port) { _portFromServer(logger, port, null); } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/BodyWrapperStream.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Features; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Stream wrapper that create specific compression stream only if necessary. /// internal class BodyWrapperStream : Stream, IHttpBufferingFeature, IHttpSendFileFeature { private readonly HttpContext _context; private readonly Stream _bodyOriginalStream; private readonly IResponseCompressionProvider _provider; private readonly IHttpBufferingFeature _innerBufferFeature; private readonly IHttpSendFileFeature _innerSendFileFeature; private ICompressionProvider _compressionProvider = null; private bool _compressionChecked = false; private Stream _compressionStream = null; private bool _providerCreated = false; private bool _autoFlush = false; internal BodyWrapperStream(HttpContext context, Stream bodyOriginalStream, IResponseCompressionProvider provider, IHttpBufferingFeature innerBufferFeature, IHttpSendFileFeature innerSendFileFeature) { _context = context; _bodyOriginalStream = bodyOriginalStream; _provider = provider; _innerBufferFeature = innerBufferFeature; _innerSendFileFeature = innerSendFileFeature; } protected override void Dispose(bool disposing) { if (_compressionStream != null) { _compressionStream.Dispose(); _compressionStream = null; } } public override bool CanRead => false; public override bool CanSeek => false; public override bool CanWrite => _bodyOriginalStream.CanWrite; public override long Length { get { throw new NotSupportedException(); } } public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public override void Flush() { if (!_compressionChecked) { OnWrite(); // Flush the original stream to send the headers. Flushing the compression stream won't // flush the original stream if no data has been written yet. _bodyOriginalStream.Flush(); return; } if (_compressionStream != null) { _compressionStream.Flush(); } else { _bodyOriginalStream.Flush(); } } public override Task FlushAsync(CancellationToken cancellationToken) { if (!_compressionChecked) { OnWrite(); // Flush the original stream to send the headers. Flushing the compression stream won't // flush the original stream if no data has been written yet. return _bodyOriginalStream.FlushAsync(cancellationToken); } if (_compressionStream != null) { return _compressionStream.FlushAsync(cancellationToken); } return _bodyOriginalStream.FlushAsync(cancellationToken); } public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { OnWrite(); if (_compressionStream != null) { _compressionStream.Write(buffer, offset, count); if (_autoFlush) { _compressionStream.Flush(); } } else { _bodyOriginalStream.Write(buffer, offset, count); } } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) { var tcs = new TaskCompletionSource(state); InternalWriteAsync(buffer, offset, count, callback, tcs); return tcs.Task; } private async void InternalWriteAsync(byte[] buffer, int offset, int count, AsyncCallback callback, TaskCompletionSource tcs) { try { await WriteAsync(buffer, offset, count); tcs.TrySetResult(null); } catch (Exception ex) { tcs.TrySetException(ex); } if (callback != null) { // Offload callbacks to avoid stack dives on sync completions. var ignored = Task.Run(() => { try { callback(tcs.Task); } catch (Exception) { // Suppress exceptions on background threads. } }); } } public override void EndWrite(IAsyncResult asyncResult) { if (asyncResult == null) { throw new ArgumentNullException(nameof(asyncResult)); } var task = (Task)asyncResult; task.GetAwaiter().GetResult(); } public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { OnWrite(); if (_compressionStream != null) { await _compressionStream.WriteAsync(buffer, offset, count, cancellationToken); if (_autoFlush) { await _compressionStream.FlushAsync(cancellationToken); } } else { await _bodyOriginalStream.WriteAsync(buffer, offset, count, cancellationToken); } } private void OnWrite() { if (!_compressionChecked) { _compressionChecked = true; if (_provider.ShouldCompressResponse(_context)) { // If the MIME type indicates that the response could be compressed, caches will need to vary by the Accept-Encoding header var varyValues = _context.Response.Headers.GetCommaSeparatedValues(HeaderNames.Vary); var varyByAcceptEncoding = false; for (var i = 0; i < varyValues.Length; i++) { if (string.Equals(varyValues[i], HeaderNames.AcceptEncoding, StringComparison.OrdinalIgnoreCase)) { varyByAcceptEncoding = true; break; } } if (!varyByAcceptEncoding) { _context.Response.Headers.Append(HeaderNames.Vary, HeaderNames.AcceptEncoding); } var compressionProvider = ResolveCompressionProvider(); if (compressionProvider != null) { _context.Response.Headers.Append(HeaderNames.ContentEncoding, compressionProvider.EncodingName); _context.Response.Headers.Remove(HeaderNames.ContentMD5); // Reset the MD5 because the content changed. _context.Response.Headers.Remove(HeaderNames.ContentLength); _compressionStream = compressionProvider.CreateStream(_bodyOriginalStream); } } } } private ICompressionProvider ResolveCompressionProvider() { if (!_providerCreated) { _providerCreated = true; _compressionProvider = _provider.GetCompressionProvider(_context); } return _compressionProvider; } public void DisableRequestBuffering() { // Unrelated _innerBufferFeature?.DisableRequestBuffering(); } // For this to be effective it needs to be called before the first write. public void DisableResponseBuffering() { if (ResolveCompressionProvider()?.SupportsFlush == false) { // Don't compress, some of the providers don't implement Flush (e.g. .NET 4.5.1 GZip/Deflate stream) // which would block real-time responses like SignalR. _compressionChecked = true; } else { _autoFlush = true; } _innerBufferFeature?.DisableResponseBuffering(); } // The IHttpSendFileFeature feature will only be registered if _innerSendFileFeature exists. public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation) { OnWrite(); if (_compressionStream != null) { return InnerSendFileAsync(path, offset, count, cancellation); } return _innerSendFileFeature.SendFileAsync(path, offset, count, cancellation); } private async Task InnerSendFileAsync(string path, long offset, long? count, CancellationToken cancellation) { cancellation.ThrowIfCancellationRequested(); var fileInfo = new FileInfo(path); if (offset < 0 || offset > fileInfo.Length) { throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty); } if (count.HasValue && (count.Value < 0 || count.Value > fileInfo.Length - offset)) { throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty); } int bufferSize = 1024 * 16; var fileStream = new FileStream( path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: bufferSize, options: FileOptions.Asynchronous | FileOptions.SequentialScan); using (fileStream) { fileStream.Seek(offset, SeekOrigin.Begin); await StreamCopyOperation.CopyToAsync(fileStream, _compressionStream, count, cancellation); if (_autoFlush) { await _compressionStream.FlushAsync(cancellation); } } } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/BrotliCompressionProvider.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.IO.Compression; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Brotli compression provider. /// public class BrotliCompressionProvider : ICompressionProvider { /// /// Creates a new instance of with options. /// /// public BrotliCompressionProvider(IOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } Options = options.Value; } private BrotliCompressionProviderOptions Options { get; } /// public string EncodingName => "br"; /// public bool SupportsFlush => true; /// public Stream CreateStream(Stream outputStream) { #if NETCOREAPP2_1 return new BrotliStream(outputStream, Options.Level, leaveOpen: true); #elif NET461 || NETSTANDARD2_0 // Brotli is only supported in .NET Core 2.1+ throw new PlatformNotSupportedException(); #else #error Target frameworks need to be updated. #endif } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/BrotliCompressionProviderOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO.Compression; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Options for the /// public class BrotliCompressionProviderOptions : IOptions { /// /// What level of compression to use for the stream. The default is . /// public CompressionLevel Level { get; set; } = CompressionLevel.Fastest; /// BrotliCompressionProviderOptions IOptions.Value => this; } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderCollection.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.ObjectModel; namespace Microsoft.AspNetCore.ResponseCompression { /// /// A Collection of ICompressionProvider's that also allows them to be instantiated from an . /// public class CompressionProviderCollection : Collection { /// /// Adds a type representing an . /// /// /// Provider instances will be created using an . /// public void Add() where TCompressionProvider : ICompressionProvider { Add(typeof(TCompressionProvider)); } /// /// Adds a type representing an . /// /// Type representing an . /// /// Provider instances will be created using an . /// public void Add(Type providerType) { if (providerType == null) { throw new ArgumentNullException(nameof(providerType)); } if (!typeof(ICompressionProvider).IsAssignableFrom(providerType)) { throw new ArgumentException($"The provider must implement {nameof(ICompressionProvider)}.", nameof(providerType)); } var factory = new CompressionProviderFactory(providerType); Add(factory); } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/CompressionProviderFactory.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.ResponseCompression { /// /// This is a placeholder for the CompressionProviderCollection that allows creating the given type via /// an . /// internal class CompressionProviderFactory : ICompressionProvider { public CompressionProviderFactory(Type providerType) { ProviderType = providerType; } private Type ProviderType { get; } public ICompressionProvider CreateInstance(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new ArgumentNullException(nameof(serviceProvider)); } return (ICompressionProvider)ActivatorUtilities.CreateInstance(serviceProvider, ProviderType, Type.EmptyTypes); } string ICompressionProvider.EncodingName { get { throw new NotSupportedException(); } } bool ICompressionProvider.SupportsFlush { get { throw new NotSupportedException(); } } Stream ICompressionProvider.CreateStream(Stream outputStream) { throw new NotSupportedException(); } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProvider.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.IO.Compression; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.ResponseCompression { /// /// GZIP compression provider. /// public class GzipCompressionProvider : ICompressionProvider { /// /// Creates a new instance of GzipCompressionProvider with options. /// /// public GzipCompressionProvider(IOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } Options = options.Value; } private GzipCompressionProviderOptions Options { get; } /// public string EncodingName => "gzip"; /// public bool SupportsFlush { get { #if NET461 return false; #elif NETSTANDARD2_0 || NETCOREAPP2_1 return true; #else #error target frameworks need to be updated #endif } } /// public Stream CreateStream(Stream outputStream) { return new GZipStream(outputStream, Options.Level, leaveOpen: true); } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/GzipCompressionProviderOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO.Compression; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Options for the GzipCompressionProvider /// public class GzipCompressionProviderOptions : IOptions { /// /// What level of compression to use for the stream. The default is Fastest. /// public CompressionLevel Level { get; set; } = CompressionLevel.Fastest; /// GzipCompressionProviderOptions IOptions.Value => this; } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/ICompressionProvider.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Provides a specific compression implementation to compress HTTP responses. /// public interface ICompressionProvider { /// /// The encoding name used in the 'Accept-Encoding' request header and 'Content-Encoding' response header. /// string EncodingName { get; } /// /// Indicates if the given provider supports Flush and FlushAsync. If not, compression may be disabled in some scenarios. /// bool SupportsFlush { get; } /// /// Create a new compression stream. /// /// The stream where the compressed data have to be written. /// The compression stream. Stream CreateStream(Stream outputStream); } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/IResponseCompressionProvider.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Used to examine requests and responses to see if compression should be enabled. /// public interface IResponseCompressionProvider { /// /// Examines the request and selects an acceptable compression provider, if any. /// /// /// A compression provider or null if compression should not be used. ICompressionProvider GetCompressionProvider(HttpContext context); /// /// Examines the response on first write to see if compression should be used. /// /// /// bool ShouldCompressResponse(HttpContext context); /// /// Examines the request to see if compression should be used for response. /// /// /// bool CheckRequestAcceptsCompression(HttpContext context); } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/Microsoft.AspNetCore.ResponseCompression.csproj ================================================  ASP.NET Core middleware for HTTP Response compression. net461;netstandard2.0;netcoreapp2.1 true aspnetcore ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/Properties/AssemblyInfo.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.ResponseCompression.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionBuilderExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the ResponseCompression middleware. /// public static class ResponseCompressionBuilderExtensions { /// /// Adds middleware for dynamically compressing HTTP Responses. /// /// The instance this method extends. public static IApplicationBuilder UseResponseCompression(this IApplicationBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } return builder.UseMiddleware(); } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionDefaults.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Defaults for the ResponseCompressionMiddleware /// public class ResponseCompressionDefaults { /// /// Default MIME types to compress responses for. /// // This list is not intended to be exhaustive, it's a baseline for the 90% case. public static readonly IEnumerable MimeTypes = new[] { // General "text/plain", // Static files "text/css", "application/javascript", // MVC "text/html", "application/xml", "text/xml", "application/json", "text/json", // WebAssembly "application/wasm", }; } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionMiddleware.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Enable HTTP response compression. /// public class ResponseCompressionMiddleware { private readonly RequestDelegate _next; private readonly IResponseCompressionProvider _provider; /// /// Initialize the Response Compression middleware. /// /// /// public ResponseCompressionMiddleware(RequestDelegate next, IResponseCompressionProvider provider) { if (next == null) { throw new ArgumentNullException(nameof(next)); } if (provider == null) { throw new ArgumentNullException(nameof(provider)); } _next = next; _provider = provider; } /// /// Invoke the middleware. /// /// /// public async Task Invoke(HttpContext context) { if (!_provider.CheckRequestAcceptsCompression(context)) { await _next(context); return; } var bodyStream = context.Response.Body; var originalBufferFeature = context.Features.Get(); var originalSendFileFeature = context.Features.Get(); var bodyWrapperStream = new BodyWrapperStream(context, bodyStream, _provider, originalBufferFeature, originalSendFileFeature); context.Response.Body = bodyWrapperStream; context.Features.Set(bodyWrapperStream); if (originalSendFileFeature != null) { context.Features.Set(bodyWrapperStream); } try { await _next(context); // This is not disposed via a using statement because we don't want to flush the compression buffer for unhandled exceptions, // that may cause secondary exceptions. bodyWrapperStream.Dispose(); } finally { context.Response.Body = bodyStream; context.Features.Set(originalBufferFeature); if (originalSendFileFeature != null) { context.Features.Set(originalSendFileFeature); } } } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; namespace Microsoft.AspNetCore.ResponseCompression { /// /// Options for the HTTP response compression middleware. /// public class ResponseCompressionOptions { /// /// Response Content-Type MIME types to compress. /// public IEnumerable MimeTypes { get; set; } /// /// Response Content-Type MIME types to not compress. /// public IEnumerable ExcludedMimeTypes { get; set; } /// /// Indicates if responses over HTTPS connections should be compressed. The default is 'false'. /// Enabling compression on HTTPS connections may expose security problems. /// public bool EnableForHttps { get; set; } = false; /// /// The types to use for responses. /// Providers are prioritized based on the order they are added. /// public CompressionProviderCollection Providers { get; } = new CompressionProviderCollection(); } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionProvider.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.ResponseCompression.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.ResponseCompression { /// public class ResponseCompressionProvider : IResponseCompressionProvider { private readonly ICompressionProvider[] _providers; private readonly HashSet _mimeTypes; private readonly HashSet _excludedMimeTypes; private readonly bool _enableForHttps; private readonly ILogger _logger; /// /// If no compression providers are specified then GZip is used by default. /// /// Services to use when instantiating compression providers. /// public ResponseCompressionProvider(IServiceProvider services, IOptions options) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } var responseCompressionOptions = options.Value; _providers = responseCompressionOptions.Providers.ToArray(); if (_providers.Length == 0) { // Use the factory so it can resolve IOptions from DI. _providers = new ICompressionProvider[] { #if NETCOREAPP2_1 new CompressionProviderFactory(typeof(BrotliCompressionProvider)), #elif NET461 || NETSTANDARD2_0 // Brotli is only supported in .NET Core 2.1+ #else #error Target frameworks need to be updated. #endif new CompressionProviderFactory(typeof(GzipCompressionProvider)), }; } for (var i = 0; i < _providers.Length; i++) { var factory = _providers[i] as CompressionProviderFactory; if (factory != null) { _providers[i] = factory.CreateInstance(services); } } var mimeTypes = responseCompressionOptions.MimeTypes; if (mimeTypes == null || !mimeTypes.Any()) { mimeTypes = ResponseCompressionDefaults.MimeTypes; } _mimeTypes = new HashSet(mimeTypes, StringComparer.OrdinalIgnoreCase); _excludedMimeTypes = new HashSet( responseCompressionOptions.ExcludedMimeTypes ?? Enumerable.Empty(), StringComparer.OrdinalIgnoreCase ); _enableForHttps = responseCompressionOptions.EnableForHttps; _logger = services.GetRequiredService>(); } /// public virtual ICompressionProvider GetCompressionProvider(HttpContext context) { // e.g. Accept-Encoding: gzip, deflate, sdch var accept = context.Request.Headers[HeaderNames.AcceptEncoding]; // Note this is already checked in CheckRequestAcceptsCompression which _should_ prevent any of these other methods from being called. if (StringValues.IsNullOrEmpty(accept)) { Debug.Assert(false, "Duplicate check failed."); _logger.NoAcceptEncoding(); return null; } if (!StringWithQualityHeaderValue.TryParseList(accept, out var encodings) || !encodings.Any()) { _logger.NoAcceptEncoding(); return null; } var candidates = new HashSet(); foreach (var encoding in encodings) { var encodingName = encoding.Value; var quality = encoding.Quality.GetValueOrDefault(1); if (quality < double.Epsilon) { continue; } for (int i = 0; i < _providers.Length; i++) { var provider = _providers[i]; if (StringSegment.Equals(provider.EncodingName, encodingName, StringComparison.OrdinalIgnoreCase)) { candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider)); } } // Uncommon but valid options if (StringSegment.Equals("*", encodingName, StringComparison.Ordinal)) { for (int i = 0; i < _providers.Length; i++) { var provider = _providers[i]; // Any provider is a candidate. candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider)); } break; } if (StringSegment.Equals("identity", encodingName, StringComparison.OrdinalIgnoreCase)) { // We add 'identity' to the list of "candidates" with a very low priority and no provider. // This will allow it to be ordered based on its quality (and priority) later in the method. candidates.Add(new ProviderCandidate(encodingName.Value, quality, priority: int.MaxValue, provider: null)); } } ICompressionProvider selectedProvider = null; if (candidates.Count <= 1) { selectedProvider = candidates.FirstOrDefault().Provider; } else { selectedProvider = candidates .OrderByDescending(x => x.Quality) .ThenBy(x => x.Priority) .First().Provider; } if (selectedProvider == null) { // "identity" would match as a candidate but not have a provider implementation _logger.NoCompressionProvider(); return null; } _logger.CompressingWith(selectedProvider.EncodingName); return selectedProvider; } /// public virtual bool ShouldCompressResponse(HttpContext context) { if (context.Response.Headers.ContainsKey(HeaderNames.ContentRange)) { _logger.NoCompressionDueToHeader(HeaderNames.ContentRange); return false; } if (context.Response.Headers.ContainsKey(HeaderNames.ContentEncoding)) { _logger.NoCompressionDueToHeader(HeaderNames.ContentEncoding); return false; } var mimeType = context.Response.ContentType; if (string.IsNullOrEmpty(mimeType)) { _logger.NoCompressionForContentType(mimeType); return false; } var separator = mimeType.IndexOf(';'); if (separator >= 0) { // Remove the content-type optional parameters mimeType = mimeType.Substring(0, separator); mimeType = mimeType.Trim(); } var shouldCompress = ShouldCompressExact(mimeType) //check exact match type/subtype ?? ShouldCompressPartial(mimeType) //check partial match type/* ?? _mimeTypes.Contains("*/*"); //check wildcard */* if (shouldCompress) { _logger.ShouldCompressResponse(); // Trace, there will be more logs return true; } _logger.NoCompressionForContentType(mimeType); return false; } /// public bool CheckRequestAcceptsCompression(HttpContext context) { if (context.Request.IsHttps && !_enableForHttps) { _logger.NoCompressionForHttps(); return false; } if (string.IsNullOrEmpty(context.Request.Headers[HeaderNames.AcceptEncoding])) { _logger.NoAcceptEncoding(); return false; } _logger.RequestAcceptsCompression(); // Trace, there will be more logs return true; } private bool? ShouldCompressExact(string mimeType) { //Check excluded MIME types first, then included if (_excludedMimeTypes.Contains(mimeType)) { return false; } if (_mimeTypes.Contains(mimeType)) { return true; } return null; } private bool? ShouldCompressPartial(string mimeType) { int? slashPos = mimeType?.IndexOf('/'); if (slashPos >= 0) { string partialMimeType = mimeType.Substring(0, slashPos.Value) + "/*"; return ShouldCompressExact(partialMimeType); } return null; } private readonly struct ProviderCandidate : IEquatable { public ProviderCandidate(string encodingName, double quality, int priority, ICompressionProvider provider) { EncodingName = encodingName; Quality = quality; Priority = priority; Provider = provider; } public string EncodingName { get; } public double Quality { get; } public int Priority { get; } public ICompressionProvider Provider { get; } public bool Equals(ProviderCandidate other) { return string.Equals(EncodingName, other.EncodingName, StringComparison.OrdinalIgnoreCase); } public override bool Equals(object obj) { return obj is ProviderCandidate candidate && Equals(candidate); } public override int GetHashCode() { return StringComparer.OrdinalIgnoreCase.GetHashCode(EncodingName); } } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionServicesExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the ResponseCompression middleware. /// public static class ResponseCompressionServicesExtensions { /// /// Add response compression services. /// /// The for adding services. /// public static IServiceCollection AddResponseCompression(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } services.TryAddSingleton(); return services; } /// /// Add response compression services and configure the related options. /// /// The for adding services. /// A delegate to configure the . /// public static IServiceCollection AddResponseCompression(this IServiceCollection services, Action configureOptions) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (configureOptions == null) { throw new ArgumentNullException(nameof(configureOptions)); } services.Configure(configureOptions); services.TryAddSingleton(); return services; } } } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/baseline.netcore.json ================================================ { "AssemblyIdentity": "Microsoft.AspNetCore.ResponseCompression, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Builder.ResponseCompressionBuilderExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseResponseCompression", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.ResponseCompressionServicesExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "AddResponseCompression", "Parameters": [ { "Name": "services", "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" } ], "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddResponseCompression", "Parameters": [ { "Name": "services", "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" }, { "Name": "configureOptions", "Type": "System.Action" } ], "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.CompressionProviderCollection", "Visibility": "Public", "Kind": "Class", "BaseType": "System.Collections.ObjectModel.Collection", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Add", "Parameters": [], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [ { "ParameterName": "TCompressionProvider", "ParameterPosition": 0, "BaseTypeOrInterfaces": [ "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider" ] } ] }, { "Kind": "Method", "Name": "Add", "Parameters": [ { "Name": "providerType", "Type": "System.Type" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.GzipCompressionProvider", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [ "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider" ], "Members": [ { "Kind": "Method", "Name": "get_EncodingName", "Parameters": [], "ReturnType": "System.String", "Sealed": true, "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_SupportsFlush", "Parameters": [], "ReturnType": "System.Boolean", "Sealed": true, "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "CreateStream", "Parameters": [ { "Name": "outputStream", "Type": "System.IO.Stream" } ], "ReturnType": "System.IO.Stream", "Sealed": true, "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.GzipCompressionProviderOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [ "Microsoft.Extensions.Options.IOptions" ], "Members": [ { "Kind": "Method", "Name": "get_Level", "Parameters": [], "ReturnType": "System.IO.Compression.CompressionLevel", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_Level", "Parameters": [ { "Name": "value", "Type": "System.IO.Compression.CompressionLevel" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Visibility": "Public", "Kind": "Interface", "Abstract": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_EncodingName", "Parameters": [], "ReturnType": "System.String", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_SupportsFlush", "Parameters": [], "ReturnType": "System.Boolean", "GenericParameter": [] }, { "Kind": "Method", "Name": "CreateStream", "Parameters": [ { "Name": "outputStream", "Type": "System.IO.Stream" } ], "ReturnType": "System.IO.Stream", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider", "Visibility": "Public", "Kind": "Interface", "Abstract": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "GetCompressionProvider", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "GenericParameter": [] }, { "Kind": "Method", "Name": "ShouldCompressResponse", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Boolean", "GenericParameter": [] }, { "Kind": "Method", "Name": "CheckRequestAcceptsCompression", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Boolean", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ResponseCompressionDefaults", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Field", "Name": "MimeTypes", "Parameters": [], "ReturnType": "System.Collections.Generic.IEnumerable", "Static": true, "ReadOnly": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "provider", "Type": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ResponseCompressionOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_MimeTypes", "Parameters": [], "ReturnType": "System.Collections.Generic.IEnumerable", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_MimeTypes", "Parameters": [ { "Name": "value", "Type": "System.Collections.Generic.IEnumerable" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_EnableForHttps", "Parameters": [], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_EnableForHttps", "Parameters": [ { "Name": "value", "Type": "System.Boolean" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_Providers", "Parameters": [], "ReturnType": "Microsoft.AspNetCore.ResponseCompression.CompressionProviderCollection", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ResponseCompressionProvider", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [ "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider" ], "Members": [ { "Kind": "Method", "Name": "GetCompressionProvider", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "ShouldCompressResponse", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Boolean", "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "CheckRequestAcceptsCompression", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Boolean", "Sealed": true, "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "services", "Type": "System.IServiceProvider" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] } ] } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/baseline.netframework.json ================================================ { "AssemblyIdentity": "Microsoft.AspNetCore.ResponseCompression, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Builder.ResponseCompressionBuilderExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseResponseCompression", "Parameters": [ { "Name": "builder", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Builder.ResponseCompressionServicesExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "AddResponseCompression", "Parameters": [ { "Name": "services", "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" } ], "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddResponseCompression", "Parameters": [ { "Name": "services", "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" }, { "Name": "configureOptions", "Type": "System.Action" } ], "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.CompressionProviderCollection", "Visibility": "Public", "Kind": "Class", "BaseType": "System.Collections.ObjectModel.Collection", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Add", "Parameters": [], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [ { "ParameterName": "TCompressionProvider", "ParameterPosition": 0, "BaseTypeOrInterfaces": [ "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider" ] } ] }, { "Kind": "Method", "Name": "Add", "Parameters": [ { "Name": "providerType", "Type": "System.Type" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.GzipCompressionProvider", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [ "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider" ], "Members": [ { "Kind": "Method", "Name": "get_EncodingName", "Parameters": [], "ReturnType": "System.String", "Sealed": true, "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_SupportsFlush", "Parameters": [], "ReturnType": "System.Boolean", "Sealed": true, "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "CreateStream", "Parameters": [ { "Name": "outputStream", "Type": "System.IO.Stream" } ], "ReturnType": "System.IO.Stream", "Sealed": true, "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.GzipCompressionProviderOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [ "Microsoft.Extensions.Options.IOptions" ], "Members": [ { "Kind": "Method", "Name": "get_Level", "Parameters": [], "ReturnType": "System.IO.Compression.CompressionLevel", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_Level", "Parameters": [ { "Name": "value", "Type": "System.IO.Compression.CompressionLevel" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Visibility": "Public", "Kind": "Interface", "Abstract": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_EncodingName", "Parameters": [], "ReturnType": "System.String", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_SupportsFlush", "Parameters": [], "ReturnType": "System.Boolean", "GenericParameter": [] }, { "Kind": "Method", "Name": "CreateStream", "Parameters": [ { "Name": "outputStream", "Type": "System.IO.Stream" } ], "ReturnType": "System.IO.Stream", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider", "Visibility": "Public", "Kind": "Interface", "Abstract": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "GetCompressionProvider", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "GenericParameter": [] }, { "Kind": "Method", "Name": "ShouldCompressResponse", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Boolean", "GenericParameter": [] }, { "Kind": "Method", "Name": "CheckRequestAcceptsCompression", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Boolean", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ResponseCompressionDefaults", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Field", "Name": "MimeTypes", "Parameters": [], "ReturnType": "System.Collections.Generic.IEnumerable", "Static": true, "ReadOnly": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "provider", "Type": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ResponseCompressionOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_MimeTypes", "Parameters": [], "ReturnType": "System.Collections.Generic.IEnumerable", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_MimeTypes", "Parameters": [ { "Name": "value", "Type": "System.Collections.Generic.IEnumerable" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_EnableForHttps", "Parameters": [], "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_EnableForHttps", "Parameters": [ { "Name": "value", "Type": "System.Boolean" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_Providers", "Parameters": [], "ReturnType": "Microsoft.AspNetCore.ResponseCompression.CompressionProviderCollection", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.ResponseCompression.ResponseCompressionProvider", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [ "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider" ], "Members": [ { "Kind": "Method", "Name": "GetCompressionProvider", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "Microsoft.AspNetCore.ResponseCompression.ICompressionProvider", "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "ShouldCompressResponse", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Boolean", "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "CheckRequestAcceptsCompression", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Boolean", "Sealed": true, "Virtual": true, "ImplementedInterface": "Microsoft.AspNetCore.ResponseCompression.IResponseCompressionProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "services", "Type": "System.IServiceProvider" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] } ] } ================================================ FILE: src/Microsoft.AspNetCore.ResponseCompression/internal/ResponseCompressionLoggingExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.ResponseCompression.Internal { internal static class ResponseCompressionLoggingExtensions { private static readonly Action _noAcceptEncoding; private static readonly Action _noCompressionForHttps; private static readonly Action _requestAcceptsCompression; private static readonly Action _noCompressionDueToHeader; private static readonly Action _noCompressionForContentType; private static readonly Action _shouldCompressResponse; private static readonly Action _noCompressionProvider; private static readonly Action _compressWith; static ResponseCompressionLoggingExtensions() { _noAcceptEncoding = LoggerMessage.Define(LogLevel.Debug, 1, "No response compression available, the Accept-Encoding header is missing or invalid."); _noCompressionForHttps = LoggerMessage.Define(LogLevel.Debug, 2, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps."); _requestAcceptsCompression = LoggerMessage.Define(LogLevel.Trace, 3, "This request accepts compression."); _noCompressionDueToHeader = LoggerMessage.Define(LogLevel.Debug, 4, "Response compression disabled due to the {header} header."); _noCompressionForContentType = LoggerMessage.Define(LogLevel.Debug, 5, "Response compression is not enabled for the Content-Type '{header}'."); _shouldCompressResponse = LoggerMessage.Define(LogLevel.Trace, 6, "Response compression is available for this Content-Type."); _noCompressionProvider = LoggerMessage.Define(LogLevel.Debug, 7, "No matching response compression provider found."); _compressWith = LoggerMessage.Define(LogLevel.Debug, 8, "The response will be compressed with '{provider}'."); } public static void NoAcceptEncoding(this ILogger logger) { _noAcceptEncoding(logger, null); } public static void NoCompressionForHttps(this ILogger logger) { _noCompressionForHttps(logger, null); } public static void RequestAcceptsCompression(this ILogger logger) { _requestAcceptsCompression(logger, null); } public static void NoCompressionDueToHeader(this ILogger logger, string header) { _noCompressionDueToHeader(logger, header, null); } public static void NoCompressionForContentType(this ILogger logger, string header) { _noCompressionForContentType(logger, header, null); } public static void ShouldCompressResponse(this ILogger logger) { _shouldCompressResponse(logger, null); } public static void NoCompressionProvider(this ILogger logger) { _noCompressionProvider(logger, null); } public static void CompressingWith(this ILogger logger, string provider) { _compressWith(logger, provider, null); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/ApacheModRewriteOptionsExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Rewrite { /// /// Extensions for adding Apache mod_rewrite rules to /// public static class ApacheModRewriteOptionsExtensions { /// /// Add rules from an Apache mod_rewrite file /// /// The /// The /// The path to the file containing mod_rewrite rules. public static RewriteOptions AddApacheModRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (fileProvider == null) { throw new ArgumentNullException(nameof(fileProvider)); } var fileInfo = fileProvider.GetFileInfo(filePath); using (var stream = fileInfo.CreateReadStream()) { return options.AddApacheModRewrite(new StreamReader(stream)); } } /// /// Add rules from an Apache mod_rewrite file /// /// The /// A stream of mod_rewrite rules. public static RewriteOptions AddApacheModRewrite(this RewriteOptions options, TextReader reader) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var rules = new FileParser().Parse(reader); foreach (var rule in rules) { options.Rules.Add(rule); } return options; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Extensions/RewriteMiddlewareLoggingExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Rewrite.Logging { internal static class RewriteMiddlewareLoggingExtensions { private static readonly Action _requestContinueResults; private static readonly Action _requestResponseComplete; private static readonly Action _requestStopRules; private static readonly Action _urlRewriteDidNotMatchRule; private static readonly Action _urlRewriteMatchedRule; private static readonly Action _modRewriteDidNotMatchRule; private static readonly Action _modRewriteMatchedRule; private static readonly Action _redirectedToHttps; private static readonly Action _redirectedToWww; private static readonly Action _redirectSummary; private static readonly Action _rewriteSummary; private static readonly Action _abortedRequest; private static readonly Action _customResponse; static RewriteMiddlewareLoggingExtensions() { _requestContinueResults = LoggerMessage.Define( LogLevel.Debug, 1, "Request is continuing in applying rules. Current url is {currentUrl}"); _requestResponseComplete = LoggerMessage.Define( LogLevel.Debug, 2, "Request is done processing. Location header '{Location}' with status code '{StatusCode}'."); _requestStopRules = LoggerMessage.Define( LogLevel.Debug, 3, "Request is done applying rules. Url was rewritten to {rewrittenUrl}"); _urlRewriteDidNotMatchRule = LoggerMessage.Define( LogLevel.Debug, 4, "Request did not match current rule '{Name}'."); _urlRewriteMatchedRule = LoggerMessage.Define( LogLevel.Debug, 5, "Request matched current UrlRewriteRule '{Name}'."); _modRewriteDidNotMatchRule = LoggerMessage.Define( LogLevel.Debug, 6, "Request matched current ModRewriteRule."); _modRewriteMatchedRule = LoggerMessage.Define( LogLevel.Debug, 7, "Request matched current ModRewriteRule."); _redirectedToHttps = LoggerMessage.Define( LogLevel.Information, 8, "Request redirected to HTTPS"); _redirectSummary = LoggerMessage.Define( LogLevel.Information, 9, "Request was redirected to {redirectedUrl}"); _rewriteSummary = LoggerMessage.Define( LogLevel.Information, 10, "Request was rewritten to {rewrittenUrl}"); _abortedRequest = LoggerMessage.Define( LogLevel.Debug, 11, "Request to {requestedUrl} was aborted"); _customResponse = LoggerMessage.Define( LogLevel.Debug, 12, "Request to {requestedUrl} was ended"); _redirectedToWww = LoggerMessage.Define( LogLevel.Information, 13, "Request redirected to www"); } public static void RewriteMiddlewareRequestContinueResults(this ILogger logger, string currentUrl) { _requestContinueResults(logger, currentUrl, null); } public static void RewriteMiddlewareRequestResponseComplete(this ILogger logger, string location, int statusCode) { _requestResponseComplete(logger, location, statusCode, null); } public static void RewriteMiddlewareRequestStopRules(this ILogger logger, string rewrittenUrl) { _requestStopRules(logger, rewrittenUrl, null); } public static void UrlRewriteDidNotMatchRule(this ILogger logger, string name) { _urlRewriteDidNotMatchRule(logger, name, null); } public static void UrlRewriteMatchedRule(this ILogger logger, string name) { _urlRewriteMatchedRule(logger, name, null); } public static void ModRewriteDidNotMatchRule(this ILogger logger) { _modRewriteDidNotMatchRule(logger, null); } public static void ModRewriteMatchedRule(this ILogger logger) { _modRewriteMatchedRule(logger, null); } public static void RedirectedToHttps(this ILogger logger) { _redirectedToHttps(logger, null); } public static void RedirectedToWww(this ILogger logger) { _redirectedToWww(logger, null); } public static void RedirectedSummary(this ILogger logger, string redirectedUrl) { _redirectSummary(logger, redirectedUrl, null); } public static void RewriteSummary(this ILogger logger, string rewrittenUrl) { _rewriteSummary(logger, rewrittenUrl, null); } public static void AbortedRequest(this ILogger logger, string requestedUrl) { _abortedRequest(logger, requestedUrl, null); } public static void CustomResponse(this ILogger logger, string requestedUrl) { _customResponse(logger, requestedUrl, null); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Rewrite { /// /// Extensions for adding IIS Url Rewrite rules to /// public static class IISUrlRewriteOptionsExtensions { /// /// Add rules from a IIS config file containing Url Rewrite rules /// /// The /// The /// The path to the file containing UrlRewrite rules. public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (fileProvider == null) { throw new ArgumentNullException(nameof(fileProvider)); } var file = fileProvider.GetFileInfo(filePath); using (var stream = file.CreateReadStream()) { return AddIISUrlRewrite(options, new StreamReader(stream)); } } /// /// Add rules from a IIS config file containing Url Rewrite rules /// /// The /// The text reader stream. public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (reader == null) { throw new ArgumentException(nameof(reader)); } var rules = new UrlRewriteFileParser().Parse(reader); foreach (var rule in rules) { options.Rules.Add(rule); } return options; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/IRule.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite { /// /// Represents a rule. /// public interface IRule { /// /// Applies the rule. /// Implementations of ApplyRule should set the value for /// (defaults to RuleResult.ContinueRules) /// /// void ApplyRule(RewriteContext context); } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/ApacheModRewriteRule.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using Microsoft.AspNetCore.Rewrite.Logging; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public class ApacheModRewriteRule : IRule { public UrlMatch InitialMatch { get; } public IList Conditions { get; } public IList Actions { get; } public ApacheModRewriteRule(UrlMatch initialMatch, IList conditions, IList urlActions) { Conditions = conditions; InitialMatch = initialMatch; Actions = urlActions; } public virtual void ApplyRule(RewriteContext context) { // 1. Figure out which section of the string to match for the initial rule. var initMatchRes = InitialMatch.Evaluate(context.HttpContext.Request.Path, context); if (!initMatchRes.Success) { context.Logger?.ModRewriteDidNotMatchRule(); return; } BackReferenceCollection condBackReferences = null; if (Conditions != null) { var condResult = ConditionEvaluator.Evaluate(Conditions, context, initMatchRes.BackReferences); if (!condResult.Success) { context.Logger?.ModRewriteDidNotMatchRule(); return; } } // At this point, we know our rule passed, first apply pre conditions, // which can modify things like the cookie or env, and then apply the action context.Logger?.ModRewriteMatchedRule(); foreach (var action in Actions) { action.ApplyAction(context, initMatchRes?.BackReferences, condBackReferences); } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/Condition.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public class Condition { public Pattern Input { get; set; } public UrlMatch Match { get; set; } public bool OrNext { get; set; } public MatchResults Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { var pattern = Input.Evaluate(context, ruleBackReferences, conditionBackReferences); return Match.Evaluate(pattern, context); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/ConditionEvaluator.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public static class ConditionEvaluator { public static MatchResults Evaluate(IEnumerable conditions, RewriteContext context, BackReferenceCollection backReferences) { return Evaluate(conditions, context, backReferences, trackAllCaptures: false); } public static MatchResults Evaluate(IEnumerable conditions, RewriteContext context, BackReferenceCollection backReferences, bool trackAllCaptures) { BackReferenceCollection prevBackReferences = null; MatchResults condResult = null; var orSucceeded = false; foreach (var condition in conditions) { if (orSucceeded && condition.OrNext) { continue; } else if (orSucceeded) { orSucceeded = false; continue; } condResult = condition.Evaluate(context, backReferences, prevBackReferences); var currentBackReferences = condResult.BackReferences; if (condition.OrNext) { orSucceeded = condResult.Success; } else if (!condResult.Success) { return condResult; } if (condResult.Success && trackAllCaptures && prevBackReferences != null) { prevBackReferences.Add(currentBackReferences); currentBackReferences = prevBackReferences; } prevBackReferences = currentBackReferences; } return new MatchResults { BackReferences = prevBackReferences, Success = condResult.Success }; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/ConditionPatternParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Globalization; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { /// /// Parses the "CondPattern" portion of the RewriteCond. /// RewriteCond TestString CondPattern /// public class ConditionPatternParser { private const char Not = '!'; private const char Dash = '-'; private const char Less = '<'; private const char Greater = '>'; private const char EqualSign = '='; /// /// Given a CondPattern, create a ParsedConditionExpression, containing the type of operation /// and value. /// ParsedConditionExpression is an intermediary object, which will be made into a ConditionExpression /// once the flags are parsed. /// /// The CondPattern portion of a mod_rewrite RewriteCond. /// A new parsed condition. public ParsedModRewriteInput ParseActionCondition(string condition) { if (condition == null) { condition = string.Empty; } var context = new ParserContext(condition); var results = new ParsedModRewriteInput(); if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } // If we hit a !, invert the condition if (context.Current == Not) { results.Invert = true; if (!context.Next()) { // Dangling ! throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } } // Control Block for strings. Set the operation and type fields based on the sign // Switch on current character switch (context.Current) { case Greater: if (!context.Next()) { // Dangling ">" throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } if (context.Current == EqualSign) { if (!context.Next()) { // Dangling ">=" throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } results.OperationType = OperationType.GreaterEqual; results.ConditionType = ConditionType.StringComp; } else { results.OperationType = OperationType.Greater; results.ConditionType = ConditionType.StringComp; } break; case Less: if (!context.Next()) { // Dangling "<" throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } if (context.Current == EqualSign) { if (!context.Next()) { // Dangling "<=" throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } results.OperationType = OperationType.LessEqual; results.ConditionType = ConditionType.StringComp; } else { results.OperationType = OperationType.Less; results.ConditionType = ConditionType.StringComp; } break; case EqualSign: if (!context.Next()) { // Dangling "=" throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } results.OperationType = OperationType.Equal; results.ConditionType = ConditionType.StringComp; break; case Dash: results = ParseProperty(context, results.Invert); if (results.ConditionType == ConditionType.PropertyTest) { return results; } context.Next(); break; default: results.ConditionType = ConditionType.Regex; break; } // Capture the rest of the string guarantee validity. results.Operand = condition.Substring(context.GetIndex()); if (IsValidActionCondition(results)) { return results; } else { throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index)); } } /// /// Given that the current index is a property (ex checks for directory or regular files), create a /// new ParsedConditionExpression with the appropriate property operation. /// /// /// /// private static ParsedModRewriteInput ParseProperty(ParserContext context, bool invert) { if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } switch (context.Current) { case 'd': return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Directory, operand: null); case 'f': return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.RegularFile, operand: null); case 'F': return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.ExistingFile, operand: null); case 'h': case 'L': return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null); case 's': return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Size, operand: null); case 'U': return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.ExistingUrl, operand: null); case 'x': return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Executable, operand: null); case 'e': if (!context.Next() || context.Current != 'q') { // Illegal statement. throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Equal, operand: null); case 'g': if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } switch (context.Current) { case 't': return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Greater, operand: null); case 'e': return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.GreaterEqual, operand: null); default: throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } case 'l': // name conflict with -l and -lt/-le, so the assumption is if there is no // charcters after -l, we assume it a symbolic link if (!context.Next()) { return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null); } switch (context.Current) { case 't': return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.Less, operand: null); case 'e': return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.LessEqual, operand: null); default: throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } case 'n': if (!context.Next() || context.Current != 'e') { throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.NotEqual, operand: null); default: throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index)); } } private static bool IsValidActionCondition(ParsedModRewriteInput results) { if (results.ConditionType == ConditionType.IntComp) { // If the type is an integer, verify operand is actually an int int res; if (!int.TryParse(results.Operand, NumberStyles.None, CultureInfo.InvariantCulture, out res)) { return false; } } return true; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/ConditionType.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public enum ConditionType { Regex, PropertyTest, StringComp, IntComp } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/CookieActionFactory.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Globalization; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public class CookieActionFactory { /// /// Creates a for details. /// /// The flag /// The action public ChangeCookieAction Create(string flagValue) { if (string.IsNullOrEmpty(flagValue)) { throw new ArgumentException(nameof(flagValue)); } var i = 0; var separator = ':'; if (flagValue[0] == ';') { separator = ';'; i++; } ChangeCookieAction action = null; var currentField = Fields.Name; var start = i; for (; i < flagValue.Length; i++) { if (flagValue[i] == separator) { var length = i - start; SetActionOption(flagValue.Substring(start, length).Trim(), currentField, ref action); currentField++; start = i + 1; } } if (i != start) { SetActionOption(flagValue.Substring(start).Trim(new[] { ' ', separator }), currentField, ref action); } if (currentField < Fields.Domain) { throw new FormatException(Resources.FormatError_InvalidChangeCookieFlag(flagValue)); } return action; } private static void SetActionOption(string value, Fields tokenType, ref ChangeCookieAction action) { switch (tokenType) { case Fields.Name: action = new ChangeCookieAction(value); break; case Fields.Value: action.Value = value; break; case Fields.Domain: // despite what spec says, an empty domain field is allowed in mod_rewrite // by specifying NAME:VALUE:; action.Domain = string.IsNullOrEmpty(value) || value == ";" ? null : value; break; case Fields.Lifetime: if (string.IsNullOrEmpty(value)) { break; } uint minutes; if (!uint.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out minutes)) { throw new FormatException(Resources.FormatError_CouldNotParseInteger(value)); } action.Lifetime = TimeSpan.FromMinutes(minutes); break; case Fields.Path: action.Path = value; break; case Fields.Secure: action.Secure = "secure".Equals(value, StringComparison.OrdinalIgnoreCase) || "true".Equals(value, StringComparison.OrdinalIgnoreCase) || value == "1"; break; case Fields.HttpOnly: action.HttpOnly = "httponly".Equals(value, StringComparison.OrdinalIgnoreCase) || "true".Equals(value, StringComparison.OrdinalIgnoreCase) || value == "1"; break; } } // order matters // see https://httpd.apache.org/docs/current/rewrite/flags.html#flag_co private enum Fields { Name, Value, Domain, Lifetime, Path, Secure, HttpOnly } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/FileParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public class FileParser { public IList Parse(TextReader input) { string line; var rules = new List(); var builder = new RuleBuilder(); var lineNum = 0; // parsers var testStringParser = new TestStringParser(); var conditionParser = new ConditionPatternParser(); var regexParser = new RuleRegexParser(); var flagsParser = new FlagParser(); var tokenizer = new Tokenizer(); while ((line = input.ReadLine()) != null) { lineNum++; if (string.IsNullOrEmpty(line)) { continue; } if (line.StartsWith("#")) { continue; } var tokens = tokenizer.Tokenize(line); if (tokens.Count > 4) { // This means the line didn't have an appropriate format, throw format exception throw new FormatException(Resources.FormatError_ModRewriteParseError("Too many tokens on line", lineNum)); } switch (tokens[0]) { case "RewriteBase": // the notion of the path base spans across all rules, not just mod_rewrite // So not implemented for now throw new NotImplementedException("RewriteBase is not implemented"); case "RewriteCond": try { var pattern = testStringParser.Parse(tokens[1]); var condActionParsed = conditionParser.ParseActionCondition(tokens[2]); var flags = new Flags(); if (tokens.Count == 4) { flags = flagsParser.Parse(tokens[3]); } builder.AddConditionFromParts(pattern, condActionParsed, flags); } catch (FormatException formatException) { throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException); } break; case "RewriteRule": try { var regex = regexParser.ParseRuleRegex(tokens[1]); var pattern = testStringParser.Parse(tokens[2]); Flags flags; if (tokens.Count == 4) { flags = flagsParser.Parse(tokens[3]); } else { flags = new Flags(); } builder.AddMatch(regex, flags); builder.AddAction(pattern, flags); rules.Add(builder.Build()); builder = new RuleBuilder(); } catch (FormatException formatException) { throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException); } break; case "RewriteMap": // Lack of use throw new NotImplementedException("RewriteMap are not implemented"); case "RewriteEngine": // Explicitly do nothing here, no notion of turning on regex engine. break; default: throw new FormatException(Resources.FormatError_ModRewriteParseError("Unrecognized keyword: " + tokens[0], lineNum)); } } return rules; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/FlagParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public class FlagParser { private readonly IDictionary _ruleFlagLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "b", FlagType.EscapeBackreference}, { "c", FlagType.Chain }, { "chain", FlagType.Chain}, { "co", FlagType.Cookie }, { "cookie", FlagType.Cookie }, { "dpi", FlagType.DiscardPath }, { "discardpath", FlagType.DiscardPath }, { "e", FlagType.Env}, { "env", FlagType.Env}, { "end", FlagType.End }, { "f", FlagType.Forbidden }, { "forbidden", FlagType.Forbidden }, { "g", FlagType.Gone }, { "gone", FlagType.Gone }, { "h", FlagType.Handler }, { "handler", FlagType.Handler }, { "l", FlagType.Last }, { "last", FlagType.Last }, { "n", FlagType.Next }, { "next", FlagType.Next }, { "nc", FlagType.NoCase }, { "nocase", FlagType.NoCase }, { "ne", FlagType.NoEscape }, { "noescape", FlagType.NoEscape }, { "ns", FlagType.NoSubReq }, { "nosubreq", FlagType.NoSubReq }, { "or", FlagType.Or }, { "ornext", FlagType.Or }, { "p", FlagType.Proxy }, { "proxy", FlagType.Proxy }, { "pt", FlagType.PassThrough }, { "passthrough", FlagType.PassThrough }, { "qsa", FlagType.QSAppend }, { "qsappend", FlagType.QSAppend }, { "qsd", FlagType.QSDiscard }, { "qsdiscard", FlagType.QSDiscard }, { "qsl", FlagType.QSLast }, { "qslast", FlagType.QSLast }, { "r", FlagType.Redirect }, { "redirect", FlagType.Redirect }, { "s", FlagType.Skip }, { "skip", FlagType.Skip }, { "t", FlagType.Type }, { "type", FlagType.Type }, }; public Flags Parse(string flagString) { if (string.IsNullOrEmpty(flagString)) { throw new ArgumentException(nameof(flagString)); } // Check that flags are contained within [] // Guaranteed to have a length of at least 1 here, so this will never throw for indexing. if (!(flagString[0] == '[' && flagString[flagString.Length - 1] == ']')) { throw new FormatException("Flags should start and end with square brackets: [flags]"); } // Lexing esque step to split all flags. // Invalid syntax to have any spaces. var tokens = flagString.Substring(1, flagString.Length - 2).Split(','); var flags = new Flags(); foreach (var token in tokens) { var hasPayload = token.Split('='); FlagType flag; if (!_ruleFlagLookup.TryGetValue(hasPayload[0], out flag)) { throw new FormatException($"Unrecognized flag: '{hasPayload[0]}'"); } if (hasPayload.Length == 2) { flags.SetFlag(flag, hasPayload[1]); } else { flags.SetFlag(flag, string.Empty); } } return flags; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/FlagType.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public enum FlagType { EscapeBackreference, Chain, Cookie, DiscardPath, Env, End, Forbidden, Gone, Handler, Last, Next, NoCase, NoEscape, NoSubReq, NoVary, Or, Proxy, PassThrough, QSAppend, QSDiscard, QSLast, Redirect, Skip, Type } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/Flags.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { // For more information of flags, and what flags we currently support: // https://github.com/aspnet/BasicMiddleware/issues/66 // http://httpd.apache.org/docs/current/expr.html#vars public class Flags { public IDictionary FlagDictionary { get; } public Flags(IDictionary flags) { FlagDictionary = flags; } public Flags() { FlagDictionary = new Dictionary(); } public void SetFlag(FlagType flag, string value) { if (value == null) { value = string.Empty; } FlagDictionary[flag] = value; } public bool GetValue(FlagType flag, out string value) { string res; if (!FlagDictionary.TryGetValue(flag, out res)) { value = null; return false; } value = res; return true; } public string this[FlagType flag] { get { string res; if (!FlagDictionary.TryGetValue(flag, out res)) { return null; } return res; } set { FlagDictionary[flag] = value ?? string.Empty; } } public bool HasFlag(FlagType flag) { string res; return FlagDictionary.TryGetValue(flag, out res); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/OperationType.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public enum OperationType { None, Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual, Directory, RegularFile, ExistingFile, SymbolicLink, Size, ExistingUrl, Executable } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/ParsedModRewriteCondition.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public class ParsedModRewriteInput { public bool Invert { get; set; } public ConditionType ConditionType { get; set; } public OperationType OperationType { get; set; } public string Operand { get; set; } public ParsedModRewriteInput() { } public ParsedModRewriteInput(bool invert, ConditionType conditionType, OperationType operationType, string operand) { Invert = invert; ConditionType = conditionType; OperationType = operationType; Operand = operand; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/RuleBuilder.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public class RuleBuilder { private IList _conditions; internal IList _actions = new List(); private UrlMatch _match; private CookieActionFactory _cookieActionFactory = new CookieActionFactory(); private readonly TimeSpan _regexTimeout = TimeSpan.FromSeconds(1); public ApacheModRewriteRule Build() { if (_actions.Count == 0 || _match == null) { throw new InvalidOperationException("Cannot create ModRewriteRule without action and match"); } return new ApacheModRewriteRule(_match, _conditions, _actions); } public void AddRule(string rule) { var tokens = new Tokenizer().Tokenize(rule); var regex = new RuleRegexParser().ParseRuleRegex(tokens[1]); var pattern = new TestStringParser().Parse(tokens[2]); Flags flags; if (tokens.Count == 4) { flags = new FlagParser().Parse(tokens[3]); } else { flags = new Flags(); } AddMatch(regex, flags); AddAction(pattern, flags); } public void AddConditionFromParts( Pattern pattern, ParsedModRewriteInput input, Flags flags) { if (_conditions == null) { _conditions = new List(); } var condition = new Condition(); condition.OrNext = flags.HasFlag(FlagType.Or); condition.Input = pattern; switch (input.ConditionType) { case ConditionType.Regex: if (flags.HasFlag(FlagType.NoCase)) { condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, _regexTimeout), input.Invert); } else { condition.Match = new RegexMatch(new Regex(input.Operand, RegexOptions.CultureInvariant | RegexOptions.Compiled, _regexTimeout), input.Invert); } break; case ConditionType.IntComp: switch (input.OperationType) { case OperationType.Equal: condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Equal); break; case OperationType.Greater: condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Greater); break; case OperationType.GreaterEqual: condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.GreaterEqual); break; case OperationType.Less: condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.Less); break; case OperationType.LessEqual: condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.LessEqual); break; case OperationType.NotEqual: condition.Match = new IntegerMatch(input.Operand, IntegerOperationType.NotEqual); break; default: throw new ArgumentException("Invalid operation for integer comparison."); } break; case ConditionType.StringComp: switch (input.OperationType) { case OperationType.Equal: condition.Match = new StringMatch(input.Operand, StringOperationType.Equal, input.Invert); break; case OperationType.Greater: condition.Match = new StringMatch(input.Operand, StringOperationType.Greater, input.Invert); break; case OperationType.GreaterEqual: condition.Match = new StringMatch(input.Operand, StringOperationType.GreaterEqual, input.Invert); break; case OperationType.Less: condition.Match = new StringMatch(input.Operand, StringOperationType.Less, input.Invert); break; case OperationType.LessEqual: condition.Match = new StringMatch(input.Operand, StringOperationType.LessEqual, input.Invert); break; default: throw new ArgumentException("Invalid operation for string comparison."); } break; default: switch (input.OperationType) { case OperationType.Directory: condition.Match = new IsDirectoryMatch(input.Invert); break; case OperationType.RegularFile: condition.Match = new IsFileMatch(input.Invert); break; case OperationType.ExistingFile: condition.Match = new IsFileMatch(input.Invert); break; case OperationType.SymbolicLink: // TODO see if FileAttributes.ReparsePoint works for this? throw new NotImplementedException("Symbolic links are not supported because " + "of cross platform implementation"); case OperationType.Size: condition.Match = new FileSizeMatch(input.Invert); break; case OperationType.ExistingUrl: throw new NotSupportedException("Existing Url lookups not supported because it requires a subrequest"); case OperationType.Executable: throw new NotSupportedException("Executable Property is not supported because Windows " + "requires a pinvoke to get this property"); default: throw new ArgumentException("Invalid operation for property comparison"); } break; } _conditions.Add(condition); } public void AddMatch( ParsedModRewriteInput input, Flags flags) { if (flags.HasFlag(FlagType.NoCase)) { _match = new RegexMatch(new Regex(input.Operand, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, _regexTimeout), input.Invert); } else { _match = new RegexMatch(new Regex(input.Operand, RegexOptions.CultureInvariant | RegexOptions.Compiled, _regexTimeout), input.Invert); } } public void AddAction( Pattern pattern, Flags flags) { string flag; if (flags.GetValue(FlagType.Cookie, out flag)) { var action = _cookieActionFactory.Create(flag); _actions.Add(action); } if (flags.GetValue(FlagType.Env, out flag)) { throw new NotSupportedException(Resources.Error_ChangeEnvironmentNotSupported); } if (flags.HasFlag(FlagType.Forbidden)) { _actions.Add(new ForbiddenAction()); } else if (flags.HasFlag(FlagType.Gone)) { _actions.Add(new GoneAction()); } else { var escapeBackReference = flags.HasFlag(FlagType.EscapeBackreference); var queryStringAppend = flags.HasFlag(FlagType.QSAppend); var queryStringDelete = flags.HasFlag(FlagType.QSDiscard); // is redirect? string statusCode; if (flags.GetValue(FlagType.Redirect, out statusCode)) { int responseStatusCode; if (string.IsNullOrEmpty(statusCode)) { responseStatusCode = StatusCodes.Status302Found; } else if (!int.TryParse(statusCode, NumberStyles.None, CultureInfo.InvariantCulture, out responseStatusCode)) { throw new FormatException(Resources.FormatError_InputParserInvalidInteger(statusCode, -1)); } _actions.Add(new RedirectAction(responseStatusCode, pattern, queryStringAppend, queryStringDelete, escapeBackReference)); } else { var last = flags.HasFlag(FlagType.End) || flags.HasFlag(FlagType.Last); var termination = last ? RuleResult.SkipRemainingRules : RuleResult.ContinueRules; _actions.Add(new RewriteAction(termination, pattern, queryStringAppend, queryStringDelete, escapeBackReference)); } } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/RuleRegexParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public class RuleRegexParser { public ParsedModRewriteInput ParseRuleRegex(string regex) { if (string.IsNullOrEmpty(regex)) { throw new FormatException("Regex expression is null"); } if (regex[0] == '!') { return new ParsedModRewriteInput { Invert = true, Operand = regex.Substring(1) }; } else { return new ParsedModRewriteInput { Invert = false, Operand = regex }; } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/SegmentType.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { public enum SegmentType { Literal, ServerParameter, ConditionParameter, RuleParameter } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/ServerVariables.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { /// /// mod_rewrite lookups for specific string constants. /// public static class ServerVariables { /// /// Translates mod_rewrite server variables strings to an enum of different server variables. /// /// The server variable string. /// The Parser context /// The appropriate enum if the server variable exists, else ServerVariable.None public static PatternSegment FindServerVariable(string serverVariable, ParserContext context) { switch (serverVariable) { case "HTTP_ACCEPT": return new HeaderSegment(HeaderNames.Accept); case "HTTP_COOKIE": return new HeaderSegment(HeaderNames.Cookie); case "HTTP_HOST": return new HeaderSegment(HeaderNames.Host); case "HTTP_REFERER": return new HeaderSegment(HeaderNames.Referer); case "HTTP_USER_AGENT": return new HeaderSegment(HeaderNames.UserAgent); case "HTTP_CONNECTION": return new HeaderSegment(HeaderNames.Connection); case "HTTP_FORWARDED": return new HeaderSegment("Forwarded"); case "AUTH_TYPE": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "CONN_REMOTE_ADDR": return new RemoteAddressSegment(); case "CONTEXT_PREFIX": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "CONTEXT_DOCUMENT_ROOT": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "IPV6": return new IsIPV6Segment(); case "PATH_INFO": throw new NotImplementedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "QUERY_STRING": return new QueryStringSegment(); case "REMOTE_ADDR": return new RemoteAddressSegment(); case "REMOTE_HOST": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "REMOTE_IDENT": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "REMOTE_PORT": return new RemotePortSegment(); case "REMOTE_USER": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "REQUEST_METHOD": return new RequestMethodSegment(); case "SCRIPT_FILENAME": return new RequestFileNameSegment(); case "DOCUMENT_ROOT": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "SCRIPT_GROUP": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "SCRIPT_USER": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "SERVER_ADDR": return new LocalAddressSegment(); case "SERVER_ADMIN": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "SERVER_NAME": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "SERVER_PORT": return new LocalPortSegment(); case "SERVER_PROTOCOL": return new ServerProtocolSegment(); case "SERVER_SOFTWARE": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "TIME_YEAR": return new DateTimeSegment(serverVariable); case "TIME_MON": return new DateTimeSegment(serverVariable); case "TIME_DAY": return new DateTimeSegment(serverVariable); case "TIME_HOUR": return new DateTimeSegment(serverVariable); case "TIME_MIN": return new DateTimeSegment(serverVariable); case "TIME_SEC": return new DateTimeSegment(serverVariable); case "TIME_WDAY": return new DateTimeSegment(serverVariable); case "TIME": return new DateTimeSegment(serverVariable); case "API_VERSION": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "HTTPS": return new IsHttpsModSegment(); case "HTTP2": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "IS_SUBREQ": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "REQUEST_FILENAME": return new RequestFileNameSegment(); case "REQUEST_SCHEME": return new SchemeSegment(); case "REQUEST_URI": return new UrlSegment(); case "THE_REQUEST": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); default: throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(serverVariable, context.Index)); } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/TestStringParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { /// /// Parses the TestString segment of the mod_rewrite condition. /// public class TestStringParser { private const char Percent = '%'; private const char Dollar = '$'; private const char Colon = ':'; private const char OpenBrace = '{'; private const char CloseBrace = '}'; /// /// Creates a pattern, which is a template to create a new test string to /// compare to the condition pattern. Can contain server variables, back references, etc. /// /// The test string portion of the RewriteCond /// Examples: /// %{REMOTE_ADDR} /// /var/www/%{REQUEST_URI} /// %1 /// $1 /// A new , containing a list of /// http://httpd.apache.org/docs/current/mod/mod_rewrite.html public Pattern Parse(string testString) { if (testString == null) { testString = string.Empty; } var context = new ParserContext(testString); var results = new List(); while (context.Next()) { switch (context.Current) { case Percent: // This is a server parameter, parse for a condition variable if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(testString, context.Index)); } ParseConditionParameter(context, results); break; case Dollar: // This is a parameter from the rule, verify that it is a number from 0 to 9 directly after it // and create a new Pattern Segment. if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index)); } context.Mark(); if (context.Current >= '0' && context.Current <= '9') { context.Next(); var ruleVariable = context.Capture(); context.Back(); var parsedIndex = int.Parse(ruleVariable); results.Add(new RuleMatchSegment(parsedIndex)); } else { throw new FormatException(Resources.FormatError_InputParserInvalidInteger(testString, context.Index)); } break; default: ParseLiteral(context, results); break; } } return new Pattern(results); } /// /// Obtains the condition parameter, which could either be a condition variable or a /// server variable. Assumes the current character is immediately after the '%'. /// context, on return will be on the last character of variable captured, such that after /// Next() is called, it will be on the character immediately after the condition parameter. /// /// The ParserContext /// The List of results which the new condition parameter will be added. /// true private static void ParseConditionParameter(ParserContext context, IList results) { // Parse { } if (context.Current == OpenBrace) { // Start of a server variable if (!context.Next()) { // Dangling { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } context.Mark(); while (context.Current != CloseBrace) { if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } else if (context.Current == Colon) { // Have a segmented look up Ex: HTTP:xxxx // Most of these we can't handle throw new NotImplementedException("Segmented Lookups no implemented"); } } // Need to verify server variable captured exists var rawServerVariable = context.Capture(); results.Add(ServerVariables.FindServerVariable(rawServerVariable, context)); } else if (context.Current >= '0' && context.Current <= '9') { // means we have a segmented lookup // store information in the testString result to know what to look up. context.Mark(); context.Next(); var rawConditionParameter = context.Capture(); // Once we leave this method, the while loop will call next again. Because // capture is exclusive, we need to go one past the end index, capture, and then go back. context.Back(); var parsedIndex = int.Parse(rawConditionParameter); results.Add(new ConditionMatchSegment(parsedIndex)); } else { // illegal escape of a character throw new FormatException(Resources.FormatError_InputParserInvalidInteger(context.Template, context.Index)); } } /// /// Parse a string literal in the test string. Continues capturing until the start of a new variable type. /// /// /// /// private static void ParseLiteral(ParserContext context, IList results) { context.Mark(); string literal; while (true) { if (context.Current == Percent || context.Current == Dollar) { literal = context.Capture(); context.Back(); break; } if (!context.Next()) { literal = context.Capture(); break; } } // add results results.Add(new LiteralSegment(literal)); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ApacheModRewrite/Tokenizer.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite { /// /// Tokenizes a mod_rewrite rule, delimited by spaces. /// public class Tokenizer { private const char Space = ' '; private const char Escape = '\\'; private const char Tab = '\t'; private const char Quote = '"'; /// /// Splits a string on whitespace, ignoring spaces, creating into a list of strings. /// /// The rule to tokenize. /// A list of tokens. public IList Tokenize(string rule) { // TODO make list of strings a reference to the original rule? (run into problems with escaped spaces). // TODO handle "s and probably replace \ character with no slash. if (string.IsNullOrEmpty(rule)) { return null; } var context = new ParserContext(rule); context.Next(); var tokens = new List(); context.Mark(); while (true) { switch (context.Current) { case Escape: // Need to progress such that the next character is not evaluated. if (!context.Next()) { // Means that a character was not escaped appropriately Ex: "foo\" throw new FormatException($"Invalid escaper character in string: {rule}"); } break; case Quote: // Ignore all characters until the next quote is hit if (!context.Next()) { throw new FormatException($"Mismatched number of quotes: {rule}"); } while (context.Current != Quote) { if (!context.Next()) { throw new FormatException($"Mismatched number of quotes: {rule}"); } } break; case Space: case Tab: // time to capture! var token = context.Capture(); if (!string.IsNullOrEmpty(token)) { tokens.Add(token); do { if (!context.Next()) { // At end of string, we can return at this point. RemoveQuotesAndEscapeCharacters(tokens); return tokens; } } while (context.Current == Space || context.Current == Tab); context.Mark(); context.Back(); } break; } if (!context.Next()) { // End of string. Capture. break; } } var done = context.Capture(); if (!string.IsNullOrEmpty(done)) { tokens.Add(done); } RemoveQuotesAndEscapeCharacters(tokens); return tokens; } // Need to remove leading and trailing slashes if they exist. // This is on start-up, so more forgivening towards substrings/ new strings // If this is a perf/memory problem, discuss later. private static void RemoveQuotesAndEscapeCharacters(IList tokens) { for (var i = 0; i < tokens.Count; i++) { var token = tokens[i]; var trimmed = token.Trim('\"'); tokens[i] = Regex.Unescape(trimmed); } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/BackReferenceCollection.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace Microsoft.AspNetCore.Rewrite.Internal { public class BackReferenceCollection { private List _backReferences = new List(); public BackReferenceCollection(GroupCollection references) { if (references != null) { for (var i = 0; i < references.Count; i++) { _backReferences.Add(references[i].Value); } } } public BackReferenceCollection(string reference) { _backReferences.Add(reference); } public string this[int index] { get { if (index < _backReferences.Count) { return _backReferences[index]; } else { throw new IndexOutOfRangeException($"Cannot access back reference at index {index}. Only {_backReferences.Count} back references were captured."); } } } public void Add(BackReferenceCollection references) { if (references != null) { _backReferences.AddRange(references._backReferences); } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/DelegateRule.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; namespace Microsoft.AspNetCore.Rewrite.Internal { public class DelegateRule : IRule { private readonly Action _onApplyRule; public DelegateRule(Action onApplyRule) { _onApplyRule = onApplyRule; } public void ApplyRule(RewriteContext context) => _onApplyRule(context); } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/ActionType.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public enum ActionType { None, Rewrite, Redirect, CustomResponse, AbortRequest } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/Condition.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class Condition { public Pattern Input { get; set; } public UrlMatch Match { get; set; } public MatchResults Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { var pattern = Input.Evaluate(context, ruleBackReferences, conditionBackReferences); return Match.Evaluate(pattern, context); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/ConditionCollection.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections; using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class ConditionCollection : IEnumerable { private readonly List _conditions = new List(); public LogicalGrouping Grouping { get; } public bool TrackAllCaptures { get; } public ConditionCollection() :this(LogicalGrouping.MatchAll, trackAllCaptures: false) { } public ConditionCollection(LogicalGrouping grouping, bool trackAllCaptures) { Grouping = grouping; TrackAllCaptures = trackAllCaptures; } public int Count => _conditions.Count; public Condition this[int index] { get { if (index < _conditions.Count) { return _conditions[index]; } throw new IndexOutOfRangeException($"Cannot access condition at index {index}. Only {_conditions.Count} conditions were captured."); } } public void Add(Condition condition) { if (condition != null) { _conditions.Add(condition); } } public void AddConditions(IEnumerable conditions) { if (conditions != null) { _conditions.AddRange(conditions); } } IEnumerator IEnumerable.GetEnumerator() { return _conditions.GetEnumerator(); } public IEnumerator GetEnumerator() { return _conditions.GetEnumerator(); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/ConditionEvaluator.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public static class ConditionEvaluator { public static MatchResults Evaluate(ConditionCollection conditions, RewriteContext context, BackReferenceCollection backReferences) { BackReferenceCollection prevBackReferences = null; MatchResults condResult = null; var orSucceeded = false; foreach (var condition in conditions) { if (orSucceeded && conditions.Grouping == LogicalGrouping.MatchAny) { continue; } if (orSucceeded) { orSucceeded = false; continue; } condResult = condition.Evaluate(context, backReferences, prevBackReferences); var currentBackReferences = condResult.BackReferences; if (conditions.Grouping == LogicalGrouping.MatchAny) { orSucceeded = condResult.Success; } else if (!condResult.Success) { return condResult; } if (condResult.Success && conditions.TrackAllCaptures && prevBackReferences!= null) { prevBackReferences.Add(currentBackReferences); currentBackReferences = prevBackReferences; } prevBackReferences = currentBackReferences; } return new MatchResults { BackReferences = prevBackReferences, Success = condResult.Success }; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class IISRewriteMap { private readonly Dictionary _map = new Dictionary(); public IISRewriteMap(string name) { if (string.IsNullOrEmpty(name)) { throw new ArgumentException(nameof(name)); } Name = name; } public string Name { get; } public string this[string key] { get { string value; return _map.TryGetValue(key, out value) ? value : null; } set { if (string.IsNullOrEmpty(key)) { throw new ArgumentException(nameof(key)); } if (string.IsNullOrEmpty(value)) { throw new ArgumentException(nameof(value)); } _map[key] = value; } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMapCollection.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class IISRewriteMapCollection : IEnumerable { private readonly Dictionary _rewriteMaps = new Dictionary(); public void Add(IISRewriteMap rewriteMap) { if (rewriteMap != null) { _rewriteMaps[rewriteMap.Name] = rewriteMap; } } public int Count => _rewriteMaps.Count; public IISRewriteMap this[string key] { get { IISRewriteMap value; return _rewriteMaps.TryGetValue(key, out value) ? value : null; } } IEnumerator IEnumerable.GetEnumerator() { return _rewriteMaps.Values.GetEnumerator(); } public IEnumerator GetEnumerator() { return _rewriteMaps.Values.GetEnumerator(); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISUrlRewriteRule.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Logging; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class IISUrlRewriteRule : IRule { public string Name { get; } public UrlMatch InitialMatch { get; } public ConditionCollection Conditions { get; } public UrlAction Action { get; } public bool Global { get; } public IISUrlRewriteRule(string name, UrlMatch initialMatch, ConditionCollection conditions, UrlAction action) : this(name, initialMatch, conditions, action, false) { } public IISUrlRewriteRule(string name, UrlMatch initialMatch, ConditionCollection conditions, UrlAction action, bool global) { Name = name; InitialMatch = initialMatch; Conditions = conditions; Action = action; Global = global; } public virtual void ApplyRule(RewriteContext context) { // Due to the path string always having a leading slash, // remove it from the path before regex comparison var path = context.HttpContext.Request.Path; MatchResults initMatchResults; if (path == PathString.Empty) { initMatchResults = InitialMatch.Evaluate(path.ToString(), context); } else { initMatchResults = InitialMatch.Evaluate(path.ToString().Substring(1), context); } if (!initMatchResults.Success) { context.Logger?.UrlRewriteDidNotMatchRule(Name); return; } MatchResults condResult = null; if (Conditions != null) { condResult = ConditionEvaluator.Evaluate(Conditions, context, initMatchResults.BackReferences); if (!condResult.Success) { context.Logger?.UrlRewriteDidNotMatchRule(Name); return; } } context.Logger?.UrlRewriteMatchedRule(Name); // at this point we know the rule passed, evaluate the replacement. Action.ApplyAction(context, initMatchResults?.BackReferences, condResult?.BackReferences); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class InputParser { private const char Colon = ':'; private const char OpenBrace = '{'; private const char CloseBrace = '}'; private readonly IISRewriteMapCollection _rewriteMaps; public InputParser() { } public InputParser(IISRewriteMapCollection rewriteMaps) { _rewriteMaps = rewriteMaps; } /// /// Creates a pattern, which is a template to create a new test string to /// compare to the condition. Can contain server variables, back references, etc. /// /// /// A new , containing a list of public Pattern ParseInputString(string testString) { return ParseInputString(testString, UriMatchPart.Path); } /// /// Creates a pattern, which is a template to create a new test string to /// compare to the condition. Can contain server variables, back references, etc. /// /// /// When testString evaluates to a URL segment, specify which part of the URI to evaluate. /// A new , containing a list of public Pattern ParseInputString(string testString, UriMatchPart uriMatchPart) { if (testString == null) { testString = string.Empty; } var context = new ParserContext(testString); return ParseString(context, uriMatchPart); } private Pattern ParseString(ParserContext context, UriMatchPart uriMatchPart) { var results = new List(); while (context.Next()) { if (context.Current == OpenBrace) { // This is a server parameter, parse for a condition variable if (!context.Next()) { // missing { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } ParseParameter(context, results, uriMatchPart); } else if (context.Current == CloseBrace) { return new Pattern(results); } else { // Parse for literals, which will return on either the end of the test string // or when it hits a special character ParseLiteral(context, results); } } return new Pattern(results); } private void ParseParameter(ParserContext context, IList results, UriMatchPart uriMatchPart) { context.Mark(); // Four main cases: // 1. {NAME} - Server Variable, create lambda to get the part of the context // 2. {R:1} - IRule parameter // 3. {C:1} - Condition Parameter // 4. {function:xxx} - String function // (unless we support Reload) string parameter; while (context.Next()) { if (context.Current == CloseBrace) { // This is just a server variable, so we do a lookup and verify the server variable exists. parameter = context.Capture(); results.Add(ServerVariables.FindServerVariable(parameter, context, uriMatchPart)); return; } else if (context.Current == Colon) { parameter = context.Capture(); // Only 5 strings to expect here. Case sensitive. switch (parameter) { case "ToLower": { var pattern = ParseString(context, uriMatchPart); results.Add(new ToLowerSegment(pattern)); // at this point, we expect our context to be on the ending closing brace, // because the ParseString() call will increment the context until it // has processed the new string. if (context.Current != CloseBrace) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } return; } case "UrlDecode": { throw new NotImplementedException("UrlDecode is not implemented because of no great library available"); } case "UrlEncode": { var pattern = ParseString(context, uriMatchPart); results.Add(new UrlEncodeSegment(pattern)); if (context.Current != CloseBrace) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } return; } case "R": { var index = GetBackReferenceIndex(context); results.Add(new RuleMatchSegment(index)); return; } case "C": { var index = GetBackReferenceIndex(context); results.Add(new ConditionMatchSegment(index)); return; } default: var rewriteMap = _rewriteMaps?[parameter]; if (rewriteMap != null) { var pattern = ParseString(context, uriMatchPart); results.Add(new RewriteMapSegment(rewriteMap, pattern)); return; } throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(parameter, context.Index)); } } } throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } private static int GetBackReferenceIndex(ParserContext context) { if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index)); } context.Mark(); while (context.Current != CloseBrace) { if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } } var res = context.Capture(); int index; if (!int.TryParse(res, NumberStyles.None, CultureInfo.InvariantCulture, out index)) { throw new FormatException(Resources.FormatError_InputParserInvalidInteger(res, context.Index)); } if (index > 9 || index < 0) { throw new FormatException(Resources.FormatError_InputParserIndexOutOfRange(res, context.Index)); } return index; } private static void ParseLiteral(ParserContext context, IList results) { context.Mark(); string literal; while (true) { if (context.Current == OpenBrace || context.Current == CloseBrace) { literal = context.Capture(); context.Back(); break; } if (!context.Next()) { literal = context.Capture(); break; } } results.Add(new LiteralSegment(literal)); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InvalidUrlRewriteFormatException.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Xml; using System.Xml.Linq; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class InvalidUrlRewriteFormatException : FormatException { public int LineNumber { get; } public int LinePosition { get; } public InvalidUrlRewriteFormatException(XElement element, string message) : base(FormatMessage(element, message)) { } public InvalidUrlRewriteFormatException(XElement element, string message, Exception innerException) : base(FormatMessage(element, message), innerException) { var xmlLineInfo = (IXmlLineInfo)element; LineNumber = xmlLineInfo.LineNumber; LinePosition = xmlLineInfo.LinePosition; } private static string FormatMessage(XElement element, string message) { var xmlLineInfo = (IXmlLineInfo)element; return Resources.FormatError_UrlRewriteParseError(message, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/LogicalGrouping.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public enum LogicalGrouping { MatchAll, MatchAny } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/MatchType.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public enum MatchType { Pattern, IsFile, IsDirectory } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/PatternSyntax.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public enum PatternSyntax { ECMAScript, Wildcard, ExactMatch } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RedirectType.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public enum RedirectType { Permanent = StatusCodes.Status301MovedPermanently, Found = StatusCodes.Status302Found, SeeOther = StatusCodes.Status303SeeOther, Temporary = StatusCodes.Status307TemporaryRedirect } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Xml.Linq; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public static class RewriteMapParser { public static IISRewriteMapCollection Parse(XElement xmlRoot) { if (xmlRoot == null) { throw new ArgumentNullException(nameof(xmlRoot)); } var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault(); if (mapsElement == null) { return null; } var rewriteMaps = new IISRewriteMapCollection(); foreach (var mapElement in mapsElement.Elements(RewriteTags.RewriteMap)) { var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); foreach (var addElement in mapElement.Elements(RewriteTags.Add)) { map[addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant()] = addElement.Attribute(RewriteTags.Value).Value; } rewriteMaps.Add(map); } return rewriteMaps; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteTags.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public static class RewriteTags { public const string Action = "action"; public const string Add = "add"; public const string AppendQueryString = "appendQueryString"; public const string Conditions = "conditions"; public const string Enabled = "enabled"; public const string GlobalRules = "globalRules"; public const string IgnoreCase = "ignoreCase"; public const string Input = "input"; public const string Key = "key"; public const string LogicalGrouping = "logicalGrouping"; public const string LogRewrittenUrl = "logRewrittenUrl"; public const string Match = "match"; public const string MatchPattern = "matchPattern"; public const string MatchType = "matchType"; public const string Name = "name"; public const string Negate = "negate"; public const string Pattern = "pattern"; public const string PatternSyntax = "patternSyntax"; public const string RedirectType = "redirectType"; public const string Rewrite = "rewrite"; public const string RewriteMap = "rewriteMap"; public const string RewriteMaps = "rewriteMaps"; public const string Rule = "rule"; public const string Rules = "rules"; public const string StatusCode = "statusCode"; public const string SubStatusCode = "subStatusCode"; public const string StatusDescription = "statusDescription"; public const string StatusReason = "statusReason"; public const string StopProcessing = "stopProcessing"; public const string TrackAllCaptures = "trackAllCaptures"; public const string Type = "type"; public const string Url = "url"; public const string Value = "value"; } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/ServerVariables.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public static class ServerVariables { /// /// Returns the matching for the given /// /// The server variable /// The parser context which is utilized when an exception is thrown /// Indicates whether the full URI or the path should be evaluated for URL segments /// Thrown when the server variable is unknown /// The matching public static PatternSegment FindServerVariable(string serverVariable, ParserContext context, UriMatchPart uriMatchPart) { switch (serverVariable) { // TODO Add all server variables here. case "ALL_RAW": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "APP_POOL_ID": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "CONTENT_LENGTH": return new HeaderSegment(HeaderNames.ContentLength); case "CONTENT_TYPE": return new HeaderSegment(HeaderNames.ContentType); case "HTTP_ACCEPT": return new HeaderSegment(HeaderNames.Accept); case "HTTP_COOKIE": return new HeaderSegment(HeaderNames.Cookie); case "HTTP_HOST": return new HeaderSegment(HeaderNames.Host); case "HTTP_REFERER": return new HeaderSegment(HeaderNames.Referer); case "HTTP_USER_AGENT": return new HeaderSegment(HeaderNames.UserAgent); case "HTTP_CONNECTION": return new HeaderSegment(HeaderNames.Connection); case "HTTP_URL": return new UrlSegment(uriMatchPart); case "HTTPS": return new IsHttpsUrlSegment(); case "LOCAL_ADDR": return new LocalAddressSegment(); case "HTTP_PROXY_CONNECTION": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "QUERY_STRING": return new QueryStringSegment(); case "REMOTE_ADDR": return new RemoteAddressSegment(); case "REMOTE_HOST": throw new NotSupportedException(Resources.FormatError_UnsupportedServerVariable(serverVariable)); case "REMOTE_PORT": return new RemotePortSegment(); case "REQUEST_FILENAME": return new RequestFileNameSegment(); case "REQUEST_METHOD": return new RequestMethodSegment(); case "REQUEST_URI": return new UrlSegment(uriMatchPart); default: throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(serverVariable, context.Index)); } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UriMatchCondition.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class UriMatchCondition : Condition { private TimeSpan _regexTimeout = TimeSpan.FromSeconds(1); public UriMatchCondition(InputParser inputParser, string input, string pattern, UriMatchPart uriMatchPart, bool ignoreCase, bool negate) { var regexOptions = RegexOptions.CultureInvariant | RegexOptions.Compiled; regexOptions = ignoreCase ? regexOptions | RegexOptions.IgnoreCase : regexOptions; var regex = new Regex( pattern, regexOptions, _regexTimeout ); Input = inputParser.ParseInputString(input, uriMatchPart); Match = new RegexMatch(regex, negate); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UriMatchPart.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public enum UriMatchPart { Full, Path } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class UrlRewriteFileParser { private InputParser _inputParser; /// /// Parse an IIS rewrite section into a list of s. /// /// The reader containing the rewrite XML public IList Parse(TextReader reader) { var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo); var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); if (xmlRoot == null) { return null; } _inputParser = new InputParser(RewriteMapParser.Parse(xmlRoot)); var result = new List(); ParseRules(xmlRoot.Descendants(RewriteTags.GlobalRules).FirstOrDefault(), result, global: true); ParseRules(xmlRoot.Descendants(RewriteTags.Rules).FirstOrDefault(), result, global: false); return result; } private void ParseRules(XElement rules, IList result, bool global) { if (rules == null) { return; } foreach (var rule in rules.Elements(RewriteTags.Rule)) { var builder = new UrlRewriteRuleBuilder { Global = global }; ParseRuleAttributes(rule, builder); if (builder.Enabled) { result.Add(builder.Build()); } } } private void ParseRuleAttributes(XElement rule, UrlRewriteRuleBuilder builder) { builder.Name = rule.Attribute(RewriteTags.Name)?.Value; if (ParseBool(rule, RewriteTags.Enabled, defaultValue: true)) { builder.Enabled = true; } else { return; } var patternSyntax = ParseEnum(rule, RewriteTags.PatternSyntax, PatternSyntax.ECMAScript); var stopProcessing = ParseBool(rule, RewriteTags.StopProcessing, defaultValue: false); var match = rule.Element(RewriteTags.Match); if (match == null) { throw new InvalidUrlRewriteFormatException(rule, "Condition must have an associated match"); } var action = rule.Element(RewriteTags.Action); if (action == null) { throw new InvalidUrlRewriteFormatException(rule, "Rule does not have an associated action attribute"); } ParseMatch(match, builder, patternSyntax); ParseConditions(rule.Element(RewriteTags.Conditions), builder, patternSyntax); ParseUrlAction(action, builder, stopProcessing); } private void ParseMatch(XElement match, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) { var parsedInputString = match.Attribute(RewriteTags.Url)?.Value; if (parsedInputString == null) { throw new InvalidUrlRewriteFormatException(match, "Match must have Url Attribute"); } var ignoreCase = ParseBool(match, RewriteTags.IgnoreCase, defaultValue: true); var negate = ParseBool(match, RewriteTags.Negate, defaultValue: false); builder.AddUrlMatch(parsedInputString, ignoreCase, negate, patternSyntax); } private void ParseConditions(XElement conditions, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) { if (conditions == null) { return; } var grouping = ParseEnum(conditions, RewriteTags.LogicalGrouping, LogicalGrouping.MatchAll); var trackAllCaptures = ParseBool(conditions, RewriteTags.TrackAllCaptures, defaultValue: false); builder.ConfigureConditionBehavior(grouping, trackAllCaptures); foreach (var cond in conditions.Elements(RewriteTags.Add)) { ParseCondition(cond, builder, patternSyntax); } } private void ParseCondition(XElement conditionElement, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax) { var ignoreCase = ParseBool(conditionElement, RewriteTags.IgnoreCase, defaultValue: true); var negate = ParseBool(conditionElement, RewriteTags.Negate, defaultValue: false); var matchType = ParseEnum(conditionElement, RewriteTags.MatchType, MatchType.Pattern); var parsedInputString = conditionElement.Attribute(RewriteTags.Input)?.Value; if (parsedInputString == null) { throw new InvalidUrlRewriteFormatException(conditionElement, "Conditions must have an input attribute"); } var parsedPatternString = conditionElement.Attribute(RewriteTags.Pattern)?.Value; Condition condition; switch (patternSyntax) { case PatternSyntax.ECMAScript: { switch (matchType) { case MatchType.Pattern: { if (string.IsNullOrEmpty(parsedPatternString)) { throw new FormatException("Match does not have an associated pattern attribute in condition"); } condition = new UriMatchCondition(_inputParser, parsedInputString, parsedPatternString, builder.UriMatchPart, ignoreCase, negate); break; } case MatchType.IsDirectory: { condition = new Condition { Input = _inputParser.ParseInputString(parsedInputString, builder.UriMatchPart), Match = new IsDirectoryMatch(negate) }; break; } case MatchType.IsFile: { condition = new Condition { Input = _inputParser.ParseInputString(parsedInputString, builder.UriMatchPart), Match = new IsFileMatch(negate) }; break; } default: throw new FormatException("Unrecognized matchType"); } break; } case PatternSyntax.Wildcard: throw new NotSupportedException("Wildcard syntax is not supported"); case PatternSyntax.ExactMatch: if (string.IsNullOrEmpty(parsedPatternString)) { throw new FormatException("Match does not have an associated pattern attribute in condition"); } condition = new Condition { Input = _inputParser.ParseInputString(parsedInputString, builder.UriMatchPart), Match = new ExactMatch(ignoreCase, parsedPatternString, negate) }; break; default: throw new FormatException("Unrecognized pattern syntax"); } builder.AddUrlCondition(condition); } private void ParseUrlAction(XElement urlAction, UrlRewriteRuleBuilder builder, bool stopProcessing) { var actionType = ParseEnum(urlAction, RewriteTags.Type, ActionType.None); UrlAction action; switch (actionType) { case ActionType.None: action = new NoneAction(stopProcessing ? RuleResult.SkipRemainingRules : RuleResult.ContinueRules); break; case ActionType.Rewrite: case ActionType.Redirect: var url = string.Empty; if (urlAction.Attribute(RewriteTags.Url) != null) { url = urlAction.Attribute(RewriteTags.Url).Value; if (string.IsNullOrEmpty(url)) { throw new InvalidUrlRewriteFormatException(urlAction, "Url attribute cannot contain an empty string"); } } var urlPattern = _inputParser.ParseInputString(url, builder.UriMatchPart); var appendQuery = ParseBool(urlAction, RewriteTags.AppendQueryString, defaultValue: true); if (actionType == ActionType.Rewrite) { action = new RewriteAction(stopProcessing ? RuleResult.SkipRemainingRules : RuleResult.ContinueRules, urlPattern, appendQuery); } else { var redirectType = ParseEnum(urlAction, RewriteTags.RedirectType, RedirectType.Permanent); action = new RedirectAction((int)redirectType, urlPattern, appendQuery); } break; case ActionType.AbortRequest: action = new AbortAction(); break; case ActionType.CustomResponse: int statusCode; if (!int.TryParse(urlAction.Attribute(RewriteTags.StatusCode)?.Value, NumberStyles.None, CultureInfo.InvariantCulture, out statusCode)) { throw new InvalidUrlRewriteFormatException(urlAction, "A valid status code is required"); } if (statusCode < 200 || statusCode > 999) { throw new NotSupportedException("Status codes must be between 200 and 999 (inclusive)"); } if (!string.IsNullOrEmpty(urlAction.Attribute(RewriteTags.SubStatusCode)?.Value)) { throw new NotSupportedException("Substatus codes are not supported"); } var statusReason = urlAction.Attribute(RewriteTags.StatusReason)?.Value; var statusDescription = urlAction.Attribute(RewriteTags.StatusDescription)?.Value; action = new CustomResponseAction(statusCode) { StatusReason = statusReason, StatusDescription = statusDescription }; break; default: throw new NotSupportedException($"The action type {actionType} wasn't recognized"); } builder.AddUrlAction(action); } private bool ParseBool(XElement element, string rewriteTag, bool defaultValue) { bool result; var attribute = element.Attribute(rewriteTag); if (attribute == null) { return defaultValue; } else if (!bool.TryParse(attribute.Value, out result)) { throw new InvalidUrlRewriteFormatException(element, $"The {rewriteTag} parameter '{attribute.Value}' was not recognized"); } return result; } private TEnum ParseEnum(XElement element, string rewriteTag, TEnum defaultValue) where TEnum : struct { TEnum enumResult = default(TEnum); var attribute = element.Attribute(rewriteTag); if (attribute == null) { return defaultValue; } else if(!Enum.TryParse(attribute.Value, ignoreCase: true, result: out enumResult)) { throw new InvalidUrlRewriteFormatException(element, $"The {rewriteTag} parameter '{attribute.Value}' was not recognized"); } return enumResult; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteRuleBuilder.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class UrlRewriteRuleBuilder { private readonly TimeSpan _regexTimeout = TimeSpan.FromSeconds(1); public string Name { get; set; } public bool Enabled { get; set; } public bool Global { get; set; } public UriMatchPart UriMatchPart => Global ? UriMatchPart.Full : UriMatchPart.Path; private UrlMatch _initialMatch; private ConditionCollection _conditions; private UrlAction _action; public IISUrlRewriteRule Build() { if (_initialMatch == null || _action == null) { throw new InvalidOperationException("Cannot create UrlRewriteRule without action and match"); } return new IISUrlRewriteRule(Name, _initialMatch, _conditions, _action, Global); } public void AddUrlAction(UrlAction action) { if (action == null) { throw new ArgumentNullException(nameof(action), "Rules must contain an action"); } _action = action; } public void AddUrlMatch(string input, bool ignoreCase = true, bool negate = false, PatternSyntax patternSyntax = PatternSyntax.ECMAScript) { switch (patternSyntax) { case PatternSyntax.ECMAScript: { if (ignoreCase) { var regex = new Regex(input, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase, _regexTimeout); _initialMatch = new RegexMatch(regex, negate); } else { var regex = new Regex(input, RegexOptions.CultureInvariant | RegexOptions.Compiled, _regexTimeout); _initialMatch = new RegexMatch(regex, negate); } break; } case PatternSyntax.Wildcard: throw new NotSupportedException("Wildcard syntax is not supported"); case PatternSyntax.ExactMatch: _initialMatch = new ExactMatch(ignoreCase, input, negate); break; } } public void ConfigureConditionBehavior(LogicalGrouping logicalGrouping, bool trackAllCaptures) { _conditions = new ConditionCollection(logicalGrouping, trackAllCaptures); } public void AddUrlCondition(Condition condition) { if (_conditions == null) { throw new InvalidOperationException($"You must first configure condition behavior by calling {nameof(ConfigureConditionBehavior)}"); } if (condition == null) { throw new ArgumentNullException(nameof(condition)); } _conditions.Add(condition); } public void AddUrlConditions(IEnumerable conditions) { if (_conditions == null) { throw new InvalidOperationException($"You must first configure condition behavior by calling {nameof(ConfigureConditionBehavior)}"); } _conditions.AddConditions(conditions); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/MatchResults.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text.RegularExpressions; namespace Microsoft.AspNetCore.Rewrite.Internal { public class MatchResults { public static readonly MatchResults EmptySuccess = new MatchResults { Success = true }; public static readonly MatchResults EmptyFailure = new MatchResults { Success = false }; public bool Success { get; set; } public BackReferenceCollection BackReferences { get; set; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/ParserContext.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal { /// /// Represents a string iterator, with captures. /// public class ParserContext { public readonly string Template; public int Index { get; set; } private int? _mark; public ParserContext(string condition) { Template = condition; Index = -1; } public char Current => (Index < Template.Length && Index >= 0) ? Template[Index] : (char)0; public bool Back() { return --Index >= 0; } public bool Next() { return ++Index < Template.Length; } public bool HasNext() { return (Index + 1) < Template.Length; } public void Mark() { _mark = Index; } public int GetIndex() { return Index; } public string Capture() { // TODO make this return a range rather than a string. if (_mark.HasValue) { var value = Template.Substring(_mark.Value, Index - _mark.Value); _mark = null; return value; } else { return null; } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/Pattern.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal { public class Pattern { public IList PatternSegments { get; } public Pattern(IList patternSegments) { PatternSegments = patternSegments; } public string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { foreach (var pattern in PatternSegments) { context.Builder.Append(pattern.Evaluate(context, ruleBackReferences, conditionBackReferences)); } var retVal = context.Builder.ToString(); context.Builder.Clear(); return retVal; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal { public abstract class PatternSegment { public abstract string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences); } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ConditionMatchSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class ConditionMatchSegment : PatternSegment { private readonly int _index; public ConditionMatchSegment(int index) { _index = index; } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return conditionBackReferences?[_index]; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/DateTimeSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Globalization; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class DateTimeSegment : PatternSegment { private readonly DateTimePortion _portion; public DateTimeSegment(string segment) { switch (segment) { case "TIME_YEAR": _portion = DateTimePortion.Year; break; case "TIME_MON": _portion = DateTimePortion.Month; break; case "TIME_DAY": _portion = DateTimePortion.Day; break; case "TIME_HOUR": _portion = DateTimePortion.Hour; break; case "TIME_MIN": _portion = DateTimePortion.Minute; break; case "TIME_SEC": _portion = DateTimePortion.Second; break; case "TIME_WDAY": _portion = DateTimePortion.DayOfWeek; break; case "TIME": _portion = DateTimePortion.Time; break; default: throw new FormatException($"Unsupported segment: '{segment}'"); } } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReference) { switch (_portion) { case DateTimePortion.Year: return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture); case DateTimePortion.Month: return DateTimeOffset.UtcNow.Month.ToString(CultureInfo.InvariantCulture); case DateTimePortion.Day: return DateTimeOffset.UtcNow.Day.ToString(CultureInfo.InvariantCulture); case DateTimePortion.Hour: return DateTimeOffset.UtcNow.Hour.ToString(CultureInfo.InvariantCulture); case DateTimePortion.Minute: return DateTimeOffset.UtcNow.Minute.ToString(CultureInfo.InvariantCulture); case DateTimePortion.Second: return DateTimeOffset.UtcNow.Second.ToString(CultureInfo.InvariantCulture); case DateTimePortion.DayOfWeek: return ((int)DateTimeOffset.UtcNow.DayOfWeek).ToString(CultureInfo.InvariantCulture); case DateTimePortion.Time: return DateTimeOffset.UtcNow.ToString(CultureInfo.InvariantCulture); default: return string.Empty; } } private enum DateTimePortion { Year, Month, Day, Hour, Minute, Second, DayOfWeek, Time } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/HeaderSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class HeaderSegment : PatternSegment { private readonly string _header; public HeaderSegment(string header) { _header = header; } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Request.Headers[_header]; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsHttpsModSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class IsHttpsModSegment : PatternSegment { // Note: Mod rewrite pattern matches on lower case "on" and "off" // while IIS looks for capitalized "ON" and "OFF" public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Request.IsHttps ? "on" : "off"; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsHttpsUrlSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class IsHttpsUrlSegment : PatternSegment { // Note: Mod rewrite pattern matches on lower case "on" and "off" // while IIS looks for capitalized "ON" and "OFF" public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Request.IsHttps ? "ON" : "OFF"; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/IsIPV6Segment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net.Sockets; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class IsIPV6Segment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { if (context.HttpContext.Connection.RemoteIpAddress == null) { return "off"; } return context.HttpContext.Connection.RemoteIpAddress.AddressFamily == AddressFamily.InterNetworkV6 ? "on" : "off"; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LiteralSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class LiteralSegment : PatternSegment { private readonly string _literal; public LiteralSegment(string literal) { _literal = literal; } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return _literal; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LocalAddressSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class LocalAddressSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Connection.LocalIpAddress?.ToString(); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/LocalPortSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Globalization; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class LocalPortSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Connection.LocalPort.ToString(CultureInfo.InvariantCulture); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/QueryStringSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class QueryStringSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackRefernces, BackReferenceCollection conditionBackReferences) { var queryString = context.HttpContext.Request.QueryString.ToString(); if (!string.IsNullOrEmpty(queryString)) { return queryString.Substring(1); } return queryString; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RemoteAddressSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class RemoteAddressSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Connection.RemoteIpAddress?.ToString(); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RemotePortSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Globalization; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class RemotePortSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Connection.RemotePort.ToString(CultureInfo.InvariantCulture); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RequestFilenameSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class RequestFileNameSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Request.Path; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RequestMethodSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class RequestMethodSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Request.Method; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class RewriteMapSegment : PatternSegment { private readonly IISRewriteMap _rewriteMap; private readonly Pattern _pattern; public RewriteMapSegment(IISRewriteMap rewriteMap, Pattern pattern) { _rewriteMap = rewriteMap; _pattern = pattern; } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { var key = _pattern.Evaluate(context, ruleBackReferences, conditionBackReferences).ToLowerInvariant(); return _rewriteMap[key]; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RuleMatchSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class RuleMatchSegment : PatternSegment { private readonly int _index; public RuleMatchSegment(int index) { _index = index; } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return ruleBackReferences[_index]; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/SchemeSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class SchemeSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Request.Scheme; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ServerProtocolSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class ServerProtocolSegment : PatternSegment { public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return context.HttpContext.Features.Get()?.Protocol; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/ToLowerSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class ToLowerSegment : PatternSegment { private readonly Pattern _pattern; public ToLowerSegment(Pattern pattern) { _pattern = pattern; } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { // PERF as we share the string builder across the context, we need to make a new one here to evaluate // lowercase segments. var tempBuilder = context.Builder; context.Builder = new StringBuilder(64); var pattern = _pattern.Evaluate(context, ruleBackReferences, conditionBackReferences); context.Builder = tempBuilder; return pattern.ToLowerInvariant(); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlEncodeSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; using System.Text.Encodings.Web; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class UrlEncodeSegment : PatternSegment { private readonly Pattern _pattern; public UrlEncodeSegment(Pattern pattern) { _pattern = pattern; } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { var oldBuilder = context.Builder; // PERF // Because we need to be able to evaluate multiple nested patterns, // we provided a new string builder and evaluate the new pattern, // and restore it after evaluation. context.Builder = new StringBuilder(64); var pattern = _pattern.Evaluate(context, ruleBackReferences, conditionBackReferences); context.Builder = oldBuilder; return UrlEncoder.Default.Encode(pattern); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/UrlSegment.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { public class UrlSegment : PatternSegment { private readonly UriMatchPart _uriMatchPart; public UrlSegment() : this(UriMatchPart.Path) { } public UrlSegment(UriMatchPart uriMatchPart) { _uriMatchPart = uriMatchPart; } public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { return _uriMatchPart == UriMatchPart.Full ? context.HttpContext.Request.GetEncodedUrl() : (string)context.HttpContext.Request.Path; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/RedirectRule.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; using Microsoft.AspNetCore.Rewrite.Logging; namespace Microsoft.AspNetCore.Rewrite.Internal { public class RedirectRule : IRule { private readonly TimeSpan _regexTimeout = TimeSpan.FromSeconds(1); public Regex InitialMatch { get; } public string Replacement { get; } public int StatusCode { get; } public RedirectRule(string regex, string replacement, int statusCode) { if (string.IsNullOrEmpty(regex)) { throw new ArgumentException(nameof(regex)); } if (string.IsNullOrEmpty(replacement)) { throw new ArgumentException(nameof(replacement)); } InitialMatch = new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant, _regexTimeout); Replacement = replacement; StatusCode = statusCode; } public virtual void ApplyRule(RewriteContext context) { var path = context.HttpContext.Request.Path; var pathBase = context.HttpContext.Request.PathBase; Match initMatchResults; if (path == PathString.Empty) { initMatchResults = InitialMatch.Match(path.ToString()); } else { initMatchResults = InitialMatch.Match(path.ToString().Substring(1)); } if (initMatchResults.Success) { var newPath = initMatchResults.Result(Replacement); var response = context.HttpContext.Response; response.StatusCode = StatusCode; context.Result = RuleResult.EndResponse; if (string.IsNullOrEmpty(newPath)) { response.Headers[HeaderNames.Location] = pathBase.HasValue ? pathBase.Value : "/"; return; } if (newPath.IndexOf("://", StringComparison.Ordinal) == -1 && newPath[0] != '/') { newPath = '/' + newPath; } var split = newPath.IndexOf('?'); if (split >= 0) { var query = context.HttpContext.Request.QueryString.Add( QueryString.FromUriComponent( newPath.Substring(split))); // not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308 response.Headers[HeaderNames.Location] = pathBase + newPath.Substring(0, split) + query; } else { response.Headers[HeaderNames.Location] = pathBase + newPath + context.HttpContext.Request.QueryString; } context.Logger?.RedirectedSummary(newPath); } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/RedirectToHttpsRule.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Logging; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite.Internal { public class RedirectToHttpsRule : IRule { public int? SSLPort { get; set; } public int StatusCode { get; set; } public virtual void ApplyRule(RewriteContext context) { if (!context.HttpContext.Request.IsHttps) { var host = context.HttpContext.Request.Host; if (SSLPort.HasValue && SSLPort.Value > 0) { // a specific SSL port is specified host = new HostString(host.Host, SSLPort.Value); } else { // clear the port host = new HostString(host.Host); } var req = context.HttpContext.Request; var newUrl = new StringBuilder().Append("https://").Append(host).Append(req.PathBase).Append(req.Path).Append(req.QueryString); var response = context.HttpContext.Response; response.StatusCode = StatusCode; response.Headers[HeaderNames.Location] = newUrl.ToString(); context.Result = RuleResult.EndResponse; context.Logger?.RedirectedToHttps(); } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/RedirectToWwwRule.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Rewrite.Logging; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite.Internal { public class RedirectToWwwRule : IRule { public readonly int _statusCode; public RedirectToWwwRule(int statusCode) { _statusCode = statusCode; } public virtual void ApplyRule(RewriteContext context) { var req = context.HttpContext.Request; if (req.Host.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { context.Result = RuleResult.ContinueRules; return; } if (req.Host.Value.StartsWith("www.", StringComparison.OrdinalIgnoreCase)) { context.Result = RuleResult.ContinueRules; return; } var wwwHost = new HostString($"www.{req.Host.Value}"); var newUrl = UriHelper.BuildAbsolute(req.Scheme, wwwHost, req.PathBase, req.Path, req.QueryString); var response = context.HttpContext.Response; response.StatusCode = _statusCode; response.Headers[HeaderNames.Location] = newUrl; context.Result = RuleResult.EndResponse; context.Logger?.RedirectedToWww(); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/RewriteRule.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Rewrite.Logging; namespace Microsoft.AspNetCore.Rewrite.Internal { public class RewriteRule : IRule { private readonly TimeSpan _regexTimeout = TimeSpan.FromSeconds(1); public Regex InitialMatch { get; } public string Replacement { get; } public bool StopProcessing { get; } public RewriteRule(string regex, string replacement, bool stopProcessing) { if (string.IsNullOrEmpty(regex)) { throw new ArgumentException(nameof(regex)); } if (string.IsNullOrEmpty(replacement)) { throw new ArgumentException(nameof(replacement)); } InitialMatch = new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant, _regexTimeout); Replacement = replacement; StopProcessing = stopProcessing; } public virtual void ApplyRule(RewriteContext context) { var path = context.HttpContext.Request.Path; Match initMatchResults; if (path == PathString.Empty) { initMatchResults = InitialMatch.Match(path.ToString()); } else { initMatchResults = InitialMatch.Match(path.ToString().Substring(1)); } if (initMatchResults.Success) { var result = initMatchResults.Result(Replacement); var request = context.HttpContext.Request; if (StopProcessing) { context.Result = RuleResult.SkipRemainingRules; } if (string.IsNullOrEmpty(result)) { result = "/"; } if (result.IndexOf("://", StringComparison.Ordinal) >= 0) { string scheme; HostString host; PathString pathString; QueryString query; FragmentString fragment; UriHelper.FromAbsolute(result, out scheme, out host, out pathString, out query, out fragment); request.Scheme = scheme; request.Host = host; request.Path = pathString; request.QueryString = query.Add(request.QueryString); } else { var split = result.IndexOf('?'); if (split >= 0) { var newPath = result.Substring(0, split); if (newPath[0] == '/') { request.Path = PathString.FromUriComponent(newPath); } else { request.Path = PathString.FromUriComponent('/' + newPath); } request.QueryString = request.QueryString.Add( QueryString.FromUriComponent( result.Substring(split))); } else { if (result[0] == '/') { request.Path = PathString.FromUriComponent(result); } else { request.Path = PathString.FromUriComponent('/' + result); } } } context.Logger?.RewriteSummary(result); } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal { public abstract class UrlAction { protected Pattern Url { get; set; } public abstract void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences); } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/AbortAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Rewrite.Logging; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class AbortAction : UrlAction { public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { context.HttpContext.Abort(); context.Result = RuleResult.EndResponse; context.Logger?.AbortedRequest(context.HttpContext.Request.Path + context.HttpContext.Request.QueryString); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ChangeCookieAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class ChangeCookieAction : UrlAction { private readonly Func _timeSource; private CookieOptions _cachedOptions; public ChangeCookieAction(string name) : this(name, () => DateTimeOffset.UtcNow) { } // for testing internal ChangeCookieAction(string name, Func timeSource) { _timeSource = timeSource; if (string.IsNullOrEmpty(name)) { throw new ArgumentException(nameof(name)); } Name = name; } public string Name { get; } public string Value { get; set; } public string Domain { get; set; } public TimeSpan Lifetime { get; set; } public string Path { get; set; } public bool Secure { get; set; } public bool HttpOnly { get; set; } public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { var options = GetOrCreateOptions(); context.HttpContext.Response.Cookies.Append(Name, Value ?? string.Empty, options); } private CookieOptions GetOrCreateOptions() { if (Lifetime > TimeSpan.Zero) { var now = _timeSource(); return new CookieOptions() { Domain = Domain, HttpOnly = HttpOnly, Secure = Secure, Path = Path, Expires = now.Add(Lifetime) }; } if (_cachedOptions == null) { _cachedOptions = new CookieOptions() { Domain = Domain, HttpOnly = HttpOnly, Secure = Secure, Path = Path }; } return _cachedOptions; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/CustomResponseAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Rewrite.Logging; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class CustomResponseAction : UrlAction { public int StatusCode { get; } public string StatusReason { get; set; } public string StatusDescription { get; set; } public CustomResponseAction(int statusCode) { StatusCode = statusCode; } public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { var response = context.HttpContext.Response; response.StatusCode = StatusCode; if (!string.IsNullOrEmpty(StatusReason)) { context.HttpContext.Features.Get().ReasonPhrase = StatusReason; } if (!string.IsNullOrEmpty(StatusDescription)) { var content = Encoding.UTF8.GetBytes(StatusDescription); response.ContentLength = content.Length; response.ContentType = "text/plain; charset=utf-8"; response.Body.Write(content, 0, content.Length); } context.Result = RuleResult.EndResponse; context.Logger?.CustomResponse(context.HttpContext.Request.GetEncodedUrl()); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/ForbiddenAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class ForbiddenAction : UrlAction { public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { context.HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden; context.Result = RuleResult.EndResponse; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/GoneAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class GoneAction : UrlAction { public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { context.HttpContext.Response.StatusCode = StatusCodes.Status410Gone; context.Result = RuleResult.EndResponse; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/NoneAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class NoneAction : UrlAction { public RuleResult Result { get; } public NoneAction(RuleResult result) { Result = result; } // Explicitly say that nothing happens public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { context.Result = Result; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RedirectAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class RedirectAction : UrlAction { public int StatusCode { get; } public bool QueryStringAppend { get; } public bool QueryStringDelete { get; } public bool EscapeBackReferences { get; } public RedirectAction( int statusCode, Pattern pattern, bool queryStringAppend, bool queryStringDelete, bool escapeBackReferences) { StatusCode = statusCode; Url = pattern; QueryStringAppend = queryStringAppend; QueryStringDelete = queryStringDelete; EscapeBackReferences = escapeBackReferences; } public RedirectAction( int statusCode, Pattern pattern, bool queryStringAppend) : this( statusCode, pattern, queryStringAppend, queryStringDelete: true, escapeBackReferences: false) { } public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { var pattern = Url.Evaluate(context, ruleBackReferences, conditionBackReferences); var response = context.HttpContext.Response; var pathBase = context.HttpContext.Request.PathBase; if (EscapeBackReferences) { // because escapebackreferences will be encapsulated by the pattern, just escape the pattern pattern = Uri.EscapeDataString(pattern); } if (string.IsNullOrEmpty(pattern)) { response.Headers[HeaderNames.Location] = pathBase.HasValue ? pathBase.Value : "/"; return; } if (pattern.IndexOf("://", StringComparison.Ordinal) == -1 && pattern[0] != '/') { pattern = '/' + pattern; } response.StatusCode = StatusCode; // url can either contain the full url or the path and query // always add to location header. // TODO check for false positives var split = pattern.IndexOf('?'); if (split >= 0 && QueryStringAppend) { var query = context.HttpContext.Request.QueryString.Add( QueryString.FromUriComponent( pattern.Substring(split))); // not using the response.redirect here because status codes may be 301, 302, 307, 308 response.Headers[HeaderNames.Location] = pathBase + pattern.Substring(0, split) + query; } else { // If the request url has a query string and the target does not, append the query string // by default. if (QueryStringDelete) { response.Headers[HeaderNames.Location] = pathBase + pattern; } else { response.Headers[HeaderNames.Location] = pathBase + pattern + context.HttpContext.Request.QueryString; } } context.Result = RuleResult.EndResponse; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlActions/RewriteAction.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions { public class RewriteAction : UrlAction { public RuleResult Result { get; } public bool QueryStringAppend { get; } public bool QueryStringDelete { get; } public bool EscapeBackReferences { get; } public RewriteAction( RuleResult result, Pattern pattern, bool queryStringAppend, bool queryStringDelete, bool escapeBackReferences) { // For the replacement, we must have at least // one segment (cannot have an empty replacement) Result = result; Url = pattern; QueryStringAppend = queryStringAppend; QueryStringDelete = queryStringDelete; EscapeBackReferences = escapeBackReferences; } public RewriteAction( RuleResult result, Pattern pattern, bool queryStringAppend) : this(result, pattern, queryStringAppend, queryStringDelete: false, escapeBackReferences: false) { } public override void ApplyAction(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { var pattern = Url.Evaluate(context, ruleBackReferences, conditionBackReferences); var request = context.HttpContext.Request; if (string.IsNullOrEmpty(pattern)) { pattern = "/"; } if (EscapeBackReferences) { // because escapebackreferences will be encapsulated by the pattern, just escape the pattern pattern = Uri.EscapeDataString(pattern); } // TODO PERF, substrings, object creation, etc. if (pattern.IndexOf("://", StringComparison.Ordinal) >= 0) { string scheme; HostString host; PathString path; QueryString query; FragmentString fragment; UriHelper.FromAbsolute(pattern, out scheme, out host, out path, out query, out fragment); if (query.HasValue) { if (QueryStringAppend) { request.QueryString = request.QueryString.Add(query); } else { request.QueryString = query; } } else if (QueryStringDelete) { request.QueryString = QueryString.Empty; } request.Scheme = scheme; request.Host = host; request.Path = path; } else { var split = pattern.IndexOf('?'); if (split >= 0) { var path = pattern.Substring(0, split); if (path[0] == '/') { request.Path = PathString.FromUriComponent(path); } else { request.Path = PathString.FromUriComponent('/' + path); } if (QueryStringAppend) { request.QueryString = request.QueryString.Add( QueryString.FromUriComponent( pattern.Substring(split))); } else { request.QueryString = QueryString.FromUriComponent( pattern.Substring(split)); } } else { if (pattern[0] == '/') { request.Path = PathString.FromUriComponent(pattern); } else { request.Path = PathString.FromUriComponent('/' + pattern); } if (QueryStringDelete) { request.QueryString = QueryString.Empty; } } } context.Result = Result; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatch.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal { public abstract class UrlMatch { protected bool Negate { get; set; } public abstract MatchResults Evaluate(string input, RewriteContext context); } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/ExactMatch.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class ExactMatch : UrlMatch { private readonly bool _ignoreCase; private readonly string _stringMatch; public ExactMatch(bool ignoreCase, string input, bool negate) { _ignoreCase = ignoreCase; _stringMatch = input; Negate = negate; } public override MatchResults Evaluate(string pattern, RewriteContext context) { var pathMatch = string.Compare(pattern, _stringMatch, _ignoreCase); var success = ((pathMatch == 0) != Negate); if (success) { return new MatchResults { Success = success, BackReferences = new BackReferenceCollection(pattern) }; } else { return MatchResults.EmptyFailure; } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/FileSizeMatch.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class FileSizeMatch : UrlMatch { public FileSizeMatch(bool negate) { Negate = negate; } public override MatchResults Evaluate(string input, RewriteContext context) { var fileInfo = context.StaticFileProvider.GetFileInfo(input); return fileInfo.Exists && fileInfo.Length > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IntegerMatch.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Globalization; using Microsoft.AspNetCore.Rewrite; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class IntegerMatch : UrlMatch { private readonly int _value; private readonly IntegerOperationType _operation; public IntegerMatch(int value, IntegerOperationType operation) { _value = value; _operation = operation; } public IntegerMatch(string value, IntegerOperationType operation) { int compValue; if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out compValue)) { throw new FormatException(Resources.Error_IntegerMatch_FormatExceptionMessage); } _value = compValue; _operation = operation; } public override MatchResults Evaluate(string input, RewriteContext context) { int compValue; if (!int.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out compValue)) { return MatchResults.EmptyFailure; } switch (_operation) { case IntegerOperationType.Equal: return compValue == _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.Greater: return compValue > _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.GreaterEqual: return compValue >= _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.Less: return compValue < _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.LessEqual: return compValue <= _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case IntegerOperationType.NotEqual: return compValue != _value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; default: return null; } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IntegerOperation.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public enum IntegerOperationType { Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsDirectoryMatch.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class IsDirectoryMatch : UrlMatch { public IsDirectoryMatch(bool negate) { Negate = negate; } public override MatchResults Evaluate(string pattern, RewriteContext context) { var res = context.StaticFileProvider.GetFileInfo(pattern).IsDirectory; return new MatchResults { Success = (res != Negate) }; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/IsFileMatch.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class IsFileMatch : UrlMatch { public IsFileMatch(bool negate) { Negate = negate; } public override MatchResults Evaluate(string pattern, RewriteContext context) { var res = context.StaticFileProvider.GetFileInfo(pattern).Exists; return new MatchResults { Success = (res != Negate) }; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/RegexMatch.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text.RegularExpressions; namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class RegexMatch : UrlMatch { private readonly Regex _match; public RegexMatch(Regex match, bool negate) { _match = match; Negate = negate; } public override MatchResults Evaluate(string pattern, RewriteContext context) { var res = _match.Match(pattern); return new MatchResults { BackReferences = new BackReferenceCollection(res.Groups), Success = (res.Success != Negate) }; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/StringMatch.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public class StringMatch : UrlMatch { private readonly string _value; private readonly StringOperationType _operation; private readonly bool _ignoreCase; public StringMatch(string value, StringOperationType operation, bool ignoreCase) { _value = value; _operation = operation; _ignoreCase = ignoreCase; } public override MatchResults Evaluate(string input, RewriteContext context) { switch (_operation) { case StringOperationType.Equal: return string.Compare(input, _value, _ignoreCase) == 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case StringOperationType.Greater: return string.Compare(input, _value, _ignoreCase) > 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case StringOperationType.GreaterEqual: return string.Compare(input, _value, _ignoreCase) >= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case StringOperationType.Less: return string.Compare(input, _value, _ignoreCase) < 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; case StringOperationType.LessEqual: return string.Compare(input, _value, _ignoreCase) <= 0 ? MatchResults.EmptySuccess : MatchResults.EmptyFailure; default: return null; } } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Internal/UrlMatches/StringOperation.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches { public enum StringOperationType { Equal, Greater, GreaterEqual, Less, LessEqual } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Microsoft.AspNetCore.Rewrite.csproj ================================================  ASP.NET Core basic middleware for rewriting URLs. Includes: * Support for custom URL rewrite rules * Support for running IIS URL Rewrite module rules * Support for running Apache mod_rewrite rules. netstandard2.0 $(NoWarn);CS1591 true aspnetcore;urlrewrite;mod_rewrite ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Properties/AssemblyInfo.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Rewrite.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Properties/Resources.Designer.cs ================================================ // namespace Microsoft.AspNetCore.Rewrite { using System.Globalization; using System.Reflection; using System.Resources; internal static class Resources { private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNetCore.Rewrite.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// Error adding a mod_rewrite rule. The change environment flag is not supported. /// internal static string Error_ChangeEnvironmentNotSupported { get { return GetString("Error_ChangeEnvironmentNotSupported"); } } /// /// Error adding a mod_rewrite rule. The change environment flag is not supported. /// internal static string FormatError_ChangeEnvironmentNotSupported() { return GetString("Error_ChangeEnvironmentNotSupported"); } /// /// Could not parse integer from value '{0}'. /// internal static string Error_CouldNotParseInteger { get { return GetString("Error_CouldNotParseInteger"); } } /// /// Could not parse integer from value '{0}'. /// internal static string FormatError_CouldNotParseInteger(object p0) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_CouldNotParseInteger"), p0); } /// /// Index out of range for backreference: '{0}' at string index: '{1}' /// internal static string Error_InputParserIndexOutOfRange { get { return GetString("Error_InputParserIndexOutOfRange"); } } /// /// Index out of range for backreference: '{0}' at string index: '{1}' /// internal static string FormatError_InputParserIndexOutOfRange(object p0, object p1) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserIndexOutOfRange"), p0, p1); } /// /// Cannot parse '{0}' to integer at string index: '{1}' /// internal static string Error_InputParserInvalidInteger { get { return GetString("Error_InputParserInvalidInteger"); } } /// /// Cannot parse '{0}' to integer at string index: '{1}' /// internal static string FormatError_InputParserInvalidInteger(object p0, object p1) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserInvalidInteger"), p0, p1); } /// /// Missing close brace for parameter at string index: '{0}' /// internal static string Error_InputParserMissingCloseBrace { get { return GetString("Error_InputParserMissingCloseBrace"); } } /// /// Missing close brace for parameter at string index: '{0}' /// internal static string FormatError_InputParserMissingCloseBrace(object p0) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserMissingCloseBrace"), p0); } /// /// Missing backreference for parameter at string index: '{0}' /// internal static string Error_InputParserNoBackreference { get { return GetString("Error_InputParserNoBackreference"); } } /// /// Missing backreference for parameter at string index: '{0}' /// internal static string FormatError_InputParserNoBackreference(object p0) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserNoBackreference"), p0); } /// /// Unrecognized parameter type: '{0}', terminated at string index: '{1}' /// internal static string Error_InputParserUnrecognizedParameter { get { return GetString("Error_InputParserUnrecognizedParameter"); } } /// /// Unrecognized parameter type: '{0}', terminated at string index: '{1}' /// internal static string FormatError_InputParserUnrecognizedParameter(object p0, object p1) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_InputParserUnrecognizedParameter"), p0, p1); } /// /// Syntax error for integers in comparison. /// internal static string Error_IntegerMatch_FormatExceptionMessage { get { return GetString("Error_IntegerMatch_FormatExceptionMessage"); } } /// /// Syntax error for integers in comparison. /// internal static string FormatError_IntegerMatch_FormatExceptionMessage() { return GetString("Error_IntegerMatch_FormatExceptionMessage"); } /// /// Error parsing the mod_rewrite rule. The cookie flag (CO) has an incorrect format '{0}'. /// internal static string Error_InvalidChangeCookieFlag { get { return GetString("Error_InvalidChangeCookieFlag"); } } /// /// Error parsing the mod_rewrite rule. The cookie flag (CO) has an incorrect format '{0}'. /// internal static string FormatError_InvalidChangeCookieFlag(object p0) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_InvalidChangeCookieFlag"), p0); } /// /// Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'. /// internal static string Error_ModRewriteParseError { get { return GetString("Error_ModRewriteParseError"); } } /// /// Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'. /// internal static string FormatError_ModRewriteParseError(object p0, object p1) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_ModRewriteParseError"), p0, p1); } /// /// Could not parse the mod_rewrite file. Line number '{0}'. /// internal static string Error_ModRewriteGeneralParseError { get { return GetString("Error_ModRewriteGeneralParseError"); } } /// /// Could not parse the mod_rewrite file. Line number '{0}'. /// internal static string FormatError_ModRewriteGeneralParseError(object p0) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_ModRewriteGeneralParseError"), p0); } /// /// Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'. /// internal static string Error_UrlRewriteParseError { get { return GetString("Error_UrlRewriteParseError"); } } /// /// Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'. /// internal static string FormatError_UrlRewriteParseError(object p0, object p1, object p2) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_UrlRewriteParseError"), p0, p1, p2); } /// /// Rules using the '{0}' server variable are not supported /// internal static string Error_UnsupportedServerVariable { get { return GetString("Error_UnsupportedServerVariable"); } } /// /// Rules using the '{0}' server variable are not supported /// internal static string FormatError_UnsupportedServerVariable(object p0) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_UnsupportedServerVariable"), p0); } private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); System.Diagnostics.Debug.Assert(value != null); if (formatterNames != null) { for (var i = 0; i < formatterNames.Length; i++) { value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); } } return value; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Error adding a mod_rewrite rule. The change environment flag is not supported. Could not parse integer from value '{0}'. Index out of range for backreference: '{0}' at string index: '{1}' Cannot parse '{0}' to integer at string index: '{1}' Missing close brace for parameter at string index: '{0}' Missing backreference for parameter at string index: '{0}' Unrecognized parameter type: '{0}', terminated at string index: '{1}' Syntax error for integers in comparison. Error parsing the mod_rewrite rule. The cookie flag (CO) has an incorrect format '{0}'. Could not parse the mod_rewrite file. Message: '{0}'. Line number '{1}'. Could not parse the mod_rewrite file. Line number '{0}'. Could not parse the UrlRewrite file. Message: '{0}'. Line number '{1}': '{2}'. Rules using the '{0}' server variable are not supported ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/RewriteBuilderExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for the /// public static class RewriteBuilderExtensions { /// /// Checks if a given Url matches rules and conditions, and modifies the HttpContext on match. /// /// The /// public static IApplicationBuilder UseRewriter(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMiddleware(); } /// /// Checks if a given Url matches rules and conditions, and modifies the HttpContext on match. /// /// The /// Options for rewrite. /// public static IApplicationBuilder UseRewriter(this IApplicationBuilder app, RewriteOptions options) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } // put middleware in pipeline return app.UseMiddleware(Options.Create(options)); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/RewriteContext.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Rewrite { /// /// A context object for /// public class RewriteContext { /// /// Gets and sets the /// public HttpContext HttpContext { get; set; } /// /// Gets and sets the File Provider for file and directory checks. /// public IFileProvider StaticFileProvider { get; set; } /// /// Gets and sets the logger /// public ILogger Logger { get; set; } /// /// A shared result that is set appropriately by each rule for the next action that /// should be taken. See /// public RuleResult Result { get; set; } internal StringBuilder Builder { get; set; } = new StringBuilder(64); } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/RewriteMiddleware.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Rewrite.Logging; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Rewrite { /// /// Represents a middleware that rewrites urls /// public class RewriteMiddleware { private readonly RequestDelegate _next; private readonly RewriteOptions _options; private readonly IFileProvider _fileProvider; private readonly ILogger _logger; /// /// Creates a new instance of /// /// The delegate representing the next middleware in the request pipeline. /// The Hosting Environment. /// The Logger Factory. /// The middleware options, containing the rules to apply. public RewriteMiddleware( RequestDelegate next, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, IOptions options) { if (next == null) { throw new ArgumentNullException(nameof(next)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } _next = next; _options = options.Value; _fileProvider = _options.StaticFileProvider ?? hostingEnvironment.WebRootFileProvider; _logger = loggerFactory.CreateLogger(); } /// /// Executes the middleware. /// /// The for the current request. /// A task that represents the execution of this middleware. public Task Invoke(HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var rewriteContext = new RewriteContext { HttpContext = context, StaticFileProvider = _fileProvider, Logger = _logger, Result = RuleResult.ContinueRules }; foreach (var rule in _options.Rules) { rule.ApplyRule(rewriteContext); switch (rewriteContext.Result) { case RuleResult.ContinueRules: _logger.RewriteMiddlewareRequestContinueResults(context.Request.GetEncodedUrl()); break; case RuleResult.EndResponse: _logger.RewriteMiddlewareRequestResponseComplete( context.Response.Headers[HeaderNames.Location], context.Response.StatusCode); return Task.CompletedTask; case RuleResult.SkipRemainingRules: _logger.RewriteMiddlewareRequestStopRules(context.Request.GetEncodedUrl()); return _next(context); default: throw new ArgumentOutOfRangeException($"Invalid rule termination {rewriteContext.Result}"); } } return _next(context); } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/RewriteOptions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using Microsoft.Extensions.FileProviders; using Microsoft.AspNetCore.Hosting; namespace Microsoft.AspNetCore.Rewrite { /// /// Options for the /// public class RewriteOptions { /// /// A list of that will be applied in order upon a request. /// public IList Rules { get; } = new List(); /// /// Gets and sets the File Provider for file and directory checks. Defaults to /// public IFileProvider StaticFileProvider { get; set; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/RewriteOptionsExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal; namespace Microsoft.AspNetCore.Rewrite { /// /// The builder to a list of rules for and /// public static class RewriteOptionsExtensions { /// /// Adds a rule to the current rules. /// /// The . /// A rule to be added to the current rules. /// The Rewrite options. public static RewriteOptions Add(this RewriteOptions options, IRule rule) { options.Rules.Add(rule); return options; } /// /// Adds a rule to the current rules. /// /// The . /// A Func that checks and applies the rule. /// public static RewriteOptions Add(this RewriteOptions options, Action applyRule) { options.Rules.Add(new DelegateRule(applyRule)); return options; } /// /// Adds a rule that rewrites the path if the regex matches the HttpContext's PathString. /// /// The . /// The regex string to compare with. /// If the regex matches, what to replace the uri with. /// If the regex matches, conditionally stop processing other rules. /// The Rewrite options. public static RewriteOptions AddRewrite(this RewriteOptions options, string regex, string replacement, bool skipRemainingRules) { options.Rules.Add(new RewriteRule(regex, replacement, skipRemainingRules)); return options; } /// /// Redirect the request if the regex matches the HttpContext's PathString /// /// The . /// The regex string to compare with. /// If the regex matches, what to replace the uri with. /// The Rewrite options. public static RewriteOptions AddRedirect(this RewriteOptions options, string regex, string replacement) { return AddRedirect(options, regex, replacement, statusCode: StatusCodes.Status302Found); } /// /// Redirect the request if the regex matches the HttpContext's PathString /// /// The . /// The regex string to compare with. /// If the regex matches, what to replace the uri with. /// The status code to add to the response. /// The Rewrite options. public static RewriteOptions AddRedirect(this RewriteOptions options, string regex, string replacement, int statusCode) { options.Rules.Add(new RedirectRule(regex, replacement, statusCode)); return options; } /// /// Redirect a request to https if the incoming request is http, with returning a 301 /// status code for permanently redirected. /// /// The . /// public static RewriteOptions AddRedirectToHttpsPermanent(this RewriteOptions options) { return AddRedirectToHttps(options, statusCode: StatusCodes.Status301MovedPermanently, sslPort: null); } /// /// Redirect a request to https if the incoming request is http /// /// The . public static RewriteOptions AddRedirectToHttps(this RewriteOptions options) { return AddRedirectToHttps(options, statusCode: StatusCodes.Status302Found, sslPort: null); } /// /// Redirect a request to https if the incoming request is http /// /// The . /// The status code to add to the response. public static RewriteOptions AddRedirectToHttps(this RewriteOptions options, int statusCode) { return AddRedirectToHttps(options, statusCode, sslPort: null); } /// /// Redirect a request to https if the incoming request is http /// /// The . /// The status code to add to the response. /// The SSL port to add to the response. public static RewriteOptions AddRedirectToHttps(this RewriteOptions options, int statusCode, int? sslPort) { options.Rules.Add(new RedirectToHttpsRule { StatusCode = statusCode, SSLPort = sslPort }); return options; } /// /// Permanently redirects the request to the www subdomain if the request is non-www. /// /// The . /// public static RewriteOptions AddRedirectToWwwPermanent(this RewriteOptions options) { return AddRedirectToWww(options, statusCode: StatusCodes.Status308PermanentRedirect); } /// /// Redirect the request to the www subdomain if the incoming request is non-www. /// /// The . public static RewriteOptions AddRedirectToWww(this RewriteOptions options) { return AddRedirectToWww(options, statusCode: StatusCodes.Status307TemporaryRedirect); } /// /// Redirect the request to the www subdomain if the incoming request is non-www. /// /// The . /// The status code to add to the response. public static RewriteOptions AddRedirectToWww(this RewriteOptions options, int statusCode) { options.Rules.Add(new RedirectToWwwRule(statusCode)); return options; } } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/RuleResult.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Rewrite { /// /// An enum representing the result of a rule. /// public enum RuleResult { /// /// Default value, continue applying rules. /// ContinueRules, /// /// The rule ended the request by providing a response. /// EndResponse, /// /// Stop applying rules and send context to the next middleware /// SkipRemainingRules } } ================================================ FILE: src/Microsoft.AspNetCore.Rewrite/baseline.netcore.json ================================================ { "AssemblyIdentity": "Microsoft.AspNetCore.Rewrite, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Builder.RewriteBuilderExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "UseRewriter", "Parameters": [ { "Name": "app", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "UseRewriter", "Parameters": [ { "Name": "app", "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" }, { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" } ], "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Rewrite.ApacheModRewriteOptionsExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "AddApacheModRewrite", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "fileProvider", "Type": "Microsoft.Extensions.FileProviders.IFileProvider" }, { "Name": "filePath", "Type": "System.String" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddApacheModRewrite", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "reader", "Type": "System.IO.TextReader" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Rewrite.IISUrlRewriteOptionsExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "AddIISUrlRewrite", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "fileProvider", "Type": "Microsoft.Extensions.FileProviders.IFileProvider" }, { "Name": "filePath", "Type": "System.String" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddIISUrlRewrite", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "reader", "Type": "System.IO.TextReader" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Rewrite.IRule", "Visibility": "Public", "Kind": "Interface", "Abstract": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "ApplyRule", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Rewrite.RewriteContext" } ], "ReturnType": "System.Void", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Rewrite.RewriteContext", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_HttpContext", "Parameters": [], "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_HttpContext", "Parameters": [ { "Name": "value", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_StaticFileProvider", "Parameters": [], "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_StaticFileProvider", "Parameters": [ { "Name": "value", "Type": "Microsoft.Extensions.FileProviders.IFileProvider" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_Logger", "Parameters": [], "ReturnType": "Microsoft.Extensions.Logging.ILogger", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_Logger", "Parameters": [ { "Name": "value", "Type": "Microsoft.Extensions.Logging.ILogger" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_Result", "Parameters": [], "ReturnType": "Microsoft.AspNetCore.Rewrite.RuleResult", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_Result", "Parameters": [ { "Name": "value", "Type": "Microsoft.AspNetCore.Rewrite.RuleResult" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Rewrite.RewriteMiddleware", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Invoke", "Parameters": [ { "Name": "context", "Type": "Microsoft.AspNetCore.Http.HttpContext" } ], "ReturnType": "System.Threading.Tasks.Task", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { "Name": "next", "Type": "Microsoft.AspNetCore.Http.RequestDelegate" }, { "Name": "hostingEnvironment", "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" }, { "Name": "loggerFactory", "Type": "Microsoft.Extensions.Logging.ILoggerFactory" }, { "Name": "options", "Type": "Microsoft.Extensions.Options.IOptions" } ], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "get_Rules", "Parameters": [], "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "get_StaticFileProvider", "Parameters": [], "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "set_StaticFileProvider", "Parameters": [ { "Name": "value", "Type": "Microsoft.Extensions.FileProviders.IFileProvider" } ], "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Rewrite.RewriteOptionsExtensions", "Visibility": "Public", "Kind": "Class", "Abstract": true, "Static": true, "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Method", "Name": "Add", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "rule", "Type": "Microsoft.AspNetCore.Rewrite.IRule" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "Add", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "applyRule", "Type": "System.Action" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRewrite", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "regex", "Type": "System.String" }, { "Name": "replacement", "Type": "System.String" }, { "Name": "skipRemainingRules", "Type": "System.Boolean" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirect", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "regex", "Type": "System.String" }, { "Name": "replacement", "Type": "System.String" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirect", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "regex", "Type": "System.String" }, { "Name": "replacement", "Type": "System.String" }, { "Name": "statusCode", "Type": "System.Int32" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirectToHttpsPermanent", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirectToHttps", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirectToHttps", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "statusCode", "Type": "System.Int32" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirectToHttps", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "statusCode", "Type": "System.Int32" }, { "Name": "sslPort", "Type": "System.Nullable" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirectToWwwPermanent", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirectToWww", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", "Name": "AddRedirectToWww", "Parameters": [ { "Name": "options", "Type": "Microsoft.AspNetCore.Rewrite.RewriteOptions" }, { "Name": "statusCode", "Type": "System.Int32" } ], "ReturnType": "Microsoft.AspNetCore.Rewrite.RewriteOptions", "Static": true, "Extension": true, "Visibility": "Public", "GenericParameter": [] } ], "GenericParameters": [] }, { "Name": "Microsoft.AspNetCore.Rewrite.RuleResult", "Visibility": "Public", "Kind": "Enumeration", "Sealed": true, "ImplementedInterfaces": [], "Members": [ { "Kind": "Field", "Name": "ContinueRules", "Parameters": [], "GenericParameter": [], "Literal": "0" }, { "Kind": "Field", "Name": "EndResponse", "Parameters": [], "GenericParameter": [], "Literal": "1" }, { "Kind": "Field", "Name": "SkipRemainingRules", "Parameters": [], "GenericParameter": [], "Literal": "2" } ], "GenericParameters": [] } ] } ================================================ FILE: test/Directory.Build.props ================================================ netcoreapp2.2 $(DeveloperBuildTestTfms) $(StandardTestTfms);net461 ================================================ FILE: test/Microsoft.AspNetCore.Buffering.Tests/Microsoft.AspNetCore.Buffering.Tests.csproj ================================================  $(StandardTestTfms) ================================================ FILE: test/Microsoft.AspNetCore.Buffering.Tests/ResponseBufferingMiddlewareTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.TestHost; using Xunit; namespace Microsoft.AspNetCore.Buffering.Tests { public class ResponseBufferingMiddlewareTests { [Fact] public async Task BufferResponse_SetsContentLength() { var builder = new WebHostBuilder() .Configure(app => { app.UseResponseBuffering(); app.Run(async context => { Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); await context.Response.WriteAsync("Hello World"); Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); response.EnsureSuccessStatusCode(); Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); // Set automatically by buffer IEnumerable values; Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values)); Assert.Equal("11", values.FirstOrDefault()); } [Fact] public async Task BufferResponseWithManualContentLength_NotReplaced() { var builder = new WebHostBuilder() .Configure(app => { app.UseResponseBuffering(); app.Run(async context => { context.Response.ContentLength = 12; Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); await context.Response.WriteAsync("Hello World"); Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); response.EnsureSuccessStatusCode(); Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); IEnumerable values; Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values)); Assert.Equal("12", values.FirstOrDefault()); } [Fact] public async Task Seek_AllowsResttingBuffer() { var builder = new WebHostBuilder() .Configure(app => { app.UseResponseBuffering(); app.Run(async context => { var body = context.Response.Body; Assert.False(context.Response.HasStarted); Assert.True(body.CanSeek); Assert.Equal(0, body.Position); Assert.Equal(0, body.Length); await context.Response.WriteAsync("Hello World"); Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); Assert.Equal(11, body.Position); Assert.Equal(11, body.Length); Assert.Throws(() => body.Seek(1, SeekOrigin.Begin)); Assert.Throws(() => body.Seek(0, SeekOrigin.Current)); Assert.Throws(() => body.Seek(0, SeekOrigin.End)); Assert.Equal(0, body.Seek(0, SeekOrigin.Begin)); Assert.Equal(0, body.Position); Assert.Equal(0, body.Length); await context.Response.WriteAsync("12345"); Assert.Equal(5, body.Position); Assert.Equal(5, body.Length); }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); response.EnsureSuccessStatusCode(); Assert.Equal("12345", await response.Content.ReadAsStringAsync()); // Set automatically by buffer IEnumerable values; Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values)); Assert.Equal("5", values.FirstOrDefault()); } [Fact] public async Task SetPosition_AllowsResttingBuffer() { var builder = new WebHostBuilder() .Configure(app => { app.UseResponseBuffering(); app.Run(async context => { var body = context.Response.Body; Assert.False(context.Response.HasStarted); Assert.True(body.CanSeek); Assert.Equal(0, body.Position); Assert.Equal(0, body.Length); await context.Response.WriteAsync("Hello World"); Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); Assert.Equal(11, body.Position); Assert.Equal(11, body.Length); Assert.Throws(() => body.Position = 1); body.Position = 0; Assert.Equal(0, body.Position); Assert.Equal(0, body.Length); await context.Response.WriteAsync("12345"); Assert.Equal(5, body.Position); Assert.Equal(5, body.Length); }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); response.EnsureSuccessStatusCode(); Assert.Equal("12345", await response.Content.ReadAsStringAsync()); // Set automatically by buffer IEnumerable values; Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values)); Assert.Equal("5", values.FirstOrDefault()); } [Fact] public async Task SetLength_AllowsResttingBuffer() { var builder = new WebHostBuilder() .Configure(app => { app.UseResponseBuffering(); app.Run(async context => { var body = context.Response.Body; Assert.False(context.Response.HasStarted); Assert.True(body.CanSeek); Assert.Equal(0, body.Position); Assert.Equal(0, body.Length); await context.Response.WriteAsync("Hello World"); Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); Assert.Equal(11, body.Position); Assert.Equal(11, body.Length); Assert.Throws(() => body.SetLength(1)); body.SetLength(0); Assert.Equal(0, body.Position); Assert.Equal(0, body.Length); await context.Response.WriteAsync("12345"); Assert.Equal(5, body.Position); Assert.Equal(5, body.Length); }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); response.EnsureSuccessStatusCode(); Assert.Equal("12345", await response.Content.ReadAsStringAsync()); // Set automatically by buffer IEnumerable values; Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values)); Assert.Equal("5", values.FirstOrDefault()); } [Fact] public async Task DisableBufferingViaFeature() { var builder = new WebHostBuilder() .Configure(app => { app.UseResponseBuffering(); app.Run(async context => { Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); var bufferingFeature = context.Features.Get(); Assert.NotNull(bufferingFeature); bufferingFeature.DisableResponseBuffering(); Assert.False(context.Response.HasStarted); Assert.False(context.Response.Body.CanSeek); await context.Response.WriteAsync("Hello World"); Assert.True(context.Response.HasStarted); Assert.False(context.Response.Body.CanSeek); }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); response.EnsureSuccessStatusCode(); Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); IEnumerable values; Assert.False(response.Content.Headers.TryGetValues("Content-Length", out values)); } [Fact] public async Task DisableBufferingViaFeatureAfterFirstWrite_Flushes() { var builder = new WebHostBuilder() .Configure(app => { app.UseResponseBuffering(); app.Run(async context => { Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); await context.Response.WriteAsync("Hello"); Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); var bufferingFeature = context.Features.Get(); Assert.NotNull(bufferingFeature); bufferingFeature.DisableResponseBuffering(); Assert.True(context.Response.HasStarted); Assert.False(context.Response.Body.CanSeek); await context.Response.WriteAsync(" World"); Assert.True(context.Response.HasStarted); Assert.False(context.Response.Body.CanSeek); }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); response.EnsureSuccessStatusCode(); Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); IEnumerable values; Assert.False(response.Content.Headers.TryGetValues("Content-Length", out values)); } [Fact] public async Task FlushDisablesBuffering() { var builder = new WebHostBuilder() .Configure(app => { app.UseResponseBuffering(); app.Run(async context => { Assert.False(context.Response.HasStarted); Assert.True(context.Response.Body.CanSeek); context.Response.Body.Flush(); Assert.True(context.Response.HasStarted); Assert.False(context.Response.Body.CanSeek); await context.Response.WriteAsync("Hello World"); Assert.True(context.Response.HasStarted); Assert.False(context.Response.Body.CanSeek); }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); response.EnsureSuccessStatusCode(); Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); IEnumerable values; Assert.False(response.Content.Headers.TryGetValues("Content-Length", out values)); } } } ================================================ FILE: test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.HostFiltering { public class HostFilteringMiddlewareTests { [Fact] public async Task MissingConfigThrows() { var builder = new WebHostBuilder() .Configure(app => { app.UseHostFiltering(); }); await Assert.ThrowsAsync(() => new TestServer(builder).SendAsync(_ => { })); } [Theory] [InlineData(true, 200)] [InlineData(false, 400)] public async Task AllowsMissingHost(bool allowed, int status) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddHostFiltering(options => { options.AllowEmptyHosts = allowed; options.AllowedHosts.Add("Localhost"); }); }) .Configure(app => { app.Use((ctx, next) => { ctx.Request.Headers.Remove(HeaderNames.Host); return next(); }); app.UseHostFiltering(); app.Run(c => { Assert.False(c.Request.Headers.TryGetValue(HeaderNames.Host, out var host)); return Task.CompletedTask; }); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("/"); Assert.Equal(status, (int)response.StatusCode); } [Theory] [InlineData(true, 200)] [InlineData(false, 400)] public async Task AllowsEmptyHost(bool allowed, int status) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddHostFiltering(options => { options.AllowEmptyHosts = allowed; options.AllowedHosts.Add("Localhost"); }); }) .Configure(app => { app.Use((ctx, next) => { ctx.Request.Headers[HeaderNames.Host] = " "; return next(); }); app.UseHostFiltering(); app.Run(c => { Assert.True(c.Request.Headers.TryGetValue(HeaderNames.Host, out var host)); Assert.True(StringValues.Equals(" ", host)); return Task.CompletedTask; }); app.Run(c => Task.CompletedTask); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("/"); Assert.Equal(status, (int)response.StatusCode); } [Theory] [InlineData("localHost", "localhost")] [InlineData("localHost", "*")] // Any - Used by HttpSys [InlineData("localHost", "[::]")] // IPv6 Any - This is what Kestrel reports when binding to * [InlineData("localHost", "0.0.0.0")] // IPv4 Any [InlineData("localhost:9090", "example.com;localHost")] [InlineData("example.com:443", "example.com;localhost")] [InlineData("localHost:80", "localhost;")] [InlineData("foo.eXample.com:443", "*.exampLe.com")] [InlineData("f.eXample.com:443", "*.exampLe.com")] [InlineData("127.0.0.1", "127.0.0.1")] [InlineData("127.0.0.1:443", "127.0.0.1")] [InlineData("xn--c1yn36f:443", "xn--c1yn36f")] [InlineData("xn--c1yn36f:443", "點看")] [InlineData("[::ABC]", "[::aBc]")] [InlineData("[::1]:80", "[::1]")] public async Task AllowsSpecifiedHost(string host, string allowedHost) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddHostFiltering(options => { options.AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); }); }) .Configure(app => { app.Use((ctx, next) => { // TestHost's ClientHandler doesn't let you set the host header, only the host in the URI // and that would over-normalize some of our test conditions like casing. ctx.Request.Headers[HeaderNames.Host] = host; return next(); }); app.UseHostFiltering(); app.Run(c => Task.CompletedTask); }); var server = new TestServer(builder); var response = await server.CreateRequest("/").GetAsync(); Assert.Equal(200, (int)response.StatusCode); } [Theory] [InlineData("example.com", "localhost")] [InlineData("localhost:9090", "example.com;")] [InlineData(";", "example.com;localhost")] [InlineData(";:80", "example.com;localhost")] [InlineData(":80", "localhost")] [InlineData(":", "localhost")] [InlineData("example.com:443", "*.example.com")] [InlineData(".example.com:443", "*.example.com")] [InlineData("foo.com:443", "*.example.com")] [InlineData("foo.example.com.bar:443", "*.example.com")] [InlineData(".com:443", "*.com")] // Unicode in the host shouldn't be allowed without punycode anyways. This match fails because the middleware converts // its input to punycode. [InlineData("點看", "點看")] [InlineData("[::1", "[::1]")] [InlineData("[::1:80", "[::1]")] public async Task RejectsMismatchedHosts(string host, string allowedHost) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddHostFiltering(options => { options.AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); }); }) .Configure(app => { app.Use((ctx, next) => { // TestHost's ClientHandler doesn't let you set the host header, only the host in the URI // and that would reject some of our test conditions. ctx.Request.Headers[HeaderNames.Host] = host; return next(); }); app.UseHostFiltering(); app.Run(c => throw new NotImplementedException("App")); }); var server = new TestServer(builder); var response = await server.CreateRequest("/").GetAsync(); Assert.Equal(400, (int)response.StatusCode); } [Fact] public async Task SupportsDynamicOptionsReload() { var config = new ConfigurationBuilder().Add(new ReloadableMemorySource()).Build(); config["AllowedHosts"] = "localhost"; var currentHost = "otherHost"; var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddHostFiltering(options => { options.AllowedHosts = new[] { config["AllowedHosts"] }; }); services.AddSingleton>(new ConfigurationChangeTokenSource(config)); }) .Configure(app => { app.Use((ctx, next) => { ctx.Request.Headers[HeaderNames.Host] = currentHost; return next(); }); app.UseHostFiltering(); app.Run(c => Task.CompletedTask); }); var server = new TestServer(builder); var response = await server.CreateRequest("/").GetAsync(); Assert.Equal(400, (int)response.StatusCode); config["AllowedHosts"] = "otherHost"; response = await server.CreateRequest("/").GetAsync(); Assert.Equal(200, (int)response.StatusCode); } private class ReloadableMemorySource : IConfigurationSource { public IConfigurationProvider Build(IConfigurationBuilder builder) { return new ReloadableMemoryProvider(); } } internal class ReloadableMemoryProvider : ConfigurationProvider { public override void Set(string key, string value) { base.Set(key, value); OnReload(); } } } } ================================================ FILE: test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj ================================================  $(StandardTestTfms) ================================================ FILE: test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.HttpOverrides { public class ForwardedHeadersMiddlewareTests { [Fact] public async Task XForwardedForDefaultSettingsChangeRemoteIpAndPort() { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = "11.111.111.11:9090"; }); Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); Assert.Equal(9090, context.Connection.RemotePort); // No Original set if RemoteIpAddress started null. Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); // Should have been consumed and removed Assert.False(context.Request.Headers.ContainsKey("X-Forwarded-For")); } [Theory] [InlineData(1, "11.111.111.11.12345", "10.0.0.1", 99)] // Invalid public async Task XForwardedForFirstValueIsInvalid(int limit, string header, string expectedIp, int expectedPort) { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, ForwardLimit = limit, }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = header; c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); c.Connection.RemotePort = 99; }); Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(expectedPort, context.Connection.RemotePort); Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-For")); Assert.Equal(header, context.Request.Headers["X-Forwarded-For"]); } [Theory] [InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "", false)] [InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "", true)] [InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "", false)] [InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "", true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456", false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456", true)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", false)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", true)] [InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", false)] [InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "", true)] [InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12.23456", false)] // Invalid 2nd value [InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12.23456", true)] // Invalid 2nd value [InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "13.113.113.13:34567,12.112.112.12.23456", false)] // Invalid 2nd value [InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "11.111.111.11", 12345, "13.113.113.13:34567,12.112.112.12.23456", true)] // Invalid 2nd value [InlineData(2, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "13.113.113.13:34567", false)] [InlineData(2, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "13.113.113.13:34567", true)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "", false)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "", true)] public async Task XForwardedForForwardLimit(int limit, string header, string expectedIp, int expectedPort, string remainingHeader, bool requireSymmetry) { var builder = new WebHostBuilder() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = requireSymmetry, ForwardLimit = limit, }; options.KnownProxies.Clear(); options.KnownNetworks.Clear(); app.UseForwardedHeaders(options); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = header; c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); c.Connection.RemotePort = 99; }); Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(expectedPort, context.Connection.RemotePort); Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-For"].ToString()); } [Theory] [InlineData("11.111.111.11", false)] [InlineData("127.0.0.1", true)] [InlineData("127.0.1.1", true)] [InlineData("::1", true)] [InlineData("::", false)] public async Task XForwardedForLoopback(string originalIp, bool expectForwarded) { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = "10.0.0.1:1234"; c.Connection.RemoteIpAddress = IPAddress.Parse(originalIp); c.Connection.RemotePort = 99; }); if (expectForwarded) { Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString()); Assert.Equal(1234, context.Connection.RemotePort); Assert.True(context.Request.Headers.ContainsKey("X-Original-For")); Assert.Equal(new IPEndPoint(IPAddress.Parse(originalIp), 99).ToString(), context.Request.Headers["X-Original-For"]); } else { Assert.Equal(originalIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(99, context.Connection.RemotePort); Assert.False(context.Request.Headers.ContainsKey("X-Original-For")); } } [Theory] [InlineData(1, "11.111.111.11:12345", "20.0.0.1", "10.0.0.1", 99, false)] [InlineData(1, "11.111.111.11:12345", "20.0.0.1", "10.0.0.1", 99, true)] [InlineData(1, "", "10.0.0.1", "10.0.0.1", 99, false)] [InlineData(1, "", "10.0.0.1", "10.0.0.1", 99, true)] [InlineData(1, "11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345, false)] [InlineData(1, "11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345, true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345, false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345, true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "11.111.111.11", 12345, false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "11.111.111.11", 12345, true)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "12.112.112.12", 23456, false)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "12.112.112.12", 23456, true)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345, false)] [InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345, true)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456, false)] [InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456, true)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "13.113.113.13", 34567, false)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "13.113.113.13", 34567, true)] [InlineData(3, "13.113.113.13:34567, 12.112.112.12;23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345, false)] // Invalid 2nd IP [InlineData(3, "13.113.113.13:34567, 12.112.112.12;23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345, true)] // Invalid 2nd IP [InlineData(3, "13.113.113.13;34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456, false)] // Invalid 3rd IP [InlineData(3, "13.113.113.13;34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456, true)] // Invalid 3rd IP public async Task XForwardedForForwardKnownIps(int limit, string header, string knownIPs, string expectedIp, int expectedPort, bool requireSymmetry) { var builder = new WebHostBuilder() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = requireSymmetry, ForwardLimit = limit, }; foreach (var ip in knownIPs.Split(',').Select(text => IPAddress.Parse(text))) { options.KnownProxies.Add(ip); } app.UseForwardedHeaders(options); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = header; c.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1"); c.Connection.RemotePort = 99; }); Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString()); Assert.Equal(expectedPort, context.Connection.RemotePort); } [Fact] public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp() { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = "BAD-IP"; }); Assert.Null(context.Connection.RemoteIpAddress); } [Fact] public async Task XForwardedHostOverrideChangesRequestHost() { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Host"] = "testhost"; }); Assert.Equal("testhost", context.Request.Host.ToString()); } public static TheoryData HostHeaderData { get { return new TheoryData() { "z", "1", "y:1", "1:1", "[ABCdef]", "[abcDEF]:0", "[abcdef:127.2355.1246.114]:0", "[::1]:80", "127.0.0.1:80", "900.900.900.900:9523547852", "foo", "foo:234", "foo.bar.baz", "foo.BAR.baz:46245", "foo.ba-ar.baz:46245", "-foo:1234", "xn--c1yn36f:134", "-", "_", "~", "!", "$", "'", "(", ")", }; } } [Theory] [MemberData(nameof(HostHeaderData))] public async Task XForwardedHostAllowsValidCharacters(string host) { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost }); app.Run(context => { Assert.Equal(host, context.Request.Host.ToString()); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Host"] = host; }); Assert.True(assertsExecuted); } public static TheoryData HostHeaderInvalidData { get { // see https://tools.ietf.org/html/rfc7230#section-5.4 var data = new TheoryData() { "", // Empty "[]", // Too short "[::]", // Too short "[ghijkl]", // Non-hex "[afd:adf:123", // Incomplete "[afd:adf]123", // Missing : "[afd:adf]:", // Missing port digits "[afd adf]", // Space "[ad-314]", // dash ":1234", // Missing host "a:b:c", // Missing [] "::1", // Missing [] "::", // Missing everything "abcd:1abcd", // Letters in port "abcd:1.2", // Dot in port "1.2.3.4:", // Missing port digits "1.2 .4", // Space }; // These aren't allowed anywhere in the host header var invalid = "\"#%*+/;<=>?@[]\\^`{}|"; foreach (var ch in invalid) { data.Add(ch.ToString()); } invalid = "!\"#$%&'()*+,/;<=>?@[]\\^_`{}|~-"; foreach (var ch in invalid) { data.Add("[abd" + ch + "]:1234"); } invalid = "!\"#$%&'()*+/;<=>?@[]\\^_`{}|~:abcABC-."; foreach (var ch in invalid) { data.Add("a.b.c:" + ch); } return data; } } [Theory] [MemberData(nameof(HostHeaderInvalidData))] public async Task XForwardedHostFailsForInvalidCharacters(string host) { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost }); app.Run(context => { Assert.NotEqual(host, context.Request.Host.Value); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Host"] = host; }); Assert.True(assertsExecuted); } [Theory] [InlineData("localHost", "localhost")] [InlineData("localHost", "*")] // Any - Used by HttpSys [InlineData("localHost", "[::]")] // IPv6 Any - This is what Kestrel reports when binding to * [InlineData("localHost", "0.0.0.0")] // IPv4 Any [InlineData("localhost:9090", "example.com;localHost")] [InlineData("example.com:443", "example.com;localhost")] [InlineData("localHost:80", "localhost;")] [InlineData("foo.eXample.com:443", "*.exampLe.com")] [InlineData("f.eXample.com:443", "*.exampLe.com")] [InlineData("127.0.0.1", "127.0.0.1")] [InlineData("127.0.0.1:443", "127.0.0.1")] [InlineData("xn--c1yn36f:443", "xn--c1yn36f")] [InlineData("xn--c1yn36f:443", "點看")] [InlineData("[::ABC]", "[::aBc]")] [InlineData("[::1]:80", "[::1]")] public async Task XForwardedHostAllowsSpecifiedHost(string host, string allowedHost) { bool assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost, AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) }); app.Run(context => { Assert.Equal(host, context.Request.Headers[HeaderNames.Host]); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); var response = await server.SendAsync(ctx => { ctx.Request.Headers["X-forwarded-Host"] = host; }); Assert.True(assertsExecuted); } [Theory] [InlineData("example.com", "localhost")] [InlineData("localhost:9090", "example.com;")] [InlineData(";", "example.com;localhost")] [InlineData(";:80", "example.com;localhost")] [InlineData(":80", "localhost")] [InlineData(":", "localhost")] [InlineData("example.com:443", "*.example.com")] [InlineData(".example.com:443", "*.example.com")] [InlineData("foo.com:443", "*.example.com")] [InlineData("foo.example.com.bar:443", "*.example.com")] [InlineData(".com:443", "*.com")] // Unicode in the host shouldn't be allowed without punycode anyways. This match fails because the middleware converts // its input to punycode. [InlineData("點看", "點看")] [InlineData("[::1", "[::1]")] [InlineData("[::1:80", "[::1]")] public async Task XForwardedHostFailsMismatchedHosts(string host, string allowedHost) { bool assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost, AllowedHosts = new[] { allowedHost } }); app.Run(context => { Assert.NotEqual(host, context.Request.Headers[HeaderNames.Host]); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); var response = await server.SendAsync(ctx => { ctx.Request.Headers["X-forwarded-Host"] = host; }); Assert.True(assertsExecuted); } [Fact] public async Task XForwardedHostStopsAtFirstUnspecifiedHost() { bool assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost, ForwardLimit = 10, AllowedHosts = new[] { "bar.com", "*.foo.com" } }); app.Run(context => { Assert.Equal("bar.foo.com:432", context.Request.Headers[HeaderNames.Host]); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); var response = await server.SendAsync(ctx => { ctx.Request.Headers["X-forwarded-Host"] = "stuff:523, bar.foo.com:432, bar.com:80"; }); Assert.True(assertsExecuted); } [Theory] [InlineData(0, "h1", "http")] [InlineData(1, "", "http")] [InlineData(1, "h1", "h1")] [InlineData(3, "h1", "h1")] [InlineData(1, "h2, h1", "h1")] [InlineData(2, "h2, h1", "h2")] [InlineData(10, "h3, h2, h1", "h3")] public async Task XForwardedProtoOverrideChangesRequestProtocol(int limit, string header, string expected) { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto, ForwardLimit = limit, }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = header; }); Assert.Equal(expected, context.Request.Scheme); } public static TheoryData ProtoHeaderData { get { // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) return new TheoryData() { "z", "Z", "1", "y+", "1-", "a.", }; } } [Theory] [MemberData(nameof(ProtoHeaderData))] public async Task XForwardedProtoAcceptsValidProtocols(string scheme) { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto }); app.Run(context => { Assert.Equal(scheme, context.Request.Scheme); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = scheme; }); Assert.True(assertsExecuted); } public static TheoryData ProtoHeaderInvalidData { get { // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) var data = new TheoryData() { "a b", // Space }; // These aren't allowed anywhere in the scheme header var invalid = "!\"#$%&'()*/:;<=>?@[]\\^_`{}|~"; foreach (var ch in invalid) { data.Add(ch.ToString()); } return data; } } [Theory] [MemberData(nameof(ProtoHeaderInvalidData))] public async Task XForwardedProtoRejectsInvalidProtocols(string scheme) { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto, }); app.Run(context => { Assert.Equal("http", context.Request.Scheme); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = scheme; }); Assert.True(assertsExecuted); } [Theory] [InlineData(0, "h1", "::1", "http")] [InlineData(1, "", "::1", "http")] [InlineData(1, "h1", "::1", "h1")] [InlineData(3, "h1", "::1", "h1")] [InlineData(3, "h2, h1", "::1", "http")] [InlineData(5, "h2, h1", "::1, ::1", "h2")] [InlineData(10, "h3, h2, h1", "::1, ::1, ::1", "h3")] [InlineData(10, "h3, h2, h1", "::1, badip, ::1", "h1")] public async Task XForwardedProtoOverrideLimitedByXForwardedForCount(int limit, string protoHeader, string forHeader, string expected) { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = true, ForwardLimit = limit, }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = protoHeader; c.Request.Headers["X-Forwarded-For"] = forHeader; }); Assert.Equal(expected, context.Request.Scheme); } [Theory] [InlineData(0, "h1", "::1", "http")] [InlineData(1, "", "::1", "http")] [InlineData(1, "h1", "", "h1")] [InlineData(1, "h1", "::1", "h1")] [InlineData(3, "h1", "::1", "h1")] [InlineData(3, "h1", "::1, ::1", "h1")] [InlineData(3, "h2, h1", "::1", "h2")] [InlineData(5, "h2, h1", "::1, ::1", "h2")] [InlineData(10, "h3, h2, h1", "::1, ::1, ::1", "h3")] [InlineData(10, "h3, h2, h1", "::1, badip, ::1", "h1")] public async Task XForwardedProtoOverrideCanBeIndependentOfXForwardedForCount(int limit, string protoHeader, string forHeader, string expected) { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = false, ForwardLimit = limit, }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = protoHeader; c.Request.Headers["X-Forwarded-For"] = forHeader; }); Assert.Equal(expected, context.Request.Scheme); } [Theory] [InlineData("", "", "::1", false, "http")] [InlineData("h1", "", "::1", false, "http")] [InlineData("h1", "F::", "::1", false, "h1")] [InlineData("h1", "F::", "E::", false, "h1")] [InlineData("", "", "::1", true, "http")] [InlineData("h1", "", "::1", true, "http")] [InlineData("h1", "F::", "::1", true, "h1")] [InlineData("h1", "", "F::", true, "http")] [InlineData("h1", "E::", "F::", true, "http")] [InlineData("h2, h1", "", "::1", true, "http")] [InlineData("h2, h1", "F::, D::", "::1", true, "h1")] [InlineData("h2, h1", "E::, D::", "F::", true, "http")] public async Task XForwardedProtoOverrideLimitedByLoopback(string protoHeader, string forHeader, string remoteIp, bool loopback, string expected) { var builder = new WebHostBuilder() .Configure(app => { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor, RequireHeaderSymmetry = true, ForwardLimit = 5, }; if (!loopback) { options.KnownNetworks.Clear(); options.KnownProxies.Clear(); } app.UseForwardedHeaders(options); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = protoHeader; c.Request.Headers["X-Forwarded-For"] = forHeader; c.Connection.RemoteIpAddress = IPAddress.Parse(remoteIp); }); Assert.Equal(expected, context.Request.Scheme); } [Fact] public void AllForwardsDisabledByDefault() { var options = new ForwardedHeadersOptions(); Assert.True(options.ForwardedHeaders == ForwardedHeaders.None); Assert.Equal(1, options.ForwardLimit); Assert.Single(options.KnownNetworks); Assert.Single(options.KnownProxies); } [Fact] public async Task AllForwardsEnabledChangeRequestRemoteIpHostandProtocol() { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = "Protocol"; c.Request.Headers["X-Forwarded-For"] = "11.111.111.11"; c.Request.Headers["X-Forwarded-Host"] = "testhost"; }); Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); Assert.Equal("testhost", context.Request.Host.ToString()); Assert.Equal("Protocol", context.Request.Scheme); } [Fact] public async Task AllOptionsDisabledRequestDoesntChange() { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.None }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = "Protocol"; c.Request.Headers["X-Forwarded-For"] = "11.111.111.11"; c.Request.Headers["X-Forwarded-Host"] = "otherhost"; }); Assert.Null(context.Connection.RemoteIpAddress); Assert.Equal("localhost", context.Request.Host.ToString()); Assert.Equal("http", context.Request.Scheme); } [Fact] public async Task PartiallyEnabledForwardsPartiallyChangesRequest() { var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-Proto"] = "Protocol"; c.Request.Headers["X-Forwarded-For"] = "11.111.111.11"; }); Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); Assert.Equal("localhost", context.Request.Host.ToString()); Assert.Equal("Protocol", context.Request.Scheme); } [Theory] [InlineData("22.33.44.55,::ffff:127.0.0.1", "", "", "22.33.44.55")] [InlineData("22.33.44.55,::ffff:172.123.142.121", "172.123.142.121", "", "22.33.44.55")] [InlineData("22.33.44.55,::ffff:172.123.142.121", "::ffff:172.123.142.121", "", "22.33.44.55")] [InlineData("22.33.44.55,::ffff:172.123.142.121,172.32.24.23", "", "172.0.0.0/8", "22.33.44.55")] [InlineData("2a00:1450:4009:802::200e,2a02:26f0:2d:183::356e,::ffff:172.123.142.121,172.32.24.23", "", "172.0.0.0/8,2a02:26f0:2d:183::1/64", "2a00:1450:4009:802::200e")] [InlineData("22.33.44.55,2a02:26f0:2d:183::356e,::ffff:127.0.0.1", "2a02:26f0:2d:183::356e", "", "22.33.44.55")] public async Task XForwardForIPv4ToIPv6Mapping(string forHeader, string knownProxies, string knownNetworks, string expectedRemoteIp) { var options = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor, ForwardLimit = null, }; foreach (var knownProxy in knownProxies.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries)) { var proxy = IPAddress.Parse(knownProxy); options.KnownProxies.Add(proxy); } foreach (var knownNetwork in knownNetworks.Split(new string[] { "," }, options:StringSplitOptions.RemoveEmptyEntries)) { var knownNetworkParts = knownNetwork.Split('/'); var networkIp = IPAddress.Parse(knownNetworkParts[0]); var prefixLength = int.Parse(knownNetworkParts[1]); options.KnownNetworks.Add(new IPNetwork(networkIp, prefixLength)); } var builder = new WebHostBuilder() .Configure(app => { app.UseForwardedHeaders(options); }); var server = new TestServer(builder); var context = await server.SendAsync(c => { c.Request.Headers["X-Forwarded-For"] = forHeader; }); Assert.Equal(expectedRemoteIp, context.Connection.RemoteIpAddress.ToString()); } } } ================================================ FILE: test/Microsoft.AspNetCore.HttpOverrides.Tests/HttpMethodOverrideMiddlewareTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Xunit; namespace Microsoft.AspNetCore.HttpOverrides { public class HttpMethodOverrideMiddlewareTest { [Fact] public async Task XHttpMethodOverrideHeaderAvaiableChangesRequestMethod() { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseHttpMethodOverride(); app.Run(context => { assertsExecuted = true; Assert.Equal("DELETE", context.Request.Method); return Task.FromResult(0); }); }); var server = new TestServer(builder); var req = new HttpRequestMessage(HttpMethod.Post, ""); req.Headers.Add("X-Http-Method-Override", "DELETE"); await server.CreateClient().SendAsync(req); Assert.True(assertsExecuted); } [Fact] public async Task XHttpMethodOverrideHeaderUnavaiableDoesntChangeRequestMethod() { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseHttpMethodOverride(); app.Run(context => { Assert.Equal("POST",context.Request.Method); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); var req = new HttpRequestMessage(HttpMethod.Post, ""); await server.CreateClient().SendAsync(req); Assert.True(assertsExecuted); } [Fact] public async Task XHttpMethodOverrideFromGetRequestDoesntChangeMethodType() { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseHttpMethodOverride(); app.Run(context => { Assert.Equal("GET", context.Request.Method); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); var req = new HttpRequestMessage(HttpMethod.Get, ""); await server.CreateClient().SendAsync(req); Assert.True(assertsExecuted); } [Fact] public async Task FormFieldAvailableChangesRequestMethod() { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseHttpMethodOverride(new HttpMethodOverrideOptions() { FormFieldName = "_METHOD" }); app.Run(context => { Assert.Equal("DELETE", context.Request.Method); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); var req = new HttpRequestMessage(HttpMethod.Post, ""); req.Content = new FormUrlEncodedContent(new Dictionary() { { "_METHOD", "DELETE" } }); await server.CreateClient().SendAsync(req); Assert.True(assertsExecuted); } [Fact] public async Task FormFieldUnavailableDoesNotChangeRequestMethod() { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseHttpMethodOverride(new HttpMethodOverrideOptions() { FormFieldName = "_METHOD" }); app.Run(context => { Assert.Equal("POST", context.Request.Method); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); var req = new HttpRequestMessage(HttpMethod.Post, ""); req.Content = new FormUrlEncodedContent(new Dictionary() { }); await server.CreateClient().SendAsync(req); Assert.True(assertsExecuted); } [Fact] public async Task FormFieldEmptyDoesNotChangeRequestMethod() { var assertsExecuted = false; var builder = new WebHostBuilder() .Configure(app => { app.UseHttpMethodOverride(new HttpMethodOverrideOptions() { FormFieldName = "_METHOD" }); app.Run(context => { Assert.Equal("POST", context.Request.Method); assertsExecuted = true; return Task.FromResult(0); }); }); var server = new TestServer(builder); var req = new HttpRequestMessage(HttpMethod.Post, ""); req.Content = new FormUrlEncodedContent(new Dictionary() { { "_METHOD", "" } }); await server.CreateClient().SendAsync(req); Assert.True(assertsExecuted); } } } ================================================ FILE: test/Microsoft.AspNetCore.HttpOverrides.Tests/IPEndPointParserTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; using Xunit; namespace Microsoft.AspNetCore.HttpOverrides.Internal { public class IPEndPointParserTests { [Theory] [InlineData("127.0.0.1", "127.0.0.1", 0)] [InlineData("127.0.0.1:1", "127.0.0.1", 1)] [InlineData("1", "0.0.0.1", 0)] [InlineData("1:1", "0.0.0.1", 1)] [InlineData("::1", "::1", 0)] [InlineData("[::1]", "::1", 0)] [InlineData("[::1]:1", "::1", 1)] public void ParsesCorrectly(string input, string expectedAddress, int expectedPort) { IPEndPoint endpoint; var success = IPEndPointParser.TryParse(input, out endpoint); Assert.True(success); Assert.Equal(expectedAddress, endpoint.Address.ToString()); Assert.Equal(expectedPort, endpoint.Port); } [Theory] [InlineData(null)] [InlineData("[::1]:")] [InlineData("[::1:")] [InlineData("::1:")] [InlineData("127:")] [InlineData("127.0.0.1:")] [InlineData("")] [InlineData("[]")] [InlineData("]")] [InlineData("]:1")] public void ShouldNotParse(string input) { IPEndPoint endpoint; var success = IPEndPointParser.TryParse(input, out endpoint); Assert.False(success); Assert.Null(endpoint); } } } ================================================ FILE: test/Microsoft.AspNetCore.HttpOverrides.Tests/IPNetworkTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; using Xunit; namespace Microsoft.AspNetCore.HttpOverrides { public class IPNetworkTest { [Theory] [InlineData("10.1.1.0", 8, "10.1.1.10")] [InlineData("174.0.0.0", 7, "175.1.1.10")] [InlineData("10.174.0.0", 15, "10.175.1.10")] [InlineData("10.168.0.0", 14, "10.171.1.10")] public void Contains_Positive(string prefixText, int length, string addressText) { var network = new IPNetwork(IPAddress.Parse(prefixText), length); Assert.True(network.Contains(IPAddress.Parse(addressText))); } [Theory] [InlineData("10.1.0.0", 16, "10.2.1.10")] [InlineData("174.0.0.0", 7, "173.1.1.10")] [InlineData("10.174.0.0", 15, "10.173.1.10")] [InlineData("10.168.0.0", 14, "10.172.1.10")] public void Contains_Negative(string prefixText, int length, string addressText) { var network = new IPNetwork(IPAddress.Parse(prefixText), length); Assert.False(network.Contains(IPAddress.Parse(addressText))); } } } ================================================ FILE: test/Microsoft.AspNetCore.HttpOverrides.Tests/Microsoft.AspNetCore.HttpOverrides.Tests.csproj ================================================  $(StandardTestTfms) ================================================ FILE: test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.HttpsPolicy.Tests { public class HstsMiddlewareTests { [Fact] public async Task SetOptionsWithDefault_SetsMaxAgeToCorrectValue() { var builder = new WebHostBuilder() .ConfigureServices(services => { }) .Configure(app => { app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); client.BaseAddress = new Uri("https://example.com:5050"); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("max-age=2592000", response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault()); } [Theory] [InlineData(0, false, false, "max-age=0")] [InlineData(-1, false, false, "max-age=-1")] [InlineData(0, true, false, "max-age=0; includeSubDomains")] [InlineData(50000, false, true, "max-age=50000; preload")] [InlineData(0, true, true, "max-age=0; includeSubDomains; preload")] [InlineData(50000, true, true, "max-age=50000; includeSubDomains; preload")] public async Task SetOptionsThroughConfigure_SetsHeaderCorrectly(int maxAge, bool includeSubDomains, bool preload, string expected) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.Configure(options => { options.Preload = preload; options.IncludeSubDomains = includeSubDomains; options.MaxAge = TimeSpan.FromSeconds(maxAge); }); }) .Configure(app => { app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); client.BaseAddress = new Uri("https://example.com:5050"); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(expected, response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault()); } [Theory] [InlineData(0, false, false, "max-age=0")] [InlineData(-1, false, false, "max-age=-1")] [InlineData(0, true, false, "max-age=0; includeSubDomains")] [InlineData(50000, false, true, "max-age=50000; preload")] [InlineData(0, true, true, "max-age=0; includeSubDomains; preload")] [InlineData(50000, true, true, "max-age=50000; includeSubDomains; preload")] public async Task SetOptionsThroughHelper_SetsHeaderCorrectly(int maxAge, bool includeSubDomains, bool preload, string expected) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddHsts(options => { options.Preload = preload; options.IncludeSubDomains = includeSubDomains; options.MaxAge = TimeSpan.FromSeconds(maxAge); }); }) .Configure(app => { app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); client.BaseAddress = new Uri("https://example.com:5050"); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(expected, response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault()); } [Theory] [InlineData("localhost")] [InlineData("Localhost")] [InlineData("LOCALHOST")] [InlineData("127.0.0.1")] [InlineData("[::1]")] public async Task DefaultExcludesCommonLocalhostDomains_DoesNotSetHstsHeader(string host) { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); client.BaseAddress = new Uri($"https://{host}:5050"); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Empty(response.Headers); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.Single(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal($"The host '{host}' is excluded. Skipping HSTS header.", message.State.ToString(), ignoreCase: true); } [Theory] [InlineData("localhost")] [InlineData("127.0.0.1")] [InlineData("[::1]")] public async Task AllowLocalhostDomainsIfListIsReset_SetHstsHeader(string host) { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); services.AddHsts(options => { options.ExcludedHosts.Clear(); }); }) .Configure(app => { app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); client.BaseAddress = new Uri($"https://{host}:5050"); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Single(response.Headers); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.Single(); Assert.Equal(LogLevel.Trace, message.LogLevel); Assert.Equal("Adding HSTS header to response.", message.State.ToString()); } [Theory] [InlineData("example.com")] [InlineData("Example.com")] [InlineData("EXAMPLE.COM")] public async Task AddExcludedDomains_DoesNotAddHstsHeader(string host) { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); services.AddHsts(options => { options.ExcludedHosts.Add(host); }); }) .Configure(app => { app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); client.BaseAddress = new Uri($"https://{host}:5050"); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Empty(response.Headers); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.Single(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal($"The host '{host}' is excluded. Skipping HSTS header.", message.State.ToString(), ignoreCase: true); } [Fact] public async Task WhenRequestIsInsecure_DoesNotAddHstsHeader() { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); client.BaseAddress = new Uri("http://example.com:5050"); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Empty(response.Headers); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.Single(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal("The request is insecure. Skipping HSTS header.", message.State.ToString()); } [Fact] public async Task WhenRequestIsSecure_AddsHstsHeader() { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); client.BaseAddress = new Uri("https://example.com:5050"); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains(response.Headers, x => x.Key == HeaderNames.StrictTransportSecurity); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.Single(); Assert.Equal(LogLevel.Trace, message.LogLevel); Assert.Equal("Adding HSTS header to response.", message.State.ToString()); } } } ================================================ FILE: test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsPolicyTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.HttpsPolicy.Tests { public class HttpsPolicyTests { [Theory] [InlineData(302, 443, 2592000, false, false, "max-age=2592000", "https://localhost/")] [InlineData(301, 5050, 2592000, false, false, "max-age=2592000", "https://localhost:5050/")] [InlineData(301, 443, 2592000, false, false, "max-age=2592000", "https://localhost/")] [InlineData(301, 443, 2592000, true, false, "max-age=2592000; includeSubDomains", "https://localhost/")] [InlineData(301, 443, 2592000, false, true, "max-age=2592000; preload", "https://localhost/")] [InlineData(301, 443, 2592000, true, true, "max-age=2592000; includeSubDomains; preload", "https://localhost/")] [InlineData(302, 5050, 2592000, true, true, "max-age=2592000; includeSubDomains; preload", "https://localhost:5050/")] public async Task SetsBothHstsAndHttpsRedirection_RedirectOnFirstRequest_HstsOnSecondRequest(int statusCode, int? tlsPort, int maxAge, bool includeSubDomains, bool preload, string expectedHstsHeader, string expectedUrl) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.Configure(options => { options.RedirectStatusCode = statusCode; options.HttpsPort = tlsPort; }); services.Configure(options => { options.IncludeSubDomains = includeSubDomains; options.MaxAge = TimeSpan.FromSeconds(maxAge); options.Preload = preload; options.ExcludedHosts.Clear(); // allowing localhost for testing }); }) .Configure(app => { app.UseHttpsRedirection(); app.UseHsts(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var featureCollection = new FeatureCollection(); featureCollection.Set(new ServerAddressesFeature()); var server = new TestServer(builder, featureCollection); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(statusCode, (int)response.StatusCode); Assert.Equal(expectedUrl, response.Headers.Location.ToString()); client = server.CreateClient(); client.BaseAddress = new Uri(response.Headers.Location.ToString()); request = new HttpRequestMessage(HttpMethod.Get, expectedUrl); response = await client.SendAsync(request); Assert.Equal(expectedHstsHeader, response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault()); } } } ================================================ FILE: test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Xunit; namespace Microsoft.AspNetCore.HttpsPolicy.Tests { public class HttpsRedirectionMiddlewareTests { [Fact] public async Task SetOptions_NotEnabledByDefault() { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.Single(); Assert.Equal(LogLevel.Warning, message.LogLevel); Assert.Equal("Failed to determine the https port for redirect.", message.State.ToString()); } [Theory] [InlineData(302, 5001, "https://localhost:5001/")] [InlineData(307, 1, "https://localhost:1/")] [InlineData(308, 3449, "https://localhost:3449/")] [InlineData(301, 5050, "https://localhost:5050/")] [InlineData(301, 443, "https://localhost/")] public async Task SetOptions_SetStatusCodeHttpsPort(int statusCode, int? httpsPort, string expected) { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); services.Configure(options => { options.RedirectStatusCode = statusCode; options.HttpsPort = httpsPort; }); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(statusCode, (int)response.StatusCode); Assert.Equal(expected, response.Headers.Location.ToString()); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.Single(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal($"Redirecting to '{expected}'.", message.State.ToString()); } [Theory] [InlineData(302, 5001, "https://localhost:5001/")] [InlineData(307, 1, "https://localhost:1/")] [InlineData(308, 3449, "https://localhost:3449/")] [InlineData(301, 5050, "https://localhost:5050/")] [InlineData(301, 443, "https://localhost/")] public async Task SetOptionsThroughHelperMethod_SetStatusCodeAndHttpsPort(int statusCode, int? httpsPort, string expectedUrl) { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); services.AddHttpsRedirection(options => { options.RedirectStatusCode = statusCode; options.HttpsPort = httpsPort; }); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(statusCode, (int)response.StatusCode); Assert.Equal(expectedUrl, response.Headers.Location.ToString()); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.Single(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal($"Redirecting to '{expectedUrl}'.", message.State.ToString()); } [Theory] [InlineData(null, null, "https://localhost:4444/", "https://localhost:4444/")] [InlineData(null, null, "https://localhost:443/", "https://localhost/")] [InlineData(null, null, "https://localhost/", "https://localhost/")] [InlineData(null, "5000", "https://localhost:4444/", "https://localhost:5000/")] [InlineData(null, "443", "https://localhost:4444/", "https://localhost/")] [InlineData(443, "5000", "https://localhost:4444/", "https://localhost/")] [InlineData(4000, "5000", "https://localhost:4444/", "https://localhost:4000/")] [InlineData(5000, null, "https://localhost:4444/", "https://localhost:5000/")] public async Task SetHttpsPortEnvironmentVariableAndServerFeature_ReturnsCorrectStatusCodeOnResponse( int? optionsHttpsPort, string configHttpsPort, string serverAddressFeatureUrl, string expectedUrl) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddHttpsRedirection(options => { options.HttpsPort = optionsHttpsPort; }); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); builder.UseSetting("HTTPS_PORT", configHttpsPort); var featureCollection = new FeatureCollection(); featureCollection.Set(new ServerAddressesFeature()); var server = new TestServer(builder, featureCollection); if (serverAddressFeatureUrl != null) { server.Features.Get().Addresses.Add(serverAddressFeatureUrl); } var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(expectedUrl, response.Headers.Location.ToString()); } [Fact] public async Task SetServerAddressesFeature_SingleHttpsAddress_Success() { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var featureCollection = new FeatureCollection(); featureCollection.Set(new ServerAddressesFeature()); var server = new TestServer(builder, featureCollection); server.Features.Get().Addresses.Add("https://localhost:5050"); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal("https://localhost:5050/", response.Headers.Location.ToString()); var logMessages = sink.Writes.ToList(); Assert.Equal(2, logMessages.Count); var message = logMessages.First(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal("Https port '5050' discovered from server endpoints.", message.State.ToString()); message = logMessages.Skip(1).First(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal("Redirecting to 'https://localhost:5050/'.", message.State.ToString()); } [Fact] public async Task SetServerAddressesFeature_MultipleHttpsAddresses_LogsAndFailsToRedirect() { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var featureCollection = new FeatureCollection(); featureCollection.Set(new ServerAddressesFeature()); var server = new TestServer(builder, featureCollection); server.Features.Get().Addresses.Add("https://localhost:5050"); server.Features.Get().Addresses.Add("https://localhost:5051"); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(200, (int)response.StatusCode); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.First(); Assert.Equal(LogLevel.Warning, message.LogLevel); Assert.Equal("Cannot determine the https port from IServerAddressesFeature, multiple values were found. " + "Please set the desired port explicitly on HttpsRedirectionOptions.HttpsPort.", message.State.ToString()); } [Fact] public async Task SetServerAddressesFeature_MultipleHttpsAddressesWithSamePort_Success() { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var featureCollection = new FeatureCollection(); featureCollection.Set(new ServerAddressesFeature()); var server = new TestServer(builder, featureCollection); server.Features.Get().Addresses.Add("https://localhost:5050"); server.Features.Get().Addresses.Add("https://example.com:5050"); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal("https://localhost:5050/", response.Headers.Location.ToString()); var logMessages = sink.Writes.ToList(); Assert.Equal(2, logMessages.Count); var message = logMessages.First(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal("Https port '5050' discovered from server endpoints.", message.State.ToString()); message = logMessages.Skip(1).First(); Assert.Equal(LogLevel.Debug, message.LogLevel); Assert.Equal("Redirecting to 'https://localhost:5050/'.", message.State.ToString()); } [Fact] public async Task NoServerAddressFeature_DoesNotThrow_DoesNotRedirect() { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(200, (int)response.StatusCode); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.First(); Assert.Equal(LogLevel.Warning, message.LogLevel); Assert.Equal("Failed to determine the https port for redirect.", message.State.ToString()); } [Fact] public async Task SetNullAddressFeature_DoesNotThrow() { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseHttpsRedirection(); app.Run(context => { return context.Response.WriteAsync("Hello world"); }); }); var featureCollection = new FeatureCollection(); featureCollection.Set(null); var server = new TestServer(builder, featureCollection); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); var response = await client.SendAsync(request); Assert.Equal(200, (int)response.StatusCode); var logMessages = sink.Writes.ToList(); Assert.Single(logMessages); var message = logMessages.First(); Assert.Equal(LogLevel.Warning, message.LogLevel); Assert.Equal("Failed to determine the https port for redirect.", message.State.ToString()); } } } ================================================ FILE: test/Microsoft.AspNetCore.HttpsPolicy.Tests/Microsoft.AspNetCore.HttpsPolicy.Tests.csproj ================================================ netcoreapp2.2 ================================================ FILE: test/Microsoft.AspNetCore.ResponseCompression.Tests/BodyWrapperStreamTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; using Moq; using Xunit; namespace Microsoft.AspNetCore.ResponseCompression.Tests { public class BodyWrapperStreamTests { [Theory] [InlineData(null, "Accept-Encoding")] [InlineData("", "Accept-Encoding")] [InlineData("AnotherHeader", "AnotherHeader,Accept-Encoding")] [InlineData("Accept-Encoding", "Accept-Encoding")] [InlineData("accepT-encodinG", "accepT-encodinG")] [InlineData("accept-encoding,AnotherHeader", "accept-encoding,AnotherHeader")] public void OnWrite_AppendsAcceptEncodingToVaryHeader_IfNotPresent(string providedVaryHeader, string expectedVaryHeader) { var httpContext = new DefaultHttpContext(); httpContext.Response.Headers[HeaderNames.Vary] = providedVaryHeader; var stream = new BodyWrapperStream(httpContext, new MemoryStream(), new MockResponseCompressionProvider(flushable: true), null, null); stream.Write(new byte[] { }, 0, 0); Assert.Equal(expectedVaryHeader, httpContext.Response.Headers[HeaderNames.Vary]); } [Theory] [InlineData(true)] [InlineData(false)] public void Write_IsPassedToUnderlyingStream_WhenDisableResponseBuffering(bool flushable) { var buffer = new byte[] { 1 }; byte[] written = null; var mock = new Mock(); mock.SetupGet(s => s.CanWrite).Returns(true); mock.Setup(s => s.Write(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((b, o, c) => { written = new ArraySegment(b, 0, c).ToArray(); }); var stream = new BodyWrapperStream(new DefaultHttpContext(), mock.Object, new MockResponseCompressionProvider(flushable), null, null); stream.DisableResponseBuffering(); stream.Write(buffer, 0, buffer.Length); Assert.Equal(buffer, written); } [Theory] [InlineData(true)] [InlineData(false)] public async Task WriteAsync_IsPassedToUnderlyingStream_WhenDisableResponseBuffering(bool flushable) { var buffer = new byte[] { 1 }; var memoryStream = new MemoryStream(); var stream = new BodyWrapperStream(new DefaultHttpContext(), memoryStream, new MockResponseCompressionProvider(flushable), null, null); stream.DisableResponseBuffering(); await stream.WriteAsync(buffer, 0, buffer.Length); Assert.Equal(buffer, memoryStream.ToArray()); } [Fact] public async Task SendFileAsync_IsPassedToUnderlyingStream_WhenDisableResponseBuffering() { var memoryStream = new MemoryStream(); var stream = new BodyWrapperStream(new DefaultHttpContext(), memoryStream, new MockResponseCompressionProvider(true), null, null); stream.DisableResponseBuffering(); var path = "testfile1kb.txt"; await stream.SendFileAsync(path, 0, null, CancellationToken.None); Assert.Equal(File.ReadAllBytes(path), memoryStream.ToArray()); } [Theory] [InlineData(true)] [InlineData(false)] public void BeginWrite_IsPassedToUnderlyingStream_WhenDisableResponseBuffering(bool flushable) { var buffer = new byte[] { 1 }; var memoryStream = new MemoryStream(); var stream = new BodyWrapperStream(new DefaultHttpContext(), memoryStream, new MockResponseCompressionProvider(flushable), null, null); stream.DisableResponseBuffering(); stream.BeginWrite(buffer, 0, buffer.Length, (o) => {}, null); Assert.Equal(buffer, memoryStream.ToArray()); } private class MockResponseCompressionProvider: IResponseCompressionProvider { private readonly bool _flushable; public MockResponseCompressionProvider(bool flushable) { _flushable = flushable; } public ICompressionProvider GetCompressionProvider(HttpContext context) { return new MockCompressionProvider(_flushable); } public bool ShouldCompressResponse(HttpContext context) { return true; } public bool CheckRequestAcceptsCompression(HttpContext context) { return true; } } private class MockCompressionProvider : ICompressionProvider { public MockCompressionProvider(bool flushable) { SupportsFlush = flushable; } public string EncodingName { get; } public bool SupportsFlush { get; } public Stream CreateStream(Stream outputStream) { if (SupportsFlush) { return new BufferedStream(outputStream); } else { return new NoFlushBufferedStream(outputStream); } } } private class NoFlushBufferedStream : Stream { private readonly BufferedStream _bufferedStream; public NoFlushBufferedStream(Stream outputStream) { _bufferedStream = new BufferedStream(outputStream); } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) => _bufferedStream.Read(buffer, offset, count); public override long Seek(long offset, SeekOrigin origin) => _bufferedStream.Seek(offset, origin); public override void SetLength(long value) => _bufferedStream.SetLength(value); public override void Write(byte[] buffer, int offset, int count) => _bufferedStream.Write(buffer, offset, count); public override bool CanRead => _bufferedStream.CanRead; public override bool CanSeek => _bufferedStream.CanSeek; public override bool CanWrite => _bufferedStream.CanWrite; public override long Length => _bufferedStream.Length; public override long Position { get { return _bufferedStream.Position; } set { _bufferedStream.Position = value; } } protected override void Dispose(bool disposing) { base.Dispose(disposing); _bufferedStream.Flush(); } } } } ================================================ FILE: test/Microsoft.AspNetCore.ResponseCompression.Tests/Microsoft.AspNetCore.ResponseCompression.Tests.csproj ================================================  $(StandardTestTfms) ================================================ FILE: test/Microsoft.AspNetCore.ResponseCompression.Tests/ResponseCompressionMiddlewareTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.ResponseCompression.Tests { public class ResponseCompressionMiddlewareTest { private const string TextPlain = "text/plain"; public static IEnumerable SupportedEncodings => TestData.Select(x => new object[] { x.EncodingName }); public static IEnumerable SupportedEncodingsWithBodyLength => TestData.Select(x => new object[] { x.EncodingName, x.ExpectedBodyLength }); private static IEnumerable TestData { get { yield return new EncodingTestData("gzip", expectedBodyLength: 24); #if NETCOREAPP2_2 yield return new EncodingTestData("br", expectedBodyLength: 20); #elif NET461 #else #error Target frameworks need to be updated. #endif } } [Fact] public void Options_HttpsDisabledByDefault() { var options = new ResponseCompressionOptions(); Assert.False(options.EnableForHttps); } [Fact] public async Task Request_NoAcceptEncoding_Uncompressed() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: null, responseType: TextPlain); CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); AssertLog(logMessages.Single(), LogLevel.Debug, "No response compression available, the Accept-Encoding header is missing or invalid."); } [Fact] public async Task Request_AcceptGzipDeflate_CompressedGzip() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: new[] { "gzip", "deflate" }, responseType: TextPlain); CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip"); AssertCompressedWithLog(logMessages, "gzip"); } [Fact] public async Task Request_AcceptBrotli_CompressedBrotli() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: new[] { "br" }, responseType: TextPlain); #if NET461 CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: true); #elif NETCOREAPP2_2 CheckResponseCompressed(response, expectedBodyLength: 20, expectedEncoding: "br"); AssertCompressedWithLog(logMessages, "br"); #else #error Target frameworks need to be updated. #endif } [Theory] [InlineData("gzip", "br")] [InlineData("br", "gzip")] public async Task Request_AcceptMixed_CompressedBrotli(string encoding1, string encoding2) { var (response, logMessages) = await InvokeMiddleware(100, new[] { encoding1, encoding2 }, responseType: TextPlain); #if NET461 CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip"); AssertCompressedWithLog(logMessages, "gzip"); #elif NETCOREAPP2_2 CheckResponseCompressed(response, expectedBodyLength: 20, expectedEncoding: "br"); AssertCompressedWithLog(logMessages, "br"); #else #error Target frameworks need to be updated. #endif } #if NETCOREAPP2_2 [Theory] [InlineData("gzip", "br")] [InlineData("br", "gzip")] public async Task Request_AcceptMixed_ConfiguredOrder_CompressedGzip(string encoding1, string encoding2) { void Configure(ResponseCompressionOptions options) { options.Providers.Add(); options.Providers.Add(); } var (response, logMessages) = await InvokeMiddleware(100, new[] { encoding1, encoding2 }, responseType: TextPlain, configure: Configure); CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip"); AssertCompressedWithLog(logMessages, "gzip"); } #elif NET461 #else #error Target frameworks need to be updated. #endif [Fact] public async Task Request_AcceptUnknown_NotCompressed() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: new[] { "unknown" }, responseType: TextPlain); CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: true); Assert.Equal(3, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); AssertLog(logMessages.Skip(1).First(), LogLevel.Trace, "Response compression is available for this Content-Type."); AssertLog(logMessages.Skip(2).First(), LogLevel.Debug, "No matching response compression provider found."); } [Theory] [InlineData("text/plain")] [InlineData("text/PLAIN")] [InlineData("text/plain; charset=ISO-8859-4")] [InlineData("text/plain ; charset=ISO-8859-4")] public async Task ContentType_WithCharset_Compress(string contentType) { var (response, logMessages) = await InvokeMiddleware(uncompressedBodyLength: 100, requestAcceptEncodings: new[] { "gzip" }, contentType); CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip"); AssertCompressedWithLog(logMessages, "gzip"); } [Fact] public async Task GZipCompressionProvider_OptionsSetInDI_Compress() { var builder = new WebHostBuilder() .ConfigureServices(services => { services.Configure(options => options.Level = CompressionLevel.NoCompression); services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; return context.Response.WriteAsync(new string('a', 100)); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd("gzip"); var response = await client.SendAsync(request); CheckResponseCompressed(response, expectedBodyLength: 123, expectedEncoding: "gzip"); } [Theory] [InlineData("")] [InlineData("text/plain2")] public async Task MimeTypes_OtherContentTypes_NoMatch(string contentType) { var (response, logMessages) = await InvokeMiddleware(uncompressedBodyLength: 100, requestAcceptEncodings: new[] { "gzip" }, contentType); CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); Assert.Equal(2, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); var expected = string.IsNullOrEmpty(contentType) ? "(null)" : contentType; AssertLog(logMessages.Skip(1).First(), LogLevel.Debug, $"Response compression is not enabled for the Content-Type '{expected}'."); } [Theory] [InlineData(null, null, "text/plain", true)] [InlineData(null, new string[0], "text/plain", true)] [InlineData(null, new[] { "TEXT/plain" }, "text/plain", false)] [InlineData(null, new[] { "TEXT/*" }, "text/plain", true)] [InlineData(null, new[] { "*/*" }, "text/plain", true)] [InlineData(new string[0], null, "text/plain", true)] [InlineData(new string[0], new string[0], "text/plain", true)] [InlineData(new string[0], new[] { "TEXT/plain" }, "text/plain", false)] [InlineData(new string[0], new[] { "TEXT/*" }, "text/plain", true)] [InlineData(new string[0], new[] { "*/*" }, "text/plain", true)] [InlineData(new[] { "TEXT/plain" }, null, "text/plain", true)] [InlineData(new[] { "TEXT/plain" }, new string[0], "text/plain", true)] [InlineData(new[] { "TEXT/plain" }, new[] { "TEXT/plain" }, "text/plain", false)] [InlineData(new[] { "TEXT/plain" }, new[] { "TEXT/*" }, "text/plain", true)] [InlineData(new[] { "TEXT/plain" }, new[] { "*/*" }, "text/plain", true)] [InlineData(new[] { "TEXT/*" }, null, "text/plain", true)] [InlineData(new[] { "TEXT/*" }, new string[0], "text/plain", true)] [InlineData(new[] { "TEXT/*" }, new[] { "TEXT/plain" }, "text/plain", false)] [InlineData(new[] { "TEXT/*" }, new[] { "TEXT/*" }, "text/plain", false)] [InlineData(new[] { "TEXT/*" }, new[] { "*/*" }, "text/plain", true)] [InlineData(new[] { "*/*" }, null, "text/plain", true)] [InlineData(new[] { "*/*" }, new string[0], "text/plain", true)] [InlineData(new[] { "*/*" }, new[] { "TEXT/plain" }, "text/plain", false)] [InlineData(new[] { "*/*" }, new[] { "TEXT/*" }, "text/plain", false)] [InlineData(new[] { "*/*" }, new[] { "*/*" }, "text/plain", true)] [InlineData(null, null, "text/plain2", false)] [InlineData(null, new string[0], "text/plain2", false)] [InlineData(null, new[] { "TEXT/plain" }, "text/plain2", false)] [InlineData(null, new[] { "TEXT/*" }, "text/plain2", false)] [InlineData(null, new[] { "*/*" }, "text/plain2", false)] [InlineData(new string[0], null, "text/plain2", false)] [InlineData(new string[0], new string[0], "text/plain2", false)] [InlineData(new string[0], new[] { "TEXT/plain" }, "text/plain2", false)] [InlineData(new string[0], new[] { "TEXT/*" }, "text/plain2", false)] [InlineData(new string[0], new[] { "*/*" }, "text/plain2", false)] [InlineData(new[] { "TEXT/plain" }, null, "text/plain2", false)] [InlineData(new[] { "TEXT/plain" }, new string[0], "text/plain2", false)] [InlineData(new[] { "TEXT/plain" }, new[] { "TEXT/plain" }, "text/plain2", false)] [InlineData(new[] { "TEXT/plain" }, new[] { "TEXT/*" }, "text/plain2", false)] [InlineData(new[] { "TEXT/plain" }, new[] { "*/*" }, "text/plain2", false)] [InlineData(new[] { "TEXT/*" }, null, "text/plain2", true)] [InlineData(new[] { "TEXT/*" }, new string[0], "text/plain2", true)] [InlineData(new[] { "TEXT/*" }, new[] { "TEXT/plain" }, "text/plain2", true)] [InlineData(new[] { "TEXT/*" }, new[] { "TEXT/*" }, "text/plain2", false)] [InlineData(new[] { "TEXT/*" }, new[] { "*/*" }, "text/plain2", true)] [InlineData(new[] { "*/*" }, null, "text/plain2", true)] [InlineData(new[] { "*/*" }, new string[0], "text/plain2", true)] [InlineData(new[] { "*/*" }, new[] { "TEXT/plain" }, "text/plain2", true)] [InlineData(new[] { "*/*" }, new[] { "TEXT/*" }, "text/plain2", false)] [InlineData(new[] { "*/*" }, new[] { "*/*" }, "text/plain2", true)] public async Task MimeTypes_IncludedAndExcluded( string[] mimeTypes, string[] excludedMimeTypes, string contentType, bool compress ) { var (response, logMessages) = await InvokeMiddleware(uncompressedBodyLength: 100, requestAcceptEncodings: new[] { "gzip" }, contentType, configure: options => { options.MimeTypes = mimeTypes; options.ExcludedMimeTypes = excludedMimeTypes; }); if (compress) { CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip"); AssertCompressedWithLog(logMessages, "gzip"); } else { CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); Assert.Equal(2, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); AssertLog(logMessages.Skip(1).First(), LogLevel.Debug, $"Response compression is not enabled for the Content-Type '{contentType}'."); } } [Fact] public async Task NoIncludedMimeTypes_UseDefaults() { var (response, logMessages) = await InvokeMiddleware(uncompressedBodyLength: 100, requestAcceptEncodings: new[] { "gzip" }, TextPlain, configure: options => { options.ExcludedMimeTypes = new[] { "text/*" }; }); CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip"); AssertCompressedWithLog(logMessages, "gzip"); } [Theory] [InlineData("")] [InlineData("text/plain")] [InlineData("text/PLAIN")] [InlineData("text/plain; charset=ISO-8859-4")] [InlineData("text/plain ; charset=ISO-8859-4")] [InlineData("text/plain2")] public async Task NoBody_NotCompressed(string contentType) { var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = contentType; return Task.FromResult(0); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd("gzip"); var response = await client.SendAsync(request); CheckResponseNotCompressed(response, expectedBodyLength: 0, sendVaryHeader: false); } [Fact] public async Task Request_AcceptStar_Compressed() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: new[] { "*" }, responseType: TextPlain); #if NET461 CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip"); AssertCompressedWithLog(logMessages, "gzip"); #elif NETCOREAPP2_2 CheckResponseCompressed(response, expectedBodyLength: 20, expectedEncoding: "br"); AssertCompressedWithLog(logMessages, "br"); #else #error Target frameworks need to be updated. #endif } [Fact] public async Task Request_AcceptIdentity_NotCompressed() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: new[] { "identity" }, responseType: TextPlain); CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: true); Assert.Equal(3, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); AssertLog(logMessages.Skip(1).First(), LogLevel.Trace, "Response compression is available for this Content-Type."); AssertLog(logMessages.Skip(2).First(), LogLevel.Debug, "No matching response compression provider found."); } [Theory] [InlineData(new[] { "identity;q=0.5", "gzip;q=1" }, 24)] [InlineData(new[] { "identity;q=0", "gzip;q=0.8" }, 24)] [InlineData(new[] { "identity;q=0.5", "gzip" }, 24)] public async Task Request_AcceptWithHigherCompressionQuality_Compressed(string[] acceptEncodings, int expectedBodyLength) { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: acceptEncodings, responseType: TextPlain); CheckResponseCompressed(response, expectedBodyLength: expectedBodyLength, expectedEncoding: "gzip"); AssertCompressedWithLog(logMessages, "gzip"); } [Theory] [InlineData(new[] { "gzip;q=0.5", "identity;q=0.8" }, 100)] public async Task Request_AcceptWithhigherIdentityQuality_NotCompressed(string[] acceptEncodings, int expectedBodyLength) { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: acceptEncodings, responseType: TextPlain); CheckResponseNotCompressed(response, expectedBodyLength: expectedBodyLength, sendVaryHeader: true); Assert.Equal(3, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); AssertLog(logMessages.Skip(1).First(), LogLevel.Trace, "Response compression is available for this Content-Type."); AssertLog(logMessages.Skip(2).First(), LogLevel.Debug, "No matching response compression provider found."); } [Fact] public async Task Response_UnknownMimeType_NotCompressed() { var (response, logMessages) = await InvokeMiddleware(100, requestAcceptEncodings: new[] { "gzip" }, responseType: "text/custom"); CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false); Assert.Equal(2, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); AssertLog(logMessages.Skip(1).First(), LogLevel.Debug, "Response compression is not enabled for the Content-Type 'text/custom'."); } [Fact] public async Task Response_WithContentRange_NotCompressed() { var (response, logMessages) = await InvokeMiddleware(50, requestAcceptEncodings: new[] { "gzip" }, responseType: TextPlain, addResponseAction: (r) => { r.Headers[HeaderNames.ContentRange] = "1-2/*"; }); CheckResponseNotCompressed(response, expectedBodyLength: 50, sendVaryHeader: false); Assert.Equal(2, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); AssertLog(logMessages.Skip(1).First(), LogLevel.Debug, "Response compression disabled due to the Content-Range header."); } [Fact] public async Task Response_WithContentEncodingAlreadySet_NotReCompressed() { var otherContentEncoding = "something"; var (response, logMessages) = await InvokeMiddleware(50, requestAcceptEncodings: new[] { "gzip" }, responseType: TextPlain, addResponseAction: (r) => { r.Headers[HeaderNames.ContentEncoding] = otherContentEncoding; }); Assert.True(response.Content.Headers.ContentEncoding.Contains(otherContentEncoding)); Assert.False(response.Content.Headers.ContentEncoding.Contains("gzip")); Assert.Equal(50, response.Content.Headers.ContentLength); Assert.Equal(2, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); AssertLog(logMessages.Skip(1).First(), LogLevel.Debug, "Response compression disabled due to the Content-Encoding header."); } [Theory] [InlineData(false, 100)] [InlineData(true, 24)] public async Task Request_Https_CompressedIfEnabled(bool enableHttps, int expectedLength) { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddSingleton(loggerFactory); services.AddResponseCompression(options => { options.EnableForHttps = enableHttps; options.MimeTypes = new[] { TextPlain }; }); }) .Configure(app => { app.UseResponseCompression(); app.Run(context => { context.Response.ContentType = TextPlain; return context.Response.WriteAsync(new string('a', 100)); }); }); var server = new TestServer(builder) { BaseAddress = new Uri("https://localhost/") }; var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd("gzip"); var response = await client.SendAsync(request); Assert.Equal(expectedLength, response.Content.ReadAsByteArrayAsync().Result.Length); var logMessages = sink.Writes.ToList(); if (enableHttps) { AssertCompressedWithLog(logMessages, "gzip"); } else { AssertLog(logMessages.Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps."); } } [Theory] [MemberData(nameof(SupportedEncodingsWithBodyLength))] public async Task FlushHeaders_SendsHeaders_Compresses(string encoding, int expectedBodyLength) { var responseReceived = new ManualResetEvent(false); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; context.Response.Body.Flush(); Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3))); return context.Response.WriteAsync(new string('a', 100)); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd(encoding); var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); responseReceived.Set(); await response.Content.LoadIntoBufferAsync(); CheckResponseCompressed(response, expectedBodyLength, encoding); } [Theory] [MemberData(nameof(SupportedEncodingsWithBodyLength))] public async Task FlushAsyncHeaders_SendsHeaders_Compresses(string encoding, int expectedBodyLength) { var responseReceived = new ManualResetEvent(false); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(async context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; await context.Response.Body.FlushAsync(); Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3))); await context.Response.WriteAsync(new string('a', 100)); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd(encoding); var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); responseReceived.Set(); await response.Content.LoadIntoBufferAsync(); CheckResponseCompressed(response, expectedBodyLength, encoding); } [Theory] [MemberData(nameof(SupportedEncodings))] public async Task FlushBody_CompressesAndFlushes(string encoding) { var responseReceived = new ManualResetEvent(false); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; context.Response.Body.Write(new byte[10], 0, 10); context.Response.Body.Flush(); Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3))); context.Response.Body.Write(new byte[90], 0, 90); return Task.FromResult(0); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd(encoding); var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); Assert.False(response.Content.Headers.TryGetValues(HeaderNames.ContentMD5, out _)); Assert.Single(response.Content.Headers.ContentEncoding, encoding); var body = await response.Content.ReadAsStreamAsync(); var read = await body.ReadAsync(new byte[100], 0, 100); Assert.True(read > 0); responseReceived.Set(); read = await body.ReadAsync(new byte[100], 0, 100); Assert.True(read > 0); } [Theory] [MemberData(nameof(SupportedEncodings))] public async Task FlushAsyncBody_CompressesAndFlushes(string encoding) { var responseReceived = new ManualResetEvent(false); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(async context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; await context.Response.WriteAsync(new string('a', 10)); await context.Response.Body.FlushAsync(); Assert.True(responseReceived.WaitOne(TimeSpan.FromSeconds(3))); await context.Response.WriteAsync(new string('a', 90)); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd(encoding); var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); Assert.False(response.Content.Headers.TryGetValues(HeaderNames.ContentMD5, out _)); Assert.Single(response.Content.Headers.ContentEncoding, encoding); var body = await response.Content.ReadAsStreamAsync(); var read = await body.ReadAsync(new byte[100], 0, 100); Assert.True(read > 0); responseReceived.Set(); read = await body.ReadAsync(new byte[100], 0, 100); Assert.True(read > 0); } [Theory] [MemberData(nameof(SupportedEncodings))] public async Task TrickleWriteAndFlush_FlushesEachWrite(string encoding) { var responseReceived = new[] { new ManualResetEvent(false), new ManualResetEvent(false), new ManualResetEvent(false), new ManualResetEvent(false), new ManualResetEvent(false), }; var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; context.Features.Get()?.DisableResponseBuffering(); foreach (var signal in responseReceived) { context.Response.Body.Write(new byte[1], 0, 1); context.Response.Body.Flush(); Assert.True(signal.WaitOne(TimeSpan.FromSeconds(3))); } return Task.FromResult(0); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd(encoding); var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); #if NET461 // Flush not supported, compression disabled Assert.NotNull(response.Content.Headers.GetValues(HeaderNames.ContentMD5)); Assert.Empty(response.Content.Headers.ContentEncoding); #elif NETCOREAPP2_2 // Flush supported, compression enabled Assert.False(response.Content.Headers.TryGetValues(HeaderNames.ContentMD5, out _)); Assert.Single(response.Content.Headers.ContentEncoding, encoding); #else #error Target frameworks need to be updated. #endif var body = await response.Content.ReadAsStreamAsync(); foreach (var signal in responseReceived) { var read = await body.ReadAsync(new byte[100], 0, 100); Assert.True(read > 0); signal.Set(); } } [Theory] [MemberData(nameof(SupportedEncodings))] public async Task TrickleWriteAndFlushAsync_FlushesEachWrite(string encoding) { var responseReceived = new[] { new ManualResetEvent(false), new ManualResetEvent(false), new ManualResetEvent(false), new ManualResetEvent(false), new ManualResetEvent(false), }; var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(async context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; context.Features.Get()?.DisableResponseBuffering(); foreach (var signal in responseReceived) { await context.Response.WriteAsync("a"); await context.Response.Body.FlushAsync(); Assert.True(signal.WaitOne(TimeSpan.FromSeconds(3))); } }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd(encoding); var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); #if NET461 // Flush not supported, compression disabled Assert.NotNull(response.Content.Headers.GetValues(HeaderNames.ContentMD5)); Assert.Empty(response.Content.Headers.ContentEncoding); #elif NETCOREAPP2_2 // Flush supported, compression enabled Assert.False(response.Content.Headers.TryGetValues(HeaderNames.ContentMD5, out _)); Assert.Single(response.Content.Headers.ContentEncoding, encoding); #else #error Target framework needs to be updated #endif var body = await response.Content.ReadAsStreamAsync(); foreach (var signal in responseReceived) { var read = await body.ReadAsync(new byte[100], 0, 100); Assert.True(read > 0); signal.Set(); } } [Fact] public async Task SendFileAsync_OnlySetIfFeatureAlreadyExists() { var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; context.Response.ContentLength = 1024; var sendFile = context.Features.Get(); Assert.Null(sendFile); return Task.FromResult(0); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd("gzip"); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); } [Fact] public async Task SendFileAsync_DifferentContentType_NotBypassed() { FakeSendFileFeature fakeSendFile = null; var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.Use((context, next) => { fakeSendFile = new FakeSendFileFeature(context.Response.Body); context.Features.Set(fakeSendFile); return next(); }); app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = "custom/type"; context.Response.ContentLength = 1024; var sendFile = context.Features.Get(); Assert.NotNull(sendFile); return sendFile.SendFileAsync("testfile1kb.txt", 0, null, CancellationToken.None); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd("gzip"); var response = await client.SendAsync(request); CheckResponseNotCompressed(response, expectedBodyLength: 1024, sendVaryHeader: false); Assert.True(fakeSendFile.Invoked); } [Fact] public async Task SendFileAsync_FirstWrite_CompressesAndFlushes() { FakeSendFileFeature fakeSendFile = null; var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.Use((context, next) => { fakeSendFile = new FakeSendFileFeature(context.Response.Body); context.Features.Set(fakeSendFile); return next(); }); app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; context.Response.ContentLength = 1024; var sendFile = context.Features.Get(); Assert.NotNull(sendFile); return sendFile.SendFileAsync("testfile1kb.txt", 0, null, CancellationToken.None); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd("gzip"); var response = await client.SendAsync(request); CheckResponseCompressed(response, expectedBodyLength: 34, expectedEncoding: "gzip"); Assert.False(fakeSendFile.Invoked); } [Fact] public async Task SendFileAsync_AfterFirstWrite_CompressesAndFlushes() { FakeSendFileFeature fakeSendFile = null; var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(); }) .Configure(app => { app.Use((context, next) => { fakeSendFile = new FakeSendFileFeature(context.Response.Body); context.Features.Set(fakeSendFile); return next(); }); app.UseResponseCompression(); app.Run(async context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = TextPlain; var sendFile = context.Features.Get(); Assert.NotNull(sendFile); await context.Response.WriteAsync(new string('a', 100)); await sendFile.SendFileAsync("testfile1kb.txt", 0, null, CancellationToken.None); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); request.Headers.AcceptEncoding.ParseAdd("gzip"); var response = await client.SendAsync(request); CheckResponseCompressed(response, expectedBodyLength: 40, expectedEncoding: "gzip"); Assert.False(fakeSendFile.Invoked); } private async Task<(HttpResponseMessage, List)> InvokeMiddleware( int uncompressedBodyLength, string[] requestAcceptEncodings, string responseType, Action addResponseAction = null, Action configure = null) { var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var builder = new WebHostBuilder() .ConfigureServices(services => { services.AddResponseCompression(configure ?? (_ => { })); services.AddSingleton(loggerFactory); }) .Configure(app => { app.UseResponseCompression(); app.Run(context => { context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.ContentType = responseType; Assert.Null(context.Features.Get()); addResponseAction?.Invoke(context.Response); return context.Response.WriteAsync(new string('a', uncompressedBodyLength)); }); }); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, ""); for (var i = 0; i < requestAcceptEncodings?.Length; i++) { request.Headers.AcceptEncoding.Add(System.Net.Http.Headers.StringWithQualityHeaderValue.Parse(requestAcceptEncodings[i])); } return (await client.SendAsync(request), sink.Writes.ToList()); } private void CheckResponseCompressed(HttpResponseMessage response, int expectedBodyLength, string expectedEncoding) { var containsVaryAcceptEncoding = false; foreach (var value in response.Headers.GetValues(HeaderNames.Vary)) { if (value.Contains(HeaderNames.AcceptEncoding)) { containsVaryAcceptEncoding = true; break; } } Assert.True(containsVaryAcceptEncoding); Assert.False(response.Content.Headers.TryGetValues(HeaderNames.ContentMD5, out _)); Assert.Single(response.Content.Headers.ContentEncoding, expectedEncoding); Assert.Equal(expectedBodyLength, response.Content.Headers.ContentLength); } private void CheckResponseNotCompressed(HttpResponseMessage response, int expectedBodyLength, bool sendVaryHeader) { if (sendVaryHeader) { var containsVaryAcceptEncoding = false; foreach (var value in response.Headers.GetValues(HeaderNames.Vary)) { if (value.Contains(HeaderNames.AcceptEncoding)) { containsVaryAcceptEncoding = true; break; } } Assert.True(containsVaryAcceptEncoding); } else { Assert.False(response.Headers.Contains(HeaderNames.Vary)); } Assert.NotNull(response.Content.Headers.GetValues(HeaderNames.ContentMD5)); Assert.Empty(response.Content.Headers.ContentEncoding); Assert.Equal(expectedBodyLength, response.Content.Headers.ContentLength); } private void AssertLog(WriteContext log, LogLevel level, string message) { Assert.Equal(level, log.LogLevel); Assert.Equal(message, log.State.ToString()); } private void AssertCompressedWithLog(List logMessages, string provider) { Assert.Equal(3, logMessages.Count); AssertLog(logMessages.First(), LogLevel.Trace, "This request accepts compression."); AssertLog(logMessages.Skip(1).First(), LogLevel.Trace, "Response compression is available for this Content-Type."); AssertLog(logMessages.Skip(2).First(), LogLevel.Debug, $"The response will be compressed with '{provider}'."); } private class FakeSendFileFeature : IHttpSendFileFeature { private readonly Stream _innerBody; public FakeSendFileFeature(Stream innerBody) { _innerBody = innerBody; } public bool Invoked { get; set; } public async Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation) { // This implementation should only be delegated to if compression is disabled. Invoked = true; using (var file = new FileStream(path, FileMode.Open)) { file.Seek(offset, SeekOrigin.Begin); if (count.HasValue) { throw new NotImplementedException("Not implemented for testing"); } await file.CopyToAsync(_innerBody, 81920, cancellation); } } } private readonly struct EncodingTestData { public EncodingTestData(string encodingName, int expectedBodyLength) { EncodingName = encodingName; ExpectedBodyLength = expectedBodyLength; } public string EncodingName { get; } public int ExpectedBodyLength { get; } } } } ================================================ FILE: test/Microsoft.AspNetCore.ResponseCompression.Tests/testfile1kb.txt ================================================ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/ConditionPatternParserTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { public class ConditionPatternParserTest { [Theory] [InlineData(">hey", OperationType.Greater, "hey", ConditionType.StringComp)] [InlineData("=hey", OperationType.GreaterEqual, "hey", ConditionType.StringComp)] [InlineData("<=hey", OperationType.LessEqual, "hey", ConditionType.StringComp)] [InlineData("=hey", OperationType.Equal, "hey", ConditionType.StringComp)] public void ConditionPatternParser_CheckStringComp(string condition, OperationType operation, string variable, ConditionType conditionType) { var results = new ConditionPatternParser().ParseActionCondition(condition); var expected = new ParsedModRewriteInput { OperationType = operation, ConditionType = conditionType, Operand = variable, Invert = false }; Assert.True(CompareConditions(expected, results)); } [Fact] public void ConditionPatternParser_CheckRegexEqual() { var condition = @"(.*)"; var results = new ConditionPatternParser().ParseActionCondition(condition); var expected = new ParsedModRewriteInput { ConditionType = ConditionType.Regex, Operand = "(.*)", Invert = false }; Assert.True(CompareConditions(expected, results)); } [Theory] [InlineData("-d", OperationType.Directory, ConditionType.PropertyTest)] [InlineData("-f", OperationType.RegularFile, ConditionType.PropertyTest)] [InlineData("-F", OperationType.ExistingFile, ConditionType.PropertyTest)] [InlineData("-h", OperationType.SymbolicLink, ConditionType.PropertyTest)] [InlineData("-L", OperationType.SymbolicLink, ConditionType.PropertyTest)] [InlineData("-l", OperationType.SymbolicLink, ConditionType.PropertyTest)] [InlineData("-s", OperationType.Size, ConditionType.PropertyTest)] [InlineData("-U", OperationType.ExistingUrl, ConditionType.PropertyTest)] [InlineData("-x", OperationType.Executable, ConditionType.PropertyTest)] public void ConditionPatternParser_CheckFileOperations(string condition, OperationType operation, ConditionType cond) { var results = new ConditionPatternParser().ParseActionCondition(condition); var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation, Invert = false }; Assert.True(CompareConditions(expected, results)); } [Theory] [InlineData("!-d", OperationType.Directory, ConditionType.PropertyTest)] [InlineData("!-f", OperationType.RegularFile, ConditionType.PropertyTest)] [InlineData("!-F", OperationType.ExistingFile, ConditionType.PropertyTest)] [InlineData("!-h", OperationType.SymbolicLink, ConditionType.PropertyTest)] [InlineData("!-L", OperationType.SymbolicLink, ConditionType.PropertyTest)] [InlineData("!-l", OperationType.SymbolicLink, ConditionType.PropertyTest)] [InlineData("!-s", OperationType.Size, ConditionType.PropertyTest)] [InlineData("!-U", OperationType.ExistingUrl, ConditionType.PropertyTest)] [InlineData("!-x", OperationType.Executable, ConditionType.PropertyTest)] public void ConditionPatternParser_CheckFileOperationsInverted(string condition, OperationType operation, ConditionType cond) { var results = new ConditionPatternParser().ParseActionCondition(condition); var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation, Invert = true }; Assert.True(CompareConditions(expected, results)); } [Theory] [InlineData("-gt1", OperationType.Greater, "1", ConditionType.IntComp)] [InlineData("-lt1", OperationType.Less, "1", ConditionType.IntComp)] [InlineData("-ge1", OperationType.GreaterEqual, "1", ConditionType.IntComp)] [InlineData("-le1", OperationType.LessEqual, "1", ConditionType.IntComp)] [InlineData("-eq1", OperationType.Equal, "1", ConditionType.IntComp)] [InlineData("-ne1", OperationType.NotEqual, "1", ConditionType.IntComp)] public void ConditionPatternParser_CheckIntComp(string condition, OperationType operation, string variable, ConditionType cond) { var results = new ConditionPatternParser().ParseActionCondition(condition); var expected = new ParsedModRewriteInput { ConditionType = cond, OperationType = operation, Invert = false, Operand = variable }; Assert.True(CompareConditions(expected, results)); } [Theory] [InlineData("", "Unrecognized parameter type: '', terminated at string index: '0'")] [InlineData("!", "Unrecognized parameter type: '!', terminated at string index: '1'")] [InlineData(">", "Unrecognized parameter type: '>', terminated at string index: '1'")] [InlineData("<", "Unrecognized parameter type: '<', terminated at string index: '1'")] [InlineData("=", "Unrecognized parameter type: '=', terminated at string index: '1'")] [InlineData(">=", "Unrecognized parameter type: '>=', terminated at string index: '2'")] [InlineData("<=", "Unrecognized parameter type: '<=', terminated at string index: '2'")] [InlineData("-a", "Unrecognized parameter type: '-a', terminated at string index: '1'")] [InlineData("-gewow", "Unrecognized parameter type: '-gewow', terminated at string index: '3'")] public void ConditionPatternParser_AssertBadInputThrowsFormatException(string input, string expected) { var ex = Assert.Throws(() => new ConditionPatternParser().ParseActionCondition(input)); Assert.Equal(expected, ex.Message); } private bool CompareConditions(ParsedModRewriteInput i1, ParsedModRewriteInput i2) { if (i1.OperationType != i2.OperationType || i1.ConditionType != i2.ConditionType || i1.Operand != i2.Operand || i1.Invert != i2.Invert) { return false; } return true; } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/CookieActionFactoryTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Test { public class CookieActionFactoryTest { [Fact] public void Creates_OneCookie() { var cookie = new CookieActionFactory().Create("NAME:VALUE:DOMAIN:1440:path:secure:httponly"); Assert.Equal("NAME", cookie.Name); Assert.Equal("VALUE", cookie.Value); Assert.Equal("DOMAIN", cookie.Domain); Assert.Equal(TimeSpan.FromMinutes(1440), cookie.Lifetime); Assert.Equal("path", cookie.Path); Assert.True(cookie.Secure); Assert.True(cookie.HttpOnly); } [Fact] public void Creates_OneCookie_AltSeparator() { var action = new CookieActionFactory().Create(";NAME;VALUE:WithColon;DOMAIN;1440;path;secure;httponly"); Assert.Equal("NAME", action.Name); Assert.Equal("VALUE:WithColon", action.Value); Assert.Equal("DOMAIN", action.Domain); Assert.Equal(TimeSpan.FromMinutes(1440), action.Lifetime); Assert.Equal("path", action.Path); Assert.True(action.Secure); Assert.True(action.HttpOnly); } [Fact] public void Creates_HttpOnly() { var action = new CookieActionFactory().Create(";NAME;VALUE;DOMAIN;;;;httponly"); Assert.Equal("NAME", action.Name); Assert.Equal("VALUE", action.Value); Assert.Equal("DOMAIN", action.Domain); Assert.Equal(0, action.Lifetime.TotalSeconds); Assert.Equal(string.Empty, action.Path); Assert.False(action.Secure); Assert.True(action.HttpOnly); } [Theory] [InlineData("NAME::", "", null)] [InlineData("NAME::domain", "", "domain")] [InlineData("NAME:VALUE:;", "VALUE", null)] // special case with dangling ';' [InlineData("NAME:value:", "value", null)] [InlineData(" NAME : v : ", "v", null)] // trims values public void TrimsValues(string flagValue, string value, string domain) { var factory = new CookieActionFactory(); var action = factory.Create(flagValue); Assert.Equal("NAME", action.Name); Assert.NotNull(action.Value); Assert.Equal(value, action.Value); Assert.Equal(domain, action.Domain); } [Theory] [InlineData("NAME")] // missing value and domain [InlineData("NAME: ")] // missing domain [InlineData("NAME:VALUE")] // missing domain [InlineData(";NAME;VAL:UE")] // missing domain public void ThrowsForInvalidFormat(string flagValue) { var factory = new CookieActionFactory(); var ex = Assert.Throws(() => factory.Create(flagValue)); Assert.Equal(Resources.FormatError_InvalidChangeCookieFlag(flagValue), ex.Message); } [Theory] [InlineData("bad_number")] [InlineData("-1")] [InlineData("0.9")] public void ThrowsForInvalidIntFormat(string badInt) { var factory = new CookieActionFactory(); var ex = Assert.Throws(() => factory.Create("NAME:VALUE:DOMAIN:" + badInt)); Assert.Equal(Resources.FormatError_CouldNotParseInteger(badInt), ex.Message); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/FlagParserTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { public class FlagParserTest { [Fact] public void FlagParser_CheckSingleTerm() { var results = new FlagParser().Parse("[NC]"); var dict = new Dictionary(); dict.Add(FlagType.NoCase, string.Empty); var expected = new Flags(dict); Assert.True(DictionaryContentsEqual(expected.FlagDictionary, results.FlagDictionary)); } [Fact] public void FlagParser_CheckManyTerms() { var results = new FlagParser().Parse("[NC,F,L]"); var dict = new Dictionary(); dict.Add(FlagType.NoCase, string.Empty); dict.Add(FlagType.Forbidden, string.Empty); dict.Add(FlagType.Last, string.Empty); var expected = new Flags(dict); Assert.True(DictionaryContentsEqual(expected.FlagDictionary, results.FlagDictionary)); } [Fact] public void FlagParser_CheckManyTermsWithEquals() { var results = new FlagParser().Parse("[NC,F,R=301]"); var dict = new Dictionary(); dict.Add(FlagType.NoCase, string.Empty); dict.Add(FlagType.Forbidden, string.Empty); dict.Add(FlagType.Redirect, "301"); var expected = new Flags(dict); Assert.True(DictionaryContentsEqual(expected.FlagDictionary, results.FlagDictionary)); } [Theory] [InlineData("]", "Flags should start and end with square brackets: [flags]")] [InlineData("[", "Flags should start and end with square brackets: [flags]")] [InlineData("[R, L]", "Unrecognized flag: ' L'")] // cannot have spaces after , [InlineData("[RL]", "Unrecognized flag: 'RL'")] public void FlagParser_AssertFormatErrorWhenFlagsArePoorlyConstructed(string input, string expected) { var ex = Assert.Throws(() => new FlagParser().Parse(input)); Assert.Equal(expected, ex.Message); } [Fact] public void FlagParser_AssertArgumentExceptionWhenFlagsAreNullOrEmpty() { Assert.Throws(() => new FlagParser().Parse(null)); Assert.Throws(() => new FlagParser().Parse(string.Empty)); } [Theory] [InlineData("[CO=VAR:VAL]", "VAR:VAL")] [InlineData("[CO=!VAR]", "!VAR")] [InlineData("[CO=;NAME:VALUE;ABC:123]", ";NAME:VALUE;ABC:123")] public void Flag_ParserHandlesComplexFlags(string flagString, string expected) { var results = new FlagParser().Parse(flagString); string value; Assert.True(results.GetValue(FlagType.Cookie, out value)); Assert.Equal(expected, value); } public bool DictionaryContentsEqual(IDictionary dictionary, IDictionary other) { return (other ?? new Dictionary()) .OrderBy(kvp => kvp.Key) .SequenceEqual((dictionary ?? new Dictionary()) .OrderBy(kvp => kvp.Key)); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/FormatExceptionTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { public class FormatExceptionTests { [Theory] [InlineData(@"RewriteCond 1 2\", @"Invalid escaper character in string: RewriteCond 1 2\")] [InlineData("BadExpression 1 2 3 4", "Could not parse the mod_rewrite file. Message: 'Too many tokens on line'. Line number '1'.")] [InlineData("RewriteCond % 2", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %{ 2", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %{asdf} 2", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %z 2", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond $ 2", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond $z 2", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 !", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 >", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 >=", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 <", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 <=", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 =", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 -", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 -a", "Could not parse the mod_rewrite file. Line number '1'.")] [InlineData("RewriteCond %1 -getemp", "Could not parse the mod_rewrite file. Line number '1'.")] public void ThrowFormatExceptionWithCorrectMessage(string input, string expected) { // Arrange, Act, Assert var ex = Assert.Throws(() => new FileParser().Parse(new StringReader(input))); Assert.Equal(expected, ex.Message); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/ModRewriteMiddlewareTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { public class ModRewriteMiddlewareTest { [Fact] public async Task Invoke_RewritePathWhenMatching() { var options = new RewriteOptions().AddApacheModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("/hey/hello"); Assert.Equal("/hello", response); } [Fact] public async Task Invoke_RewritePathTerminatesOnFirstSuccessOfRule() { var options = new RewriteOptions().AddApacheModRewrite(new StringReader("RewriteRule /hey/(.*) /$1 [L]")) .AddApacheModRewrite(new StringReader("RewriteRule /hello /what")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("/hey/hello"); Assert.Equal("/hello", response); } [Fact] public async Task Invoke_RewritePathDoesNotTerminateOnFirstSuccessOfRule() { var options = new RewriteOptions().AddApacheModRewrite(new StringReader("RewriteRule /hey/(.*) /$1")) .AddApacheModRewrite(new StringReader("RewriteRule /hello /what")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("/hey/hello"); Assert.Equal("/what", response); } [Fact] public async Task Invoke_ShouldIgnoreComments() { var options = new RewriteOptions().AddApacheModRewrite(new StringReader("#RewriteRule ^/hey/(.*) /$1 ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("/hey/hello"); Assert.Equal("/hey/hello", response); } [Fact] public async Task Invoke_ShouldRewriteHomepage() { var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule ^/$ /homepage.html")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("http://www.foo.org/"); Assert.Equal("/homepage.html", response); } [Fact] public async Task Invoke_ShouldIgnorePorts() { var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule ^/$ /homepage.html")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("http://www.foo.org:42/"); Assert.Equal("/homepage.html", response); } [Fact] public async Task Invoke_HandleNegatedRewriteRules() { var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule !^/$ /homepage.html")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("http://www.foo.org/"); Assert.Equal("/", response); } [Theory] [InlineData("http://www.foo.org/homepage.aspx", @"RewriteRule (.*)\.aspx $1.php", "/homepage.php")] [InlineData("http://www.foo.org/pages/homepage.aspx", @"RewriteRule (.*)/(.*)\.aspx $2.php", "/homepage.php")] public async Task Invoke_BackReferencesShouldBeApplied(string url, string rule, string expected) { var options = new RewriteOptions().AddApacheModRewrite(new StringReader(rule)); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync(url); Assert.Equal(expected, response); } [Theory] [InlineData("http://www.foo.org/homepage.aspx", @"RewriteRule (.*)\.aspx $1.php", "/homepage.php")] [InlineData("http://www.foo.org/homepage.ASPX", @"RewriteRule (.*)\.aspx $1.php", "/homepage.ASPX")] [InlineData("http://www.foo.org/homepage.aspx", @"RewriteRule (.*)\.aspx $1.php [NC]", "/homepage.php")] [InlineData("http://www.foo.org/homepage.ASPX", @"RewriteRule (.*)\.aspx $1.php [NC]", "/homepage.php")] [InlineData("http://www.foo.org/homepage.aspx", @"RewriteRule (.*)\.aspx $1.php [nocase]", "/homepage.php")] [InlineData("http://www.foo.org/homepage.ASPX", @"RewriteRule (.*)\.aspx $1.php [nocase]", "/homepage.php")] public async Task Invoke_ShouldHandleFlagNoCase(string url, string rule, string expected) { var options = new RewriteOptions().AddApacheModRewrite(new StringReader(rule)); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync(url); Assert.Equal(expected, response); } [Fact] public async Task Invoke_CheckFullUrlWithOnlyPath() { var options = new RewriteOptions() .AddApacheModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("http://www.foo.org/blog/2016-jun"); Assert.Equal(@"/blog/2016-jun/", response); } [Fact] public async Task Invoke_CheckFullUrlWithUFlag() { var options = new RewriteOptions() .AddApacheModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Scheme + "://" + context.Request.Host.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("http://www.foo.org/blog/2016-jun"); Assert.Equal(@"http://www.example.com/blog/2016-jun/", response); } [Fact] public async Task Invoke_CheckModFileConditions() { var options = new RewriteOptions() .AddApacheModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Scheme + "://" + context.Request.Host.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("http://www.foo.org/blog/2016-jun"); Assert.Equal(@"http://www.example.com/blog/2016-jun/", response); } [Theory] [InlineData("http://www.example.com/foo/")] public async Task Invoke_EnsureHttps(string input) { var options = new RewriteOptions() .AddApacheModRewrite(new StringReader("RewriteCond %{REQUEST_URI} /foo/ \nRewriteCond %{HTTPS} !on \nRewriteRule ^(.*)$ https://www.example.com$1 [R=301,L]")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Scheme + "://" + context.Request.Host.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(input); Assert.Equal(response.StatusCode, (HttpStatusCode)301); Assert.Equal(@"https://www.example.com/foo/", response.Headers.Location.AbsoluteUri); } [Theory] [InlineData("http://www.example.com/")] public async Task Invoke_CaptureEmptyStringInRegexAssertRedirectLocationHasForwardSlash(string input) { var options = new RewriteOptions() .AddApacheModRewrite(new StringReader("RewriteRule ^(.*)$ $1 [R=301,L]")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Scheme + "://" + context.Request.Host.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(input); Assert.Equal(HttpStatusCode.MovedPermanently, response.StatusCode); Assert.Equal("/", response.Headers.Location.OriginalString); } [Theory] [InlineData("http://www.example.com/")] public async Task Invoke_CaptureEmptyStringInRegexAssertRewriteHasForwardSlash(string input) { var options = new RewriteOptions() .AddApacheModRewrite(new StringReader("RewriteRule ^(.*)$ $1 [L]")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync(input); Assert.Equal("/", response); } [Fact] public async Task Invoke_CaptureEmptyStringInRegexAssertLocationHeaderContainsPathBase() { var options = new RewriteOptions().AddApacheModRewrite(new StringReader(@"RewriteRule ^(.*)$ $1 [R=301,L]")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder) { BaseAddress = new Uri("http://localhost:5000/foo") }; var response = await server.CreateClient().GetAsync(""); Assert.Equal("/foo", response.Headers.Location.OriginalString); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/RewriteTokenizerTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { public class RewriteTokenizerTest { [Fact] public void Tokenize_RewriteCondtion() { var testString = "RewriteCond %{HTTPS} !-f"; var tokens = new Tokenizer().Tokenize(testString); var expected = new List(); expected.Add("RewriteCond"); expected.Add("%{HTTPS}"); expected.Add("!-f"); Assert.Equal(expected, tokens); } [Fact] public void Tokenize_CheckEscapedSpaceIgnored() { var testString = @"RewriteCond %{HTTPS}\ what !-f"; var tokens = new Tokenizer().Tokenize(testString); var expected = new List(); expected.Add("RewriteCond"); expected.Add(@"%{HTTPS} what"); expected.Add("!-f"); Assert.Equal(expected, tokens); } [Fact] public void Tokenize_CheckWhiteSpaceDirectlyFollowedByEscapeCharacter_CorrectSplit() { var testString = @"RewriteCond %{HTTPS} \ what !-f"; var tokens = new Tokenizer().Tokenize(testString); var expected = new List(); expected.Add(@"RewriteCond"); expected.Add(@"%{HTTPS}"); expected.Add(@" what"); expected.Add(@"!-f"); Assert.Equal(expected, tokens); } [Fact] public void Tokenize_CheckWhiteSpaceAtEndOfString_CorrectSplit() { var testString = @"RewriteCond %{HTTPS} \ what !-f "; var tokens = new Tokenizer().Tokenize(testString); var expected = new List(); expected.Add(@"RewriteCond"); expected.Add(@"%{HTTPS}"); expected.Add(@" what"); expected.Add(@"!-f"); Assert.Equal(expected, tokens); } [Fact] public void Tokenize_CheckQuotesAreProperlyRemovedFromString() { var testString = "RewriteCond \"%{HTTPS}\" \"\\ what\" \"!-f\" "; var tokens = new Tokenizer().Tokenize(testString); var expected = new List(); expected.Add(@"RewriteCond"); expected.Add(@"%{HTTPS}"); expected.Add(@" what"); expected.Add(@"!-f"); Assert.Equal(expected, tokens); } [Fact] public void Tokenize_AssertFormatExceptionWhenEscapeCharacterIsAtEndOfString() { var ex = Assert.Throws(() => new Tokenizer().Tokenize("\\")); Assert.Equal(@"Invalid escaper character in string: \", ex.Message); } [Fact] public void Tokenize_AssertFormatExceptionWhenUnevenNumberOfQuotes() { var ex = Assert.Throws(() => new Tokenizer().Tokenize("\"")); Assert.Equal("Mismatched number of quotes: \"", ex.Message); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/RuleBuilderTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests { public class RuleBuilderTest { [Fact] // see https://httpd.apache.org/docs/2.4/rewrite/advanced.html#setenvvars public void AddAction_Throws_ChangeEnvNotSupported() { var builder = new RuleBuilder(); var flags = new Flags(); flags.SetFlag(FlagType.Env, "rewritten:1"); var ex = Assert.Throws(() => builder.AddAction(null, flags)); Assert.Equal(Resources.Error_ChangeEnvironmentNotSupported, ex.Message); } [Fact] public void AddAction_DefaultRedirectStatusCode() { var builder = new RuleBuilder(); var flags = new Flags(); var pattern = new Pattern(new List()); flags.SetFlag(FlagType.Redirect, string.Empty); builder.AddAction(pattern, flags); var redirectAction = (RedirectAction)builder._actions[0]; Assert.Equal(StatusCodes.Status302Found, redirectAction.StatusCode); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/RuleRegexParserTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { public class RuleRegexParserTest { [Fact] public void RuleRegexParser_ShouldThrowOnNull() { Assert.Throws(() => new RuleRegexParser().ParseRuleRegex(null)); } [Fact] public void RuleRegexParser_ShouldThrowOnEmpty() { Assert.Throws(() => new RuleRegexParser().ParseRuleRegex(string.Empty)); } [Fact] public void RuleRegexParser_RegularRegexExpression() { var results = new RuleRegexParser().ParseRuleRegex("(.*)"); Assert.False(results.Invert); Assert.Equal("(.*)", results.Operand); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/ApacheModRewrite/TestStringParserTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.ApacheModRewrite; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite { public class TestStringParserTests { [Fact] public void ConditionParser_SingleServerVariable() { var serverVar = "%{HTTPS}"; var result = new TestStringParser().Parse(serverVar); var list = new List(); list.Add(new IsHttpsModSegment()); var expected = new Pattern(list); AssertPatternsEqual(expected, result); } [Fact] public void ConditionParser_MultipleServerVariables() { var serverVar = "%{HTTPS}%{REQUEST_URI}"; var result = new TestStringParser().Parse(serverVar); var list = new List(); list.Add(new IsHttpsModSegment()); list.Add(new UrlSegment()); var expected = new Pattern(list); AssertPatternsEqual(expected, result); } [Fact] public void ConditionParser_ParseLiteral() { var serverVar = "Hello!"; var result = new TestStringParser().Parse(serverVar); var list = new List(); list.Add(new LiteralSegment(serverVar)); var expected = new Pattern(list); AssertPatternsEqual(expected, result); } [Fact] public void ConditionParser_ParseConditionParameters() { var serverVar = "%1"; var result = new TestStringParser().Parse(serverVar); var list = new List(); list.Add(new ConditionMatchSegment(1)); var expected = new Pattern(list); AssertPatternsEqual(expected, result); } [Fact] public void ConditionParser_ParseMultipleConditionParameters() { var serverVar = "%1%2"; var result = new TestStringParser().Parse(serverVar); var list = new List(); list.Add(new ConditionMatchSegment(1)); list.Add(new ConditionMatchSegment(2)); var expected = new Pattern(list); AssertPatternsEqual(expected, result); } [Fact] public void ConditionParser_ParseRuleVariable() { var serverVar = "$1"; var result = new TestStringParser().Parse(serverVar); var list = new List(); list.Add(new RuleMatchSegment(1)); var expected = new Pattern(list); AssertPatternsEqual(expected, result); } [Fact] public void ConditionParser_ParseMultipleRuleVariables() { var serverVar = "$1$2"; var result = new TestStringParser().Parse(serverVar); var list = new List(); list.Add(new RuleMatchSegment(1)); list.Add(new RuleMatchSegment(2)); var expected = new Pattern(list); AssertPatternsEqual(expected, result); } [Fact] public void ConditionParser_ParserComplexRequest() { var serverVar = "%{HTTPS}/$1"; var result = new TestStringParser().Parse(serverVar); var list = new List(); list.Add(new IsHttpsModSegment()); list.Add(new LiteralSegment("/")); list.Add(new RuleMatchSegment(1)); var expected = new Pattern(list); AssertPatternsEqual(expected, result); } [Theory] [InlineData(@"%}", "Cannot parse '%}' to integer at string index: '1'")] // no } at end [InlineData(@"%{", "Missing close brace for parameter at string index: '2'")] // no closing } [InlineData(@"%a", "Cannot parse '%a' to integer at string index: '1'")] // invalid character after % [InlineData(@"$a", "Cannot parse '$a' to integer at string index: '1'")] // invalid character after $ [InlineData(@"%{asdf", "Missing close brace for parameter at string index: '6'")] // no closing } with characters public void ConditionParser_InvalidInput(string testString, string expected) { var ex = Assert.Throws(() => new TestStringParser().Parse(testString)); Assert.Equal(expected, ex.Message); } private void AssertPatternsEqual(Pattern p1, Pattern p2) { Assert.Equal(p1.PatternSegments.Count, p2.PatternSegments.Count); for (int i = 0; i < p1.PatternSegments.Count; i++) { var s1 = p1.PatternSegments[i]; var s2 = p2.PatternSegments[i]; Assert.Equal(s1.GetType(), s2.GetType()); } } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/FileParserTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { public class FileParserTests { [Fact] public void RuleParse_ParseTypicalRule() { // arrange var xml = @" "; var expected = new List(); expected.Add(CreateTestRule(new ConditionCollection(), url: "^article/([0-9]+)/([_0-9a-z-]+)", name: "Rewrite to article.aspx", actionType: ActionType.Rewrite, pattern: "article.aspx?id={R:1}&title={R:2}")); // act var res = new UrlRewriteFileParser().Parse(new StringReader(xml)); // assert AssertUrlRewriteRuleEquality(expected, res); } [Fact] public void RuleParse_ParseSingleRuleWithSingleCondition() { // arrange var xml = @" "; var condList = new ConditionCollection(); condList.Add(new Condition { Input = new InputParser().ParseInputString("{HTTPS}"), Match = new RegexMatch(new Regex("^OFF$"), false) }); var expected = new List(); expected.Add(CreateTestRule(condList, url: "^article/([0-9]+)/([_0-9a-z-]+)", name: "Rewrite to article.aspx", actionType: ActionType.Rewrite, pattern: "article.aspx?id={R:1}&title={R:2}")); // act var res = new UrlRewriteFileParser().Parse(new StringReader(xml)); // assert AssertUrlRewriteRuleEquality(expected, res); } [Fact] public void RuleParse_ParseMultipleRules() { // arrange var xml = @" "; var condList = new ConditionCollection(); condList.Add(new Condition { Input = new InputParser().ParseInputString("{HTTPS}"), Match = new RegexMatch(new Regex("^OFF$"), false) }); var expected = new List(); expected.Add(CreateTestRule(condList, url: "^article/([0-9]+)/([_0-9a-z-]+)", name: "Rewrite to article.aspx", actionType: ActionType.Rewrite, pattern: "article.aspx?id={R:1}&title={R:2}")); expected.Add(CreateTestRule(condList, url: "^article/([0-9]+)/([_0-9a-z-]+)", name: "Rewrite to another article.aspx", actionType: ActionType.Rewrite, pattern: "article.aspx?id={R:1}&title={R:2}")); // act var res = new UrlRewriteFileParser().Parse(new StringReader(xml)); // assert AssertUrlRewriteRuleEquality(expected, res); } [Fact] public void Should_parse_global_rules() { // arrange var xml = @" "; // act var rules = new UrlRewriteFileParser().Parse(new StringReader(xml)); // assert Assert.Equal(2, rules.Count); Assert.True(rules[0].Global); Assert.False(rules[1].Global); } // Creates a rule with appropriate default values of the url rewrite rule. private IISUrlRewriteRule CreateTestRule(ConditionCollection conditions, string name = "", bool enabled = true, PatternSyntax patternSyntax = PatternSyntax.ECMAScript, bool stopProcessing = false, string url = "", bool ignoreCase = true, bool negate = false, ActionType actionType = ActionType.None, string pattern = "", bool appendQueryString = false, bool rewrittenUrl = false, bool global = false, UriMatchPart uriMatchPart = UriMatchPart.Path, RedirectType redirectType = RedirectType.Permanent ) { return new IISUrlRewriteRule( name, new RegexMatch(new Regex("^OFF$"), negate), conditions, new RewriteAction(RuleResult.ContinueRules, new InputParser().ParseInputString(url, uriMatchPart), queryStringAppend: false), global); } // TODO make rules comparable? private void AssertUrlRewriteRuleEquality(IList actual, IList expected) { Assert.Equal(actual.Count, expected.Count); for (var i = 0; i < actual.Count; i++) { var r1 = actual[i]; var r2 = expected[i]; Assert.Equal(r1.Name, r2.Name); if (r1.Conditions == null) { Assert.Equal(0, r2.Conditions.Count); } else if (r2.Conditions == null) { Assert.Equal(0, r1.Conditions.Count); } else { Assert.Equal(r1.Conditions.Count, r2.Conditions.Count); for (var j = 0; j < r1.Conditions.Count; j++) { var c1 = r1.Conditions[j]; var c2 = r2.Conditions[j]; Assert.Equal(c1.Input.PatternSegments.Count, c2.Input.PatternSegments.Count); } } Assert.Equal(r1.Action.GetType(), r2.Action.GetType()); Assert.Equal(r1.InitialMatch.GetType(), r2.InitialMatch.GetType()); } } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/FormatExceptionHandlingTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { public class FormatExceptionHandlingTests { [Theory] [InlineData( @" ", "Match does not have an associated pattern attribute in condition")] [InlineData( @" ", "Match does not have an associated pattern attribute in condition")] [InlineData( @" ", "Missing close brace for parameter at string index: '6'")] [InlineData( @" ", "Missing close brace for parameter at string index: '1'")] public void ThrowFormatExceptionWithCorrectMessage(string input, string expected) { // Arrange, Act, Assert var ex = Assert.Throws(() => new UrlRewriteFileParser().Parse(new StringReader(input))); Assert.Equal(expected, ex.Message); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { public class InputParserTests { [Fact] public void InputParser_ParseLiteralString() { var testString = "hello/hey/what"; var result = new InputParser().ParseInputString(testString, UriMatchPart.Path); Assert.Equal(1, result.PatternSegments.Count); } [Theory] [InlineData("foo/bar/{R:1}/what", 3)] [InlineData("foo/{R:1}", 2)] [InlineData("foo/{R:1}/{C:2}", 4)] [InlineData("foo/{R:1}{C:2}", 3)] [InlineData("foo/", 1)] public void InputParser_ParseStringWithBackReference(string testString, int expected) { var result = new InputParser().ParseInputString(testString, UriMatchPart.Path); Assert.Equal(expected, result.PatternSegments.Count); } // Test actual evaluation of the types, verifying the correct string comes from the evalation [Theory] [InlineData("hey/hello/what", "hey/hello/what")] [InlineData("hey/{R:1}/what", "hey/foo/what")] [InlineData("hey/{R:2}/what", "hey/bar/what")] [InlineData("hey/{R:3}/what", "hey/baz/what")] [InlineData("hey/{C:1}/what", "hey/foo/what")] [InlineData("hey/{C:2}/what", "hey/bar/what")] [InlineData("hey/{C:3}/what", "hey/baz/what")] [InlineData("hey/{R:1}/{C:1}", "hey/foo/foo")] public void EvaluateBackReferenceRule(string testString, string expected) { var middle = new InputParser().ParseInputString(testString, UriMatchPart.Path); var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences()); Assert.Equal(expected, result); } [Theory] [InlineData("hey/{ToLower:HEY}", "hey/hey")] [InlineData("hey/{ToLower:{R:1}}", "hey/foo")] [InlineData("hey/{ToLower:{C:1}}", "hey/foo")] [InlineData("hey/{ToLower:{C:1}/what}", "hey/foo/what")] [InlineData("hey/ToLower:/what", "hey/ToLower:/what")] public void EvaluatToLowerRule(string testString, string expected) { var middle = new InputParser().ParseInputString(testString, UriMatchPart.Path); var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences()); Assert.Equal(expected, result); } [Theory] [InlineData("hey/{UrlEncode:}", "hey/%3Chey%3E")] public void EvaluatUriEncodeRule(string testString, string expected) { var middle = new InputParser().ParseInputString(testString, UriMatchPart.Path); var result = middle.Evaluate(CreateTestRewriteContext(), CreateTestRuleBackReferences(), CreateTestCondBackReferences()); Assert.Equal(expected, result); } [Theory] [InlineData("{")] [InlineData("{:}")] [InlineData("{R:")] [InlineData("{R:1")] [InlineData("{R:A}")] [InlineData("{R:10}")] [InlineData("{R:-1}")] [InlineData("{foo:1")] [InlineData("{UrlEncode:{R:}}")] [InlineData("{UrlEncode:{R:1}")] [InlineData("{HTTPS")] public void FormatExceptionsOnBadSyntax(string testString) { Assert.Throws(() => new InputParser().ParseInputString(testString, UriMatchPart.Path)); } [Fact] public void Should_throw_FormatException_if_no_rewrite_maps_are_defined() { Assert.Throws(() => new InputParser(null).ParseInputString("{apiMap:{R:1}}", UriMatchPart.Path)); } [Fact] public void Should_throw_FormatException_if_rewrite_map_not_found() { const string definedMapName = "testMap"; const string undefinedMapName = "apiMap"; var map = new IISRewriteMap(definedMapName); var maps = new IISRewriteMapCollection { map }; Assert.Throws(() => new InputParser(maps).ParseInputString($"{{{undefinedMapName}:{{R:1}}}}", UriMatchPart.Path)); } [Fact] public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result() { const string expectedMapName = "apiMap"; const string expectedKey = "api.test.com"; const string expectedValue = "test.com/api"; var map = new IISRewriteMap(expectedMapName); map[expectedKey] = expectedValue; var maps = new IISRewriteMapCollection { map }; var inputString = $"{{{expectedMapName}:{{R:1}}}}"; var pattern = new InputParser(maps).ParseInputString(inputString, UriMatchPart.Path); Assert.Equal(1, pattern.PatternSegments.Count); var segment = pattern.PatternSegments.Single(); var rewriteMapSegment = segment as RewriteMapSegment; Assert.NotNull(rewriteMapSegment); var result = rewriteMapSegment.Evaluate(CreateTestRewriteContext(), CreateRewriteMapRuleMatch(expectedKey).BackReferences, CreateRewriteMapConditionMatch(inputString).BackReferences); Assert.Equal(expectedValue, result); } private RewriteContext CreateTestRewriteContext() { var context = new DefaultHttpContext(); return new RewriteContext { HttpContext = context, StaticFileProvider = null, Logger = NullLogger.Instance }; } private BackReferenceCollection CreateTestRuleBackReferences() { var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); return new BackReferenceCollection(match.Groups); } private BackReferenceCollection CreateTestCondBackReferences() { var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); return new BackReferenceCollection(match.Groups); } private MatchResults CreateRewriteMapRuleMatch(string input) { var match = Regex.Match(input, "([^/]*)/?(.*)"); return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success }; } private MatchResults CreateRewriteMapConditionMatch(string input) { var match = Regex.Match(input, "(.+)"); return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success }; } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InvalidUrlRewriteFormatExceptionHandlingTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { public class InvalidUrlRewriteFormatExceptionHandlingTests { [Theory] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'Condition must have an associated match'. Line number '3': '10'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'Match must have Url Attribute'. Line number '4': '14'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'Conditions must have an input attribute'. Line number '6': '18'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'Url attribute cannot contain an empty string'. Line number '5': '14'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The redirectType parameter 'foo' was not recognized'. Line number '5': '14'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The type parameter 'foo' was not recognized'. Line number '5': '14'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The logicalGrouping parameter 'foo' was not recognized'. Line number '5': '14'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The patternSyntax parameter 'foo' was not recognized'. Line number '3': '10'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The matchType parameter 'foo' was not recognized'. Line number '6': '18'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The enabled parameter 'foo' was not recognized'. Line number '3': '10'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The stopProcessing parameter 'foo' was not recognized'. Line number '3': '10'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The ignoreCase parameter 'foo' was not recognized'. Line number '4': '14'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The ignoreCase parameter 'foo' was not recognized'. Line number '6': '18'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The negate parameter 'foo' was not recognized'. Line number '4': '14'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The negate parameter 'foo' was not recognized'. Line number '6': '18'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The trackAllCaptures parameter 'foo' was not recognized'. Line number '5': '14'.")] [InlineData( @" ", "Could not parse the UrlRewrite file. Message: 'The appendQueryString parameter 'foo' was not recognized'. Line number '5': '14'.")] public void ThrowInvalidUrlRewriteFormatExceptionWithCorrectMessage(string input, string expected) { // Arrange, Act, Assert var ex = Assert.Throws(() => new UrlRewriteFileParser().Parse(new StringReader(input))); Assert.Equal(expected, ex.Message); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; using Microsoft.AspNetCore.TestHost; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { public class MiddlewareTests { [Fact] public async Task Invoke_RedirectPathToPathAndQuery() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Response.Headers[HeaderNames.Location])); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("article/10/hey"); Assert.Equal("/article.aspx?id=10&title=hey", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RewritePathToPathAndQuery() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("/article/10/hey"); Assert.Equal("/article.aspx?id=10&title=hey", response); } [Fact] public async Task Invoke_RewriteBasedOnQueryStringParameters() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("page.asp?p2=321&p1=123"); Assert.Equal("/newpage.aspx?param1=123¶m2=321", response); } [Fact] public async Task Invoke_RedirectToLowerCase() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Response.Headers[HeaderNames.Location])); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("HElLo"); Assert.Equal("/hello", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RedirectRemoveTrailingSlash() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("hey/hello/"); Assert.Equal("/hey/hello", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RedirectAddTrailingSlash() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("hey/hello"); Assert.Equal("/hey/hello/", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RedirectToHttps() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri("http://example.com")); Assert.Equal("https://example.com/", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RewriteToHttps() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync(new Uri("http://example.com")); Assert.Equal("https://example.com/", response); } [Fact] public async Task Invoke_ReverseProxyToAnotherSite() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync(new Uri("http://example.com/")); Assert.Equal("http://internalserver/", response); } [Fact] public async Task Invoke_CaptureEmptyStringInRegexAssertRedirectLocationHasForwardSlash() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri("http://example.com/")); Assert.Equal("/", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_CaptureEmptyStringInRegexAssertRewriteLocationHasForwardSlash() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync(new Uri("http://example.com/")); Assert.Equal("/", response); } [Fact] public async Task Invoke_CaptureEmptyStringInRegexAssertLocationHeaderContainsPathBase() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder) { BaseAddress = new Uri("http://localhost:5000/foo") }; var response = await server.CreateClient().GetAsync(""); Assert.Equal("/foo", response.Headers.Location.OriginalString); } [Theory] [InlineData("IsFile")] [InlineData("isfile")] [InlineData("IsDirectory")] [InlineData("isdirectory")] public async Task VerifyIsFileAndIsDirectoryParsing(string matchType) { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader($@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("hey/hello"); Assert.Equal("/hey/hello/", response.Headers.Location.OriginalString); } [Fact] public async Task VerifyTrackAllCaptures() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("article/23?p1=123&p2=abc"); Assert.Equal("/blogposts/article/abc", response.Headers.Location.OriginalString); } [Fact] public async Task VerifyTrackAllCapturesRuleAndConditionCapture() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("article/23?p1=123&p2=abc"); Assert.Equal("/blog/article/23/abc", response.Headers.Location.OriginalString); } [Fact] public async Task ThrowIndexOutOfRangeExceptionWithCorrectMessage() { // Arrange, Act, Assert var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var ex = await Assert.ThrowsAsync(() => server.CreateClient().GetAsync("article/23?p1=123&p2=abc")); Assert.Equal("Cannot access back reference at index 9. Only 5 back references were captured.", ex.Message); } [Fact] public async Task Invoke_GlobalRuleConditionMatchesAgainstFullUri_ParsedRule() { // arrange var xml = @" "; var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(xml)); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); var server = new TestServer(builder); // act var response = await server.CreateClient().GetStringAsync($"http://localhost/{Guid.NewGuid()}/foo/bar"); // assert Assert.Equal("http://www.test.com/foo/bar", response); } [Theory] [InlineData("http://fetch.environment.local/dev/path", "http://1.1.1.1/path")] [InlineData("http://fetch.environment.local/qa/path", "http://fetch.environment.local/qa/path")] public async Task Invoke_ReverseProxyToAnotherSiteUsingXmlConfiguredRewriteMap(string requestUri, string expectedRewrittenUri) { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync(new Uri(requestUri)); Assert.Equal(expectedRewrittenUri, response); } [Fact] public async Task Invoke_CustomResponse() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("article/10/hey"); var content = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); Assert.Equal("reason", response.ReasonPhrase); Assert.Equal("description", content); } [Theory] [InlineData(@"^http://localhost(/.*)", "http://localhost/foo/bar", UriMatchPart.Path)] [InlineData(@"^http://localhost(/.*)", "http://www.test.com/foo/bar", UriMatchPart.Full)] public async Task Invoke_GlobalRuleConditionMatchesAgainstFullUri_CodedRule(string conditionInputPattern, string expectedResult, UriMatchPart uriMatchPart) { // arrange var inputParser = new InputParser(); var ruleBuilder = new UrlRewriteRuleBuilder { Name = "test", Global = false }; ruleBuilder.AddUrlMatch(".*"); var condition = new UriMatchCondition( inputParser, "{REQUEST_URI}", conditionInputPattern, uriMatchPart, ignoreCase: true, negate: false); ruleBuilder.ConfigureConditionBehavior(LogicalGrouping.MatchAll, trackAllCaptures: true); ruleBuilder.AddUrlCondition(condition); var action = new RewriteAction( RuleResult.SkipRemainingRules, inputParser.ParseInputString(@"http://www.test.com{C:1}", uriMatchPart), queryStringAppend: false); ruleBuilder.AddUrlAction(action); var options = new RewriteOptions().Add(ruleBuilder.Build()); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); var server = new TestServer(builder); // act var response = await server.CreateClient().GetStringAsync("http://localhost/foo/bar"); // assert Assert.Equal(expectedResult, response); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.IISUrlRewrite { public class RewriteMapParserTests { [Fact] public void Should_parse_rewrite_map() { // arrange const string expectedMapName = "apiMap"; const string expectedKey = "api.test.com"; const string expectedValue = "test.com/api"; var xml = $@" "; // act var xmlDoc = XDocument.Load(new StringReader(xml), LoadOptions.SetLineInfo); var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); var actualMaps = RewriteMapParser.Parse(xmlRoot); // assert Assert.Equal(1, actualMaps.Count); var actualMap = actualMaps[expectedMapName]; Assert.NotNull(actualMap); Assert.Equal(expectedMapName, actualMap.Name); Assert.Equal(expectedValue, actualMap[expectedKey]); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/ServerVariableTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { public class ServerVariableTests { [Theory] [InlineData("CONTENT_LENGTH", "10", UriMatchPart.Path)] [InlineData("CONTENT_TYPE", "json", UriMatchPart.Path)] [InlineData("HTTP_ACCEPT", "accept", UriMatchPart.Path)] [InlineData("HTTP_COOKIE", "cookie", UriMatchPart.Path)] [InlineData("HTTP_HOST", "example.com", UriMatchPart.Path)] [InlineData("HTTP_REFERER", "referer", UriMatchPart.Path)] [InlineData("HTTP_USER_AGENT", "useragent", UriMatchPart.Path)] [InlineData("HTTP_CONNECTION", "connection", UriMatchPart.Path)] [InlineData("HTTP_URL", "/foo", UriMatchPart.Path)] [InlineData("HTTP_URL", "http://example.com/foo?bar=1", UriMatchPart.Full)] [InlineData("QUERY_STRING", "bar=1", UriMatchPart.Path)] [InlineData("REQUEST_FILENAME", "/foo", UriMatchPart.Path)] [InlineData("REQUEST_URI", "/foo", UriMatchPart.Path)] [InlineData("REQUEST_URI", "http://example.com/foo?bar=1", UriMatchPart.Full)] [InlineData("REQUEST_METHOD", "GET", UriMatchPart.Full)] public void CheckServerVariableParsingAndApplication(string variable, string expected, UriMatchPart uriMatchPart) { // Arrange and Act var testParserContext = new ParserContext("test"); var serverVar = ServerVariables.FindServerVariable(variable, testParserContext, uriMatchPart); var lookup = serverVar.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch().BackReferences, CreateTestCondMatch().BackReferences); // Assert Assert.Equal(expected, lookup); } private RewriteContext CreateTestHttpContext() { var context = new DefaultHttpContext(); context.Request.Method = HttpMethods.Get; context.Request.Scheme = "http"; context.Request.Host = new HostString("example.com"); context.Request.Path = PathString.FromUriComponent("/foo"); context.Request.QueryString = QueryString.FromUriComponent("?bar=1"); context.Request.ContentLength = 10; context.Request.ContentType = "json"; context.Request.Headers[HeaderNames.Accept] = "accept"; context.Request.Headers[HeaderNames.Cookie] = "cookie"; context.Request.Headers[HeaderNames.Referer] = "referer"; context.Request.Headers[HeaderNames.UserAgent] = "useragent"; context.Request.Headers[HeaderNames.Connection] = "connection"; return new RewriteContext { HttpContext = context }; } private MatchResults CreateTestRuleMatch() { var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success }; } private MatchResults CreateTestCondMatch() { var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success }; } [Fact] private void EmptyQueryStringCheck() { var context = new DefaultHttpContext(); var rewriteContext = new RewriteContext { HttpContext = context }; var testParserContext = new ParserContext("test"); var serverVar = ServerVariables.FindServerVariable("QUERY_STRING", testParserContext, UriMatchPart.Path); var lookup = serverVar.Evaluate(rewriteContext, CreateTestRuleMatch().BackReferences, CreateTestCondMatch().BackReferences); Assert.Equal(string.Empty, lookup); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/UrlRewriteApplicationTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { // TODO add more of these public class UrlRewriteApplicationTests { [Fact] public void ApplyRule_AssertStopProcessingFlagWillTerminateOnNoAction() { var xml = new StringReader(@" "); var rules = new UrlRewriteFileParser().Parse(xml); Assert.Equal(1, rules.Count); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; rules.FirstOrDefault().ApplyRule(context); Assert.Equal(RuleResult.SkipRemainingRules, context.Result); } [Fact] public void ApplyRule_AssertNoTerminateFlagWillNotTerminateOnNoAction() { var xml = new StringReader(@" "); var rules = new UrlRewriteFileParser().Parse(xml); Assert.Equal(1, rules.Count); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; rules.FirstOrDefault().ApplyRule(context); Assert.Equal(RuleResult.ContinueRules, context.Result); } [Fact] public void ApplyRule_TrackAllCaptures() { var xml = new StringReader(@" "); var rules = new UrlRewriteFileParser().Parse(xml); Assert.Equal(1, rules.Count); Assert.True(rules[0].Conditions.TrackAllCaptures); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; rules.FirstOrDefault().ApplyRule(context); Assert.Equal(RuleResult.ContinueRules, context.Result); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/Microsoft.AspNetCore.Rewrite.Tests.csproj ================================================  $(StandardTestTfms) ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/MiddlewareTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules { public class MiddlewareTests { [Fact] public async Task CheckRewritePath() { var options = new RewriteOptions().AddRewrite("(.*)", "http://example.com/$1", skipRemainingRules: false); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync("foo"); Assert.Equal("http://example.com/foo", response); } [Fact] public async Task CheckRedirectPath() { var options = new RewriteOptions().AddRedirect("(.*)", "http://example.com/$1", statusCode: StatusCodes.Status301MovedPermanently); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("foo"); Assert.Equal("http://example.com/foo", response.Headers.Location.OriginalString); } [Fact] public async Task RewriteRulesCanComeFromConfigureOptions() { var builder = new WebHostBuilder() .ConfigureServices(services => { services.Configure(options => { options.AddRedirect("(.*)", "http://example.com/$1", statusCode: StatusCodes.Status301MovedPermanently); }); }) .Configure(app => { app.UseRewriter(); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("foo"); Assert.Equal("http://example.com/foo", response.Headers.Location.OriginalString); } [Fact] public async Task CheckRedirectPathWithQueryString() { var options = new RewriteOptions().AddRedirect("(.*)", "http://example.com/$1", statusCode: StatusCodes.Status301MovedPermanently); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("foo?bar=1"); Assert.Equal("http://example.com/foo?bar=1", response.Headers.Location.OriginalString); } [Theory] [InlineData(StatusCodes.Status301MovedPermanently)] [InlineData(StatusCodes.Status302Found)] [InlineData(StatusCodes.Status307TemporaryRedirect)] [InlineData(StatusCodes.Status308PermanentRedirect)] public async Task CheckRedirectToHttps(int statusCode) { var options = new RewriteOptions().AddRedirectToHttps(statusCode: statusCode); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri("http://example.com")); Assert.Equal("https://example.com/", response.Headers.Location.OriginalString); Assert.Equal(statusCode, (int)response.StatusCode); } [Fact] public async Task CheckPermanentRedirectToHttps() { var options = new RewriteOptions().AddRedirectToHttpsPermanent(); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri("http://example.com")); Assert.Equal("https://example.com/", response.Headers.Location.OriginalString); Assert.Equal(StatusCodes.Status301MovedPermanently, (int)response.StatusCode); } [Theory] [InlineData(25, "https://example.com:25/")] [InlineData(-25, "https://example.com/")] public async Task CheckRedirectToHttpsWithSslPort(int sslPort, string expected) { var options = new RewriteOptions().AddRedirectToHttps(statusCode: StatusCodes.Status301MovedPermanently, sslPort: sslPort); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri("http://example.com")); Assert.Equal(expected, response.Headers.Location.OriginalString); Assert.Equal(StatusCodes.Status301MovedPermanently, (int)response.StatusCode); } [Theory] [InlineData(StatusCodes.Status301MovedPermanently)] [InlineData(StatusCodes.Status302Found)] [InlineData(StatusCodes.Status307TemporaryRedirect)] [InlineData(StatusCodes.Status308PermanentRedirect)] public async Task CheckRedirectToWwwWithStatusCode(int statusCode) { var options = new RewriteOptions().AddRedirectToWww(statusCode: statusCode); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri("https://example.com")); Assert.Equal("https://www.example.com/", response.Headers.Location.OriginalString); Assert.Equal(statusCode, (int)response.StatusCode); } [Theory] [InlineData("http://example.com", "http://www.example.com/")] [InlineData("https://example.com", "https://www.example.com/")] [InlineData("http://example.com:8081", "http://www.example.com:8081/")] [InlineData("http://example.com:8081/example?q=1", "http://www.example.com:8081/example?q=1")] public async Task CheckRedirectToWww(string requestUri, string redirectUri) { var options = new RewriteOptions().AddRedirectToWww(); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri(requestUri)); Assert.Equal(redirectUri, response.Headers.Location.OriginalString); Assert.Equal(StatusCodes.Status307TemporaryRedirect, (int)response.StatusCode); } [Fact] public async Task CheckPermanentRedirectToWww() { var options = new RewriteOptions().AddRedirectToWwwPermanent(); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri("https://example.com")); Assert.Equal("https://www.example.com/", response.Headers.Location.OriginalString); Assert.Equal(StatusCodes.Status308PermanentRedirect, (int)response.StatusCode); } [Theory] [InlineData("http://www.example.com")] [InlineData("https://www.example.com")] [InlineData("http://www.example.com:8081")] [InlineData("https://www.example.com:8081")] [InlineData("https://www.example.com:8081/example?q=1")] [InlineData("http://localhost")] [InlineData("https://localhost")] [InlineData("http://localhost:8081")] [InlineData("https://localhost:8081")] [InlineData("https://localhost:8081/example?q=1")] public async Task CheckNoRedirectToWww(string requestUri) { var options = new RewriteOptions().AddRedirectToWww(); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(new Uri(requestUri)); Assert.Null(response.Headers.Location); } [Fact] public async Task CheckIfEmptyStringRedirectCorrectly() { var options = new RewriteOptions().AddRedirect("(.*)", "$1", statusCode: StatusCodes.Status301MovedPermanently); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync(""); Assert.Equal("/", response.Headers.Location.OriginalString); } [Fact] public async Task CheckIfEmptyStringRewriteCorrectly() { var options = new RewriteOptions().AddRewrite("(.*)", "$1", skipRemainingRules: false); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); var response = await server.CreateClient().GetStringAsync(""); Assert.Equal("/", response); } [Fact] public async Task SettingPathBase() { var options = new RewriteOptions().AddRedirect("(.*)", "$1"); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder) { BaseAddress = new Uri("http://localhost:5000/foo") }; var response = await server.CreateClient().GetAsync(""); Assert.Equal("/foo", response.Headers.Location.OriginalString); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ConditionMatchSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text.RegularExpressions; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class ConditionMatchSegmentTests { [Theory] [InlineData(1, "foo")] [InlineData(2, "bar")] [InlineData(3, "baz")] public void ConditionMatch_AssertBackreferencesObtainsCorrectValue(int index, string expected) { // Arrange var condMatch = CreateTestMatch(); var segment = new ConditionMatchSegment(index); // Act var results = segment.Evaluate(null, null, condMatch.BackReferences); // Assert Assert.Equal(expected, results); } private static MatchResults CreateTestMatch() { var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success }; } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/DateTimeSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class DateTimeSegmentTests { [Theory] [InlineData("TIME_YEAR")] [InlineData("TIME_MON")] [InlineData("TIME_DAY")] [InlineData("TIME_HOUR")] [InlineData("TIME_MIN")] [InlineData("TIME_SEC")] [InlineData("TIME_WDAY")] [InlineData("TIME")] public void DateTime_AssertDoesntThrowOnCheckOfSegment(string input) { // Arrange var segment = new DateTimeSegment(input); // Act var results = segment.Evaluate(null, null, null); // TODO testing dates is hard, could use moq // currently just assert that the segment doesn't throw. } [Theory] [InlineData("foo", "Unsupported segment: 'foo'")] [InlineData("wow", "Unsupported segment: 'wow'")] public void DateTime_AssertThrowsOnInvalidInput(string input, string expected) { // Act And Assert var ex = Assert.Throws(() => new DateTimeSegment(input)); Assert.Equal(expected, ex.Message); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/HeaderSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class HeaderSegmentTests { [Fact] public void HeaderSegment_AssertGettingWithHeaderReturnsCorrectValue() { // Arrange var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Request.Headers[HeaderNames.Location] = "foo"; var segment = new HeaderSegment(HeaderNames.Location); // Act var results = segment.Evaluate(context, null, null); // Assert Assert.Equal("foo", results); } [Fact] public void HeaderSegment_AssertGettingANonExistantHeaderReturnsNull() { // Arrange var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var segment = new HeaderSegment(HeaderNames.Location); // Act var results = segment.Evaluate(context, null, null); // Assert Assert.Null(results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsHttpsModSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class IsHttpsModSegmentTests { [Theory] [InlineData("http", "off")] [InlineData("https", "on")] public void IsHttps_AssertCorrectBehaviorWhenProvidedHttpContext(string input, string expected) { // Arrange var segement = new IsHttpsModSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Request.Scheme = input; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal(expected, results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsHttpsSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class IsHttpsSegmentTests { [Theory] [InlineData("http", "OFF")] [InlineData("https", "ON")] public void IsHttps_AssertCorrectBehaviorWhenProvidedHttpContext(string input, string expected) { // Arrange var segement = new IsHttpsUrlSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Request.Scheme = input; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal(expected, results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/IsIPV6SegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class IsIPV6SegmentTests { [Fact] public void IsIPv6_AssertNullRemoteIpAddressReportsCorrectValue() { // Arrange var segement = new IsIPV6Segment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.RemoteIpAddress = null; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("off", results); } [Fact] public void IsIPv6_AssertCorrectBehaviorWhenIPv6IsUsed() { // Arrange var segement = new IsIPV6Segment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("on", results); } [Fact] public void IsIPv6_AssertCorrectBehaviorWhenIPv4IsUsed() { // Arrange var segement = new IsIPV6Segment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("20.30.40.50"); // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("off", results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LIteralSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class LiteralSegmentTests { [Fact] public void LiteralSegment_AssertSegmentIsCorrect() { // Arrange var segement = new LiteralSegment("foo"); // Act var results = segement.Evaluate(null, null, null); // Assert Assert.Equal("foo", results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LocalAddressSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class LocalAddressSegmentTests { [Fact] public void LocalAddress_AssertSegmentIsCorrect() { // Arrange var segement = new LocalAddressSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.LocalIpAddress = IPAddress.Parse("20.30.40.50"); // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("20.30.40.50", results); } [Fact] public void LocalAddress_AssertNullLocalIpAddressReturnsNull() { var segement = new LocalAddressSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.LocalIpAddress = null; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Null( results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/LocalPortSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class LocalPortSegmentTests { [Fact] public void LocalPortSegment_AssertSegmentIsCorrect() { // Arrange var segement = new LocalPortSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.LocalPort = 800; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("800", results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/QueryStringSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class QueryStringSegmentTests { [Fact] public void QueryString_AssertSegmentIsCorrect() { var segement = new QueryStringSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Request.QueryString = new QueryString("?hey=1"); var results = segement.Evaluate(context, null, null); Assert.Equal("hey=1", results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RemoteAddressSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class RemoteAddressSegmentTests { [Fact] public void RemoteAddress_AssertSegmentIsCorrect() { // Arrange var segement = new RemoteAddressSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.RemoteIpAddress = IPAddress.Parse("20.30.40.50"); // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("20.30.40.50", results); } [Fact] public void RemoteAddress_AssertNullLocalIpAddressReturnsNull() { var segement = new RemoteAddressSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.RemoteIpAddress = null; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Null(results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RemotePortSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class RemotePortSegmentTests { [Fact] public void RemotePort_AssertSegmentIsCorrect() { // Arrange var segement = new RemotePortSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Connection.RemotePort = 800; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("800", results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RequestFilenameSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class RequestFilenameSegmentTests { [Fact] public void RequestFilename_AssertSegmentIsCorrect() { // Arrange var segement = new RequestFileNameSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Request.Path = new PathString("/foo/bar"); // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("/foo/bar", results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RequestMethodSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class RequestMethodSegmentTests { [Fact] public void RequestMethod_AssertSegmentIsCorrect() { // Arrange var segement = new RequestMethodSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Request.Method = HttpMethods.Get; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal(HttpMethods.Get, results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/RuleMatchSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text.RegularExpressions; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class RuleMatchSegmentTests { [Theory] [InlineData(1, "foo")] [InlineData(2, "bar")] [InlineData(3, "baz")] public void RuleMatch_AssertBackreferencesObtainsCorrectValue(int index, string expected) { // Arrange var ruleMatch = CreateTestMatch(); var segment = new RuleMatchSegment(index); // Act var results = segment.Evaluate(null, ruleMatch.BackReferences, null); // Assert Assert.Equal(expected, results); } private static MatchResults CreateTestMatch() { var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success }; } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/SchemeSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class SchemeSegmentTests { [Fact] public void SchemeSegment_AssertSegmentIsCorrect() { // Arrange var segement = new SchemeSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Request.Scheme = "http"; // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("http", results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ServerProtocolSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class ServerProtocolSegmentTests { [Fact] public void ServerProtocol_AssertSegmentIsCorrect() { // Arrange var segement = new ServerProtocolSegment(); var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; context.HttpContext.Features.Set(new HttpRequestFeature { Protocol = "http" }); // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal("http", results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/ToLowerSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class ToLowerSegmentTests { [Theory] [InlineData("Hello", "hello")] [InlineData("WHAT", "what")] [InlineData("hey", "hey")] public void ToLower_AssertLowerCaseWorksAppropriately(string input, string expected) { // Arrange var pattern = new Pattern(new List()); pattern.PatternSegments.Add(new LiteralSegment(input)); var segement = new ToLowerSegment(pattern); var context = new RewriteContext(); // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal(expected, results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/UrlEncodeSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class UrlEncodeSegmentTests { [Theory] [InlineData(" ", "%20")] [InlineData("x&y", "x%26y")] [InlineData("hey", "hey")] public void ToLower_AssertLowerCaseWorksAppropriately(string input, string expected) { // Arrange var pattern = new Pattern(new List()); pattern.PatternSegments.Add(new LiteralSegment(input)); var segement = new UrlEncodeSegment(pattern); var context = new RewriteContext(); // Act var results = segement.Evaluate(context, null, null); // Assert Assert.Equal(expected, results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/PatternSegments/UrlSegmentTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.PatternSegments { public class UrlSegmentTests { [Theory] [InlineData("http", "localhost", 80, null, UriMatchPart.Path, "")] [InlineData("http", "localhost", 80, "", UriMatchPart.Path, "")] [InlineData("http", "localhost", 80, "/foo/bar", UriMatchPart.Path, "/foo/bar")] [InlineData("http", "localhost", 80, "/foo:bar", UriMatchPart.Path, "/foo:bar")] [InlineData("http", "localhost", 80, "/foo bar", UriMatchPart.Path, "/foo%20bar")] [InlineData("http", "localhost", 80, null, UriMatchPart.Full, "http://localhost:80/")] [InlineData("http", "localhost", 80, "", UriMatchPart.Full, "http://localhost:80/")] [InlineData("http", "localhost", 80, "/foo:bar", UriMatchPart.Full, "http://localhost:80/foo:bar")] [InlineData("http", "localhost", 80, "/foo bar", UriMatchPart.Full, "http://localhost:80/foo%20bar")] [InlineData("http", "localhost", 80, "/foo/bar", UriMatchPart.Full, "http://localhost:80/foo/bar")] [InlineData("http", "localhost", 81, "/foo/bar", UriMatchPart.Full, "http://localhost:81/foo/bar")] [InlineData("https", "localhost", 443, "/foo/bar", UriMatchPart.Full, "https://localhost:443/foo/bar")] public void AssertSegmentIsCorrect(string scheme, string host, int port, string path, UriMatchPart uriMatchPart, string expectedResult) { // Arrange var httpContext = new DefaultHttpContext(); httpContext.Request.Scheme = scheme; httpContext.Request.Host = new HostString(host, port); httpContext.Request.Path = new PathString(path); var context = new RewriteContext { HttpContext = httpContext }; context.HttpContext = httpContext; // Act var segment = new UrlSegment(uriMatchPart); var results = segment.Evaluate(context, null, null); // Assert Assert.Equal(expectedResult, results); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/AbortActionTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions { public class AbortActionTests { public void AbortAction_VerifyEndResponseResult() { var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var action = new AbortAction(); action.ApplyAction(context, null, null); Assert.Equal(RuleResult.EndResponse, context.Result); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ChangeCookieActionTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions { public class ChangeCookieActionTests { [Fact] public void SetsCookie() { var now = DateTimeOffset.UtcNow; var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var action = new ChangeCookieAction("Cookie", () => now) { Value = "Chocolate Chip", Domain = "contoso.com", Lifetime = TimeSpan.FromMinutes(1440), Path = "/recipes", Secure = true, HttpOnly = true }; action.ApplyAction(context, null, null); var cookieHeaders = context.HttpContext.Response.Headers[HeaderNames.SetCookie]; var header = Assert.Single(cookieHeaders); Assert.Equal($"Cookie=Chocolate%20Chip; expires={HeaderUtilities.FormatDate(now.AddMinutes(1440))}; domain=contoso.com; path=/recipes; secure; samesite=lax; httponly", header); } [Fact] public void ZeroLifetime() { var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var action = new ChangeCookieAction("Cookie") { Value = "Chocolate Chip", }; action.ApplyAction(context, null, null); var cookieHeaders = context.HttpContext.Response.Headers[HeaderNames.SetCookie]; var header = Assert.Single(cookieHeaders); Assert.Equal($"Cookie=Chocolate%20Chip; samesite=lax", header); } [Fact] public void UnsetCookie() { var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var action = new ChangeCookieAction("Cookie"); action.ApplyAction(context, null, null); var cookieHeaders = context.HttpContext.Response.Headers[HeaderNames.SetCookie]; var header = Assert.Single(cookieHeaders); Assert.Equal($"Cookie=; samesite=lax", header); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/ForbiddenActionTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions { public class ForbiddenActionTests { [Fact] public void Forbidden_Verify403IsInStatusCode() { var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var action = new ForbiddenAction(); action.ApplyAction(context, null, null); Assert.Equal(RuleResult.EndResponse, context.Result); Assert.Equal(StatusCodes.Status403Forbidden, context.HttpContext.Response.StatusCode); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/UrlActions/GoneActionTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlActions { public class GoneActionTests { [Fact] public void Gone_Verify410IsInStatusCode() { var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var action = new GoneAction(); action.ApplyAction(context, null, null); Assert.Equal(RuleResult.EndResponse, context.Result); Assert.Equal(StatusCodes.Status410Gone, context.HttpContext.Response.StatusCode); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/UrlMatches/ExactMatchTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlMatches { public class ExactMatchTests { [Theory] [InlineDataAttribute(true,"string",false,"string",true)] [InlineDataAttribute(true, "string", true, "string", false)] [InlineDataAttribute(false, "STRING", false, "string",false)] [InlineDataAttribute(false, "STRING", true, "string", true)] public void ExactMatch_Case_Sensitivity_Negate_Tests(bool ignoreCase, string inputString, bool negate, string pattern, bool expectedResult) { var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var Match = new ExactMatch(ignoreCase, inputString, negate); var matchResults = Match.Evaluate(pattern, context); Assert.Equal(expectedResult, matchResults.Success); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/UrlMatches/IntegerMatchTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlMatches { public class IntegerMatchTests { [Fact] public void IntegerMatch_Constructor_Integer_Parse_Excetion() { var ex = Assert.Throws(() => new IntegerMatch("Not an int", IntegerOperationType.Equal)); Assert.Equal(ex.Message, Resources.Error_IntegerMatch_FormatExceptionMessage); } [Theory] [InlineData(1,IntegerOperationType.Equal,"1",true)] [InlineData(1, IntegerOperationType.NotEqual, "2", true)] [InlineData(2, IntegerOperationType.Less, "1", true)] [InlineData(1, IntegerOperationType.LessEqual, "2", false)] [InlineData(1, IntegerOperationType.Greater, "2", true)] [InlineData(2, IntegerOperationType.GreaterEqual, "1", false)] [InlineData(1, IntegerOperationType.Equal, "Not an int", false)] [InlineData(1, IntegerOperationType.Equal, "", false)] [InlineData(1, IntegerOperationType.Equal, "2147483648", false)] public void IntegerMatch_Evaluation_Cases_Tests(int value,IntegerOperationType operation, string input,bool expectedResult) { var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var integerMatch = new IntegerMatch(value, operation); var matchResult = integerMatch.Evaluate(input, context); Assert.Equal(expectedResult, matchResult.Success); } } } ================================================ FILE: test/Microsoft.AspNetCore.Rewrite.Tests/UrlMatches/StringMatchTests.cs ================================================ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlMatches { public class StringMatchTests { [Theory] [InlineData("hi", StringOperationType.Equal,true,"hi",true)] [InlineData("a", StringOperationType.Greater, true, "b", true)] [InlineData("a", StringOperationType.GreaterEqual, true, "b", true)] [InlineData("b", StringOperationType.Less,true, "a", true)] [InlineData("b", StringOperationType.LessEqual, true, "a", true)] [InlineData("", StringOperationType.Equal, true, "", true)] [InlineData(null, StringOperationType.Equal, true, null, true)] public void StringMatch_Evaluation_Check_Cases(string value, StringOperationType operation, bool ignoreCase, string input, bool expectedResult) { var context = new RewriteContext { HttpContext = new DefaultHttpContext() }; var stringMatch = new StringMatch(value, operation, ignoreCase); var matchResult = stringMatch.Evaluate(input, context); Assert.Equal(expectedResult, matchResult.Success); } } } ================================================ FILE: version.props ================================================  3.0.0 alpha1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000 a- $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) $(VersionSuffix)-$(BuildNumber) 0.6.0 alpha1 $(ExperimentalVersionPrefix) $(ExperimentalVersionPrefix)-$(ExperimentalVersionSuffix)-final $(ExperimentalVersionSuffix)-$(BuildNumber)