From 09f492e4a6b0dc29bce116825474e5e3b9a59003 Mon Sep 17 00:00:00 2001 From: Mikael Mengistu Date: Fri, 26 Jun 2015 10:33:06 -0700 Subject: [PATCH] Added a middleware to handle header overrides. Initial check in. --- .gitattributes | 50 +++++ .gitignore | 28 +++ .travis.yml | 4 + BasicMiddleware.sln | 41 ++++ NuGet.Config | 7 + appveyor.yml | 7 + build.cmd | 28 +++ build.sh | 39 ++++ global.json | 3 + makefile.shade | 8 + .../ForwardedHeaders.cs | 17 ++ .../HttpMethodOverrideExtensions.cs | 31 +++ .../HttpMethodOverrideMiddleware.cs | 51 +++++ .../Microsoft.AspNet.HttpOverrides.xproj | 17 ++ .../OverrideHeaderExtensions.cs | 21 ++ .../OverrideHeaderMiddleware.cs | 68 ++++++ .../OverrideHeaderOptions.cs | 10 + .../project.json | 19 ++ .../HttpMethodOverrideMiddlewareTest.cs | 73 +++++++ ...Microsoft.AspNet.HttpOverrides.Tests.xproj | 20 ++ .../OverrideHeaderMiddlewareTest.cs | 202 ++++++++++++++++++ .../project.json | 23 ++ 22 files changed, 767 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 BasicMiddleware.sln create mode 100644 NuGet.Config create mode 100644 appveyor.yml create mode 100644 build.cmd create mode 100644 build.sh create mode 100644 global.json create mode 100644 makefile.shade create mode 100644 src/Microsoft.AspNet.HttpOverrides/ForwardedHeaders.cs create mode 100644 src/Microsoft.AspNet.HttpOverrides/HttpMethodOverrideExtensions.cs create mode 100644 src/Microsoft.AspNet.HttpOverrides/HttpMethodOverrideMiddleware.cs create mode 100644 src/Microsoft.AspNet.HttpOverrides/Microsoft.AspNet.HttpOverrides.xproj create mode 100644 src/Microsoft.AspNet.HttpOverrides/OverrideHeaderExtensions.cs create mode 100644 src/Microsoft.AspNet.HttpOverrides/OverrideHeaderMiddleware.cs create mode 100644 src/Microsoft.AspNet.HttpOverrides/OverrideHeaderOptions.cs create mode 100644 src/Microsoft.AspNet.HttpOverrides/project.json create mode 100644 test/Microsoft.AspNet.HttpOverrides.Tests/HttpMethodOverrideMiddlewareTest.cs create mode 100644 test/Microsoft.AspNet.HttpOverrides.Tests/Microsoft.AspNet.HttpOverrides.Tests.xproj create mode 100644 test/Microsoft.AspNet.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs create mode 100644 test/Microsoft.AspNet.HttpOverrides.Tests/project.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..bdaa5ba982 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,50 @@ +*.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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..566f474fe8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +[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/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..947bf868ee --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: csharp +sudo: false +script: + - ./build.sh --quiet verify \ No newline at end of file diff --git a/BasicMiddleware.sln b/BasicMiddleware.sln new file mode 100644 index 0000000000..ace965aa26 --- /dev/null +++ b/BasicMiddleware.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23018.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.HttpOverrides", "src\Microsoft.AspNet.HttpOverrides\Microsoft.AspNet.HttpOverrides.xproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A5076D28-FA7E-4606-9410-FEDD0D603527}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8437B0F3-3894-4828-A945-A9187F37631D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.HttpOverrides.Tests", "test\Microsoft.AspNet.HttpOverrides.Tests\Microsoft.AspNet.HttpOverrides.Tests.xproj", "{D6341B92-3416-4F11-8DF4-CB274296175F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99B72A07-32D6-434D-B44D-D064E3C13E08}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +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 + 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} + EndGlobalSection +EndGlobal diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000000..da57d47267 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..636a7618d3 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,7 @@ +init: + - git config --global core.autocrlf true +build_script: + - build.cmd --quiet verify +clone_depth: 1 +test: off +deploy: off \ No newline at end of file diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000000..41025afb26 --- /dev/null +++ b/build.cmd @@ -0,0 +1,28 @@ +@echo off +cd %~dp0 + +SETLOCAL +SET CACHED_NUGET=%LocalAppData%\NuGet\NuGet.exe + +IF EXIST %CACHED_NUGET% goto copynuget +echo Downloading latest version of NuGet.exe... +IF NOT EXIST %LocalAppData%\NuGet md %LocalAppData%\NuGet +@powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://www.nuget.org/nuget.exe' -OutFile '%CACHED_NUGET%'" + +:copynuget +IF EXIST .nuget\nuget.exe goto restore +md .nuget +copy %CACHED_NUGET% .nuget\nuget.exe > nul + +:restore +IF EXIST packages\KoreBuild goto run +.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre +.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion + +IF "%SKIP_DNX_INSTALL%"=="1" goto run +CALL packages\KoreBuild\build\dnvm upgrade -runtime CLR -arch x86 +CALL packages\KoreBuild\build\dnvm install default -runtime CoreCLR -arch x86 + +:run +CALL packages\KoreBuild\build\dnvm use default -runtime CLR -arch x86 +packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %* diff --git a/build.sh b/build.sh new file mode 100644 index 0000000000..3ef874f9bd --- /dev/null +++ b/build.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +if test `uname` = Darwin; then + cachedir=~/Library/Caches/KBuild +else + if [ -z $XDG_DATA_HOME ]; then + cachedir=$HOME/.local/share + else + cachedir=$XDG_DATA_HOME; + fi +fi +mkdir -p $cachedir + +url=https://www.nuget.org/nuget.exe + +if test ! -f $cachedir/nuget.exe; then + wget -O $cachedir/nuget.exe $url 2>/dev/null || curl -o $cachedir/nuget.exe --location $url /dev/null +fi + +if test ! -e .nuget; then + mkdir .nuget + cp $cachedir/nuget.exe .nuget/nuget.exe +fi + +if test ! -d packages/KoreBuild; then + mono .nuget/nuget.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre + mono .nuget/nuget.exe install Sake -version 0.2 -o packages -ExcludeVersion +fi + +if ! type dnvm > /dev/null 2>&1; then + source packages/KoreBuild/build/dnvm.sh +fi + +if ! type dnx > /dev/null 2>&1; then + dnvm upgrade +fi + +mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@" + diff --git a/global.json b/global.json new file mode 100644 index 0000000000..b0323e4281 --- /dev/null +++ b/global.json @@ -0,0 +1,3 @@ +{ + "projects": [ "src" ] +} \ No newline at end of file diff --git a/makefile.shade b/makefile.shade new file mode 100644 index 0000000000..a612302092 --- /dev/null +++ b/makefile.shade @@ -0,0 +1,8 @@ +use namespace="System.Net" + +var VERSION='0.1' +var FULL_VERSION='0.1' +var AUTHORS='Microsoft Open Technologies, Inc.' + +use-standard-lifecycle +k-standard-goals \ No newline at end of file diff --git a/src/Microsoft.AspNet.HttpOverrides/ForwardedHeaders.cs b/src/Microsoft.AspNet.HttpOverrides/ForwardedHeaders.cs new file mode 100644 index 0000000000..5b5916212e --- /dev/null +++ b/src/Microsoft.AspNet.HttpOverrides/ForwardedHeaders.cs @@ -0,0 +1,17 @@ +// 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.AspNet.HttpOverrides +{ + [Flags] + public enum ForwardedHeaders + { + None = 0, + XForwardedFor = 1 << 0, + XForwardedHost = 1 << 1, + XForwardedProto = 1 << 2, + All = XForwardedFor | XForwardedHost | XForwardedProto + } +} diff --git a/src/Microsoft.AspNet.HttpOverrides/HttpMethodOverrideExtensions.cs b/src/Microsoft.AspNet.HttpOverrides/HttpMethodOverrideExtensions.cs new file mode 100644 index 0000000000..42ae6e93b8 --- /dev/null +++ b/src/Microsoft.AspNet.HttpOverrides/HttpMethodOverrideExtensions.cs @@ -0,0 +1,31 @@ +// 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.AspNet.HttpOverrides; + +namespace Microsoft.AspNet.Builder +{ + public static class HttpMethodOverrideExtensions + { + /// + /// Allows incoming POST request to override method type with type specified in header. + /// + /// + /// + public static IApplicationBuilder UseHttpMethodOverride(this IApplicationBuilder builder) + { + return builder.Use(next => new HttpMethodOverrideMiddleware(next).Invoke); + } + + /// + /// Allows incoming POST request to override method type with type specified in form. + /// + /// + /// Denotes the element that contains the name of the resulting method type. + /// + public static IApplicationBuilder UseHttpMethodOverride(this IApplicationBuilder builder, string formFieldInput) + { + return builder.Use(next => new HttpMethodOverrideMiddleware(next, formFieldInput).Invoke); + } + } +} diff --git a/src/Microsoft.AspNet.HttpOverrides/HttpMethodOverrideMiddleware.cs b/src/Microsoft.AspNet.HttpOverrides/HttpMethodOverrideMiddleware.cs new file mode 100644 index 0000000000..8d805d80b8 --- /dev/null +++ b/src/Microsoft.AspNet.HttpOverrides/HttpMethodOverrideMiddleware.cs @@ -0,0 +1,51 @@ +// 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.AspNet.Builder; +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.HttpOverrides +{ + public class HttpMethodOverrideMiddleware + { + private const string xHttpMethodOverride = "X-Http-Method-Override"; + private readonly RequestDelegate _next; + private readonly string _formFieldName; + + public HttpMethodOverrideMiddleware(RequestDelegate next, string formFieldName = null) + { + _next = next; + _formFieldName = formFieldName; + } + + public async Task Invoke(HttpContext context) + { + if (string.Equals(context.Request.Method,"POST", StringComparison.OrdinalIgnoreCase)) + { + if (_formFieldName != null) + { + if (context.Request.HasFormContentType) + { + var form = await context.Request.ReadFormAsync(); + var methodType = form[_formFieldName]; + if (!string.IsNullOrEmpty(methodType)) + { + context.Request.Method = methodType; + } + } + } + else + { + var xHttpMethodOverrideValue = context.Request.Headers.Get(xHttpMethodOverride); + if (!string.IsNullOrEmpty(xHttpMethodOverrideValue)) + { + context.Request.Method = xHttpMethodOverrideValue; + } + } + } + await _next(context); + } + } +} diff --git a/src/Microsoft.AspNet.HttpOverrides/Microsoft.AspNet.HttpOverrides.xproj b/src/Microsoft.AspNet.HttpOverrides/Microsoft.AspNet.HttpOverrides.xproj new file mode 100644 index 0000000000..0037db59b2 --- /dev/null +++ b/src/Microsoft.AspNet.HttpOverrides/Microsoft.AspNet.HttpOverrides.xproj @@ -0,0 +1,17 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 517308c3-b477-4b01-b461-cab9c10b6928 + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderExtensions.cs b/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderExtensions.cs new file mode 100644 index 0000000000..04acee970c --- /dev/null +++ b/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderExtensions.cs @@ -0,0 +1,21 @@ +// 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.AspNet.HttpOverrides; + +namespace Microsoft.AspNet.Builder +{ + public static class OverrideHeaderExtensions + { + /// + /// Forwards proxied headers onto current request + /// + /// + /// Enables the different override options. + /// + public static IApplicationBuilder UseOverrideHeaders(this IApplicationBuilder builder, OverrideHeaderMiddlewareOptions options) + { + return builder.Use(next => new OverrideHeaderMiddleware(next, options).Invoke); + } + } +} diff --git a/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderMiddleware.cs b/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderMiddleware.cs new file mode 100644 index 0000000000..0071ac0da5 --- /dev/null +++ b/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderMiddleware.cs @@ -0,0 +1,68 @@ +// 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.Net; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.HttpOverrides +{ + public class OverrideHeaderMiddleware + { + private const string XForwardedForHeaderName = "X-Forwarded-For"; + private const string XForwardedHostHeaderName = "X-Forwarded-Host"; + private const string XForwardedProtoHeaderName = "X-Forwarded-Proto"; + private readonly OverrideHeaderMiddlewareOptions _options; + private readonly RequestDelegate _next; + + public OverrideHeaderMiddleware([NotNull] RequestDelegate next, [NotNull] OverrideHeaderMiddlewareOptions options) + { + _options = options; + _next = next; + } + + public Task Invoke(HttpContext context) + { + if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedFor) != 0) + { + var xForwardedForHeaderValue = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName); + if (xForwardedForHeaderValue != null && xForwardedForHeaderValue.Count > 0) + { + IPAddress originalIPAddress; + if (IPAddress.TryParse(xForwardedForHeaderValue[0], out originalIPAddress)) + { + if (context.Connection.RemoteIpAddress != null) + { + var ipList = context.Request.Headers.Get(XForwardedForHeaderName); + context.Request.Headers.Set(XForwardedForHeaderName, (ipList + "," + context.Connection.RemoteIpAddress.ToString())); + } + context.Connection.RemoteIpAddress = originalIPAddress; + } + } + } + + if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedHost) != 0) + { + var xForwardHostHeaderValue = context.Request.Headers.Get(XForwardedHostHeaderName); + if (!string.IsNullOrEmpty(xForwardHostHeaderValue)) + { + context.Request.Host = HostString.FromUriComponent(xForwardHostHeaderValue); + } + } + + if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedProto) != 0) + { + var xForwardProtoHeaderValue = context.Request.Headers.Get(XForwardedProtoHeaderName); + if (!string.IsNullOrEmpty(xForwardProtoHeaderValue)) + { + context.Request.Scheme = xForwardProtoHeaderValue; + } + } + + return _next(context); + } + } +} diff --git a/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderOptions.cs b/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderOptions.cs new file mode 100644 index 0000000000..428408ab25 --- /dev/null +++ b/src/Microsoft.AspNet.HttpOverrides/OverrideHeaderOptions.cs @@ -0,0 +1,10 @@ +// 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.AspNet.HttpOverrides +{ + public class OverrideHeaderMiddlewareOptions + { + public ForwardedHeaders ForwardedOptions { get; set; } + } +} diff --git a/src/Microsoft.AspNet.HttpOverrides/project.json b/src/Microsoft.AspNet.HttpOverrides/project.json new file mode 100644 index 0000000000..39c4937409 --- /dev/null +++ b/src/Microsoft.AspNet.HttpOverrides/project.json @@ -0,0 +1,19 @@ +{ + "version": "1.0.0-*", + "description": "XForward and Method override middlewares for ASP.NET", + "dependencies": { + "Microsoft.CSharp": "4.0.0-*", + "Microsoft.AspNet.Http.Abstractions": "1.0.0-*", + "Microsoft.Framework.NotNullAttribute.Sources": { "version": "1.0.0-*", "type": "build" } + }, + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + "System.Collections": "4.0.10-*", + "System.Linq": "4.0.0-*", + "System.Threading": "4.0.10-*" + } + } + } +} diff --git a/test/Microsoft.AspNet.HttpOverrides.Tests/HttpMethodOverrideMiddlewareTest.cs b/test/Microsoft.AspNet.HttpOverrides.Tests/HttpMethodOverrideMiddlewareTest.cs new file mode 100644 index 0000000000..de987a4e63 --- /dev/null +++ b/test/Microsoft.AspNet.HttpOverrides.Tests/HttpMethodOverrideMiddlewareTest.cs @@ -0,0 +1,73 @@ +// 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.Http; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Xunit; + +namespace Microsoft.AspNet.HttpOverrides +{ + public class HttpMethodOverrideMiddlewareTest + { + [Fact] + public async Task XHttpMethodOverrideHeaderAvaiableChangesRequestMethod() + { + var assertsExecuted = false; + var server = TestServer.Create(app => + { + app.UseHttpMethodOverride(); + app.Run(context => + { + assertsExecuted = true; + Assert.Equal("DELETE", context.Request.Method); + return Task.FromResult(0); + }); + }); + + 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 server = TestServer.Create(app => + { + app.UseHttpMethodOverride(); + app.Run(context => + { + Assert.Equal("POST",context.Request.Method); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var req = new HttpRequestMessage(HttpMethod.Post, ""); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task XHttpMethodOverrideFromGetRequestDoesntChangeMethodType() + { + var assertsExecuted = false; + var server = TestServer.Create(app => + { + app.UseHttpMethodOverride(); + app.Run(context => + { + Assert.Equal("GET", context.Request.Method); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var req = new HttpRequestMessage(HttpMethod.Get, ""); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + } +} diff --git a/test/Microsoft.AspNet.HttpOverrides.Tests/Microsoft.AspNet.HttpOverrides.Tests.xproj b/test/Microsoft.AspNet.HttpOverrides.Tests/Microsoft.AspNet.HttpOverrides.Tests.xproj new file mode 100644 index 0000000000..9889be51ad --- /dev/null +++ b/test/Microsoft.AspNet.HttpOverrides.Tests/Microsoft.AspNet.HttpOverrides.Tests.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + d6341b92-3416-4f11-8df4-cb274296175f + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs b/test/Microsoft.AspNet.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs new file mode 100644 index 0000000000..003033f5a1 --- /dev/null +++ b/test/Microsoft.AspNet.HttpOverrides.Tests/OverrideHeaderMiddlewareTest.cs @@ -0,0 +1,202 @@ +// 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.Http; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Xunit; + +namespace Microsoft.AspNet.HttpOverrides +{ + public class OverrideMiddlewareHeaderTests + { + [Fact] + public async Task XForwardedForOverrideChangesRemoteIp() + { + var assertsExecuted = false; + var options = new OverrideHeaderMiddlewareOptions(); + options.ForwardedOptions = ForwardedHeaders.XForwardedFor; + + var server = TestServer.Create(app => + { + app.UseOverrideHeaders(options); + app.Run(context => + { + Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp() + { + var assertsExecuted = false; + var options = new OverrideHeaderMiddlewareOptions(); + options.ForwardedOptions = ForwardedHeaders.XForwardedFor; + + var server = TestServer.Create(app => + { + app.UseOverrideHeaders(options); + app.Run(context => + { + Assert.Null(context.Connection.RemoteIpAddress); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "BAD-IP"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task XForwardedHostOverrideChangesRequestHost() + { + var assertsExecuted = false; + var options = new OverrideHeaderMiddlewareOptions(); + options.ForwardedOptions = ForwardedHeaders.XForwardedHost; + + var server = TestServer.Create(app => + { + app.UseOverrideHeaders(options); + app.Run(context => + { + Assert.Equal("testHost", context.Request.Host.ToString()); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-Host", "testHost"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task XForwardedProtoOverrideChangesRequestProtocol() + { + var assertsExecuted = false; + var options = new OverrideHeaderMiddlewareOptions(); + options.ForwardedOptions = ForwardedHeaders.XForwardedProto; + + var server = TestServer.Create(app => + { + app.UseOverrideHeaders(options); + app.Run(context => + { + Assert.Equal("TestProtocol", context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-Proto", "TestProtocol"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public void AllForwardsDisabledByDefault() + { + var options = new OverrideHeaderMiddlewareOptions(); + Assert.True(options.ForwardedOptions == 0); + } + + [Fact] + public async Task AllForwardsEnabledChangeRequestRemoteIpHostandProtocol() + { + var assertsExecuted = false; + var options = new OverrideHeaderMiddlewareOptions(); + options.ForwardedOptions = ForwardedHeaders.All; + + var server = TestServer.Create(app => + { + app.UseOverrideHeaders(options); + app.Run(context => + { + Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); + Assert.Equal("testHost", context.Request.Host.ToString()); + Assert.Equal("Protocol", context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11"); + req.Headers.Add("X-Forwarded-Host", "testHost"); + req.Headers.Add("X-Forwarded-Proto", "Protocol"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task AllOptionsDisabledRequestDoesntChange() + { + var assertsExecuted = false; + var options = new OverrideHeaderMiddlewareOptions(); + options.ForwardedOptions = ForwardedHeaders.None; + + var server = TestServer.Create(app => + { + app.UseOverrideHeaders(options); + app.Run(context => + { + Assert.Null(context.Connection.RemoteIpAddress); + Assert.Equal("localhost", context.Request.Host.ToString()); + Assert.Equal("http", context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11"); + req.Headers.Add("X-Forwarded-Host", "otherhost"); + req.Headers.Add("X-Forwarded-Proto", "Protocol"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task PartiallyEnabledForwardsPartiallyChangesRequest() + { + var assertsExecuted = false; + var XForwardedForAndProto = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + var options = new OverrideHeaderMiddlewareOptions(); + options.ForwardedOptions = XForwardedForAndProto; + + var server = TestServer.Create(app => + { + app.UseOverrideHeaders(options); + app.Run(context => + { + Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString()); + Assert.Equal("localhost", context.Request.Host.ToString()); + Assert.Equal("Protocol", context.Request.Scheme); + assertsExecuted = true; + return Task.FromResult(0); + + }); + }); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.Add("X-Forwarded-For", "11.111.111.11"); + req.Headers.Add("X-Forwarded-Proto", "Protocol"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.HttpOverrides.Tests/project.json b/test/Microsoft.AspNet.HttpOverrides.Tests/project.json new file mode 100644 index 0000000000..a83e0c4bc7 --- /dev/null +++ b/test/Microsoft.AspNet.HttpOverrides.Tests/project.json @@ -0,0 +1,23 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.HttpOverrides": "1.0.0-*", + "Microsoft.AspNet.TestHost": "1.0.0-*", + "Microsoft.Framework.Logging.Testing": "1.0.0-*", + "xunit.runner.aspnet": "2.0.0-aspnet-*" + }, + "commands": { + "test": "xunit.runner.aspnet" + }, + + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + "System.Collections": "4.0.10-*", + "System.Linq": "4.0.0-*", + "System.Threading": "4.0.10-*" + } + } + } +}