BasicMiddleware 266 B

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. commit 2b80c905549110c312644400b7eea32119009710
  2. Author: Chris Ross (ASP.NET) <[email protected]>
  3. Date: Mon Feb 5 15:38:55 2018 -0800
  4. Add host filtering middleware
  5. diff --git a/BasicMiddleware.sln b/BasicMiddleware.sln
  6. index a4452cd103a..8810201f5ae 100644
  7. --- a/BasicMiddleware.sln
  8. +++ b/BasicMiddleware.sln
  9. @@ -1,6 +1,6 @@
  10. Microsoft Visual Studio Solution File, Format Version 12.00
  11. # Visual Studio 15
  12. -VisualStudioVersion = 15.0.26817.0
  13. +VisualStudioVersion = 15.0.27130.2027
  14. MinimumVisualStudioVersion = 15.0.26730.03
  15. Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOverrides", "src\Microsoft.AspNetCore.HttpOverrides\Microsoft.AspNetCore.HttpOverrides.csproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}"
  16. EndProject
  17. @@ -63,6 +63,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpsPolicySample", "sample
  18. EndProject
  19. 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}"
  20. EndProject
  21. +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostFilteringSample", "samples\HostFilteringSample\HostFilteringSample.csproj", "{368B00A2-992A-4B0E-9085-A8136A22922D}"
  22. +EndProject
  23. +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5CEA6F31-A829-4A02-8CD5-EC3DDD4CC1EA}"
  24. + ProjectSection(SolutionItems) = preProject
  25. + build\dependencies.props = build\dependencies.props
  26. + build\repo.props = build\repo.props
  27. + build\sources.props = build\sources.props
  28. + EndProjectSection
  29. +EndProject
  30. +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}"
  31. +EndProject
  32. +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HostFiltering", "src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj", "{762F7276-C916-4111-A6C0-41668ABB3823}"
  33. +EndProject
  34. Global
  35. GlobalSection(SolutionConfigurationPlatforms) = preSolution
  36. Debug|Any CPU = Debug|Any CPU
  37. @@ -129,6 +142,18 @@ Global
  38. {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
  39. {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
  40. {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.Build.0 = Release|Any CPU
  41. + {368B00A2-992A-4B0E-9085-A8136A22922D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  42. + {368B00A2-992A-4B0E-9085-A8136A22922D}.Debug|Any CPU.Build.0 = Debug|Any CPU
  43. + {368B00A2-992A-4B0E-9085-A8136A22922D}.Release|Any CPU.ActiveCfg = Release|Any CPU
  44. + {368B00A2-992A-4B0E-9085-A8136A22922D}.Release|Any CPU.Build.0 = Release|Any CPU
  45. + {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  46. + {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Debug|Any CPU.Build.0 = Debug|Any CPU
  47. + {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Release|Any CPU.ActiveCfg = Release|Any CPU
  48. + {4BC947ED-13B8-4BE6-82A4-96A48D86980B}.Release|Any CPU.Build.0 = Release|Any CPU
  49. + {762F7276-C916-4111-A6C0-41668ABB3823}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  50. + {762F7276-C916-4111-A6C0-41668ABB3823}.Debug|Any CPU.Build.0 = Debug|Any CPU
  51. + {762F7276-C916-4111-A6C0-41668ABB3823}.Release|Any CPU.ActiveCfg = Release|Any CPU
  52. + {762F7276-C916-4111-A6C0-41668ABB3823}.Release|Any CPU.Build.0 = Release|Any CPU
  53. EndGlobalSection
  54. GlobalSection(SolutionProperties) = preSolution
  55. HideSolutionNode = FALSE
  56. @@ -149,6 +174,10 @@ Global
  57. {4D39C29B-4EC8-497C-B411-922DA494D71B} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
  58. {AC424AEE-4883-49C6-945F-2FC916B8CA1C} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
  59. {1C67B0F1-6E70-449E-A2F1-98B9D5C576CE} = {8437B0F3-3894-4828-A945-A9187F37631D}
  60. + {368B00A2-992A-4B0E-9085-A8136A22922D} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
  61. + {5CEA6F31-A829-4A02-8CD5-EC3DDD4CC1EA} = {59A9B64C-E9BE-409E-89A2-58D72E2918F5}
  62. + {4BC947ED-13B8-4BE6-82A4-96A48D86980B} = {8437B0F3-3894-4828-A945-A9187F37631D}
  63. + {762F7276-C916-4111-A6C0-41668ABB3823} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
  64. EndGlobalSection
  65. GlobalSection(ExtensibilityGlobals) = postSolution
  66. SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158}
  67. diff --git a/build/dependencies.props b/build/dependencies.props
  68. index 97d18628138..4dadaa5ffab 100644
  69. --- a/build/dependencies.props
  70. +++ b/build/dependencies.props
  71. @@ -14,6 +14,7 @@
  72. <MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview2-30281</MicrosoftAspNetCoreTestHostPackageVersion>
  73. <MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
  74. <MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationBinderPackageVersion>
  75. + <MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsConfigurationJsonPackageVersion>
  76. <MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
  77. <MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
  78. <MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview2-30281</MicrosoftExtensionsLoggingConsolePackageVersion>
  79. diff --git a/samples/HostFilteringSample/HostFilteringSample.csproj b/samples/HostFilteringSample/HostFilteringSample.csproj
  80. new file mode 100644
  81. index 00000000000..7818be1fb04
  82. --- /dev/null
  83. +++ b/samples/HostFilteringSample/HostFilteringSample.csproj
  84. @@ -0,0 +1,29 @@
  85. +<Project Sdk="Microsoft.NET.Sdk.Web">
  86. +
  87. + <PropertyGroup>
  88. + <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
  89. + </PropertyGroup>
  90. +
  91. + <ItemGroup>
  92. + <ProjectReference Include="..\..\src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj" />
  93. + </ItemGroup>
  94. +
  95. + <ItemGroup>
  96. + <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
  97. + <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
  98. + <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
  99. + </ItemGroup>
  100. +
  101. + <ItemGroup>
  102. + <Content Update="appsettings.Development.json">
  103. + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  104. + </Content>
  105. + <Content Update="appsettings.json">
  106. + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  107. + </Content>
  108. + <Content Update="appsettings.Production.json">
  109. + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  110. + </Content>
  111. + </ItemGroup>
  112. +
  113. +</Project>
  114. diff --git a/samples/HostFilteringSample/Program.cs b/samples/HostFilteringSample/Program.cs
  115. new file mode 100644
  116. index 00000000000..0d4ffa9324c
  117. --- /dev/null
  118. +++ b/samples/HostFilteringSample/Program.cs
  119. @@ -0,0 +1,37 @@
  120. +// Copyright (c) .NET Foundation. All rights reserved.
  121. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  122. +
  123. +using Microsoft.AspNetCore.Hosting;
  124. +using Microsoft.Extensions.Configuration;
  125. +using Microsoft.Extensions.Logging;
  126. +
  127. +namespace HostFilteringSample
  128. +{
  129. + public class Program
  130. + {
  131. + public static void Main(string[] args)
  132. + {
  133. + BuildWebHost(args).Run();
  134. + }
  135. +
  136. + public static IWebHost BuildWebHost(string[] args)
  137. + {
  138. + var hostBuilder = new WebHostBuilder()
  139. + .ConfigureLogging((_, factory) =>
  140. + {
  141. + factory.SetMinimumLevel(LogLevel.Debug);
  142. + factory.AddConsole();
  143. + })
  144. + .ConfigureAppConfiguration((hostingContext, config) =>
  145. + {
  146. + var env = hostingContext.HostingEnvironment;
  147. + config.AddJsonFile("appsettings.json", optional: true)
  148. + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
  149. + })
  150. + .UseKestrel()
  151. + .UseStartup<Startup>();
  152. +
  153. + return hostBuilder.Build();
  154. + }
  155. + }
  156. +}
  157. diff --git a/samples/HostFilteringSample/Properties/launchSettings.json b/samples/HostFilteringSample/Properties/launchSettings.json
  158. new file mode 100644
  159. index 00000000000..a0ee1d227c4
  160. --- /dev/null
  161. +++ b/samples/HostFilteringSample/Properties/launchSettings.json
  162. @@ -0,0 +1,27 @@
  163. +{
  164. + "iisSettings": {
  165. + "windowsAuthentication": false,
  166. + "anonymousAuthentication": true,
  167. + "iisExpress": {
  168. + "applicationUrl": "http://localhost:14124/",
  169. + "sslPort": 0
  170. + }
  171. + },
  172. + "profiles": {
  173. + "IIS Express": {
  174. + "commandName": "IISExpress",
  175. + "launchBrowser": true,
  176. + "environmentVariables": {
  177. + "ASPNETCORE_ENVIRONMENT": "Development"
  178. + }
  179. + },
  180. + "HostFilteringSample": {
  181. + "commandName": "Project",
  182. + "launchBrowser": true,
  183. + "environmentVariables": {
  184. + "ASPNETCORE_ENVIRONMENT": "Development"
  185. + },
  186. + "applicationUrl": "http://localhost:14125/"
  187. + }
  188. + }
  189. +}
  190. \ No newline at end of file
  191. diff --git a/samples/HostFilteringSample/Startup.cs b/samples/HostFilteringSample/Startup.cs
  192. new file mode 100644
  193. index 00000000000..93c217b71c4
  194. --- /dev/null
  195. +++ b/samples/HostFilteringSample/Startup.cs
  196. @@ -0,0 +1,41 @@
  197. +// Copyright (c) .NET Foundation. All rights reserved.
  198. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  199. +
  200. +using System.Collections.Generic;
  201. +using Microsoft.AspNetCore.Builder;
  202. +using Microsoft.AspNetCore.Hosting;
  203. +using Microsoft.AspNetCore.Http;
  204. +using Microsoft.Extensions.Configuration;
  205. +using Microsoft.Extensions.DependencyInjection;
  206. +
  207. +namespace HostFilteringSample
  208. +{
  209. + public class Startup
  210. + {
  211. + public IConfiguration Config { get; }
  212. +
  213. + public Startup(IConfiguration config)
  214. + {
  215. + Config = config;
  216. + }
  217. +
  218. + public void ConfigureServices(IServiceCollection services)
  219. + {
  220. + services.AddHostFiltering(options =>
  221. + {
  222. + // If this is excluded then it will fall back to the server's addresses
  223. + options.AllowedHosts = Config.GetSection("AllowedHosts").Get<List<string>>();
  224. + });
  225. + }
  226. +
  227. + public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  228. + {
  229. + app.UseHostFiltering();
  230. +
  231. + app.Run(context =>
  232. + {
  233. + return context.Response.WriteAsync("Hello World! " + context.Request.Host);
  234. + });
  235. + }
  236. + }
  237. +}
  238. diff --git a/samples/HostFilteringSample/appsettings.Development.json b/samples/HostFilteringSample/appsettings.Development.json
  239. new file mode 100644
  240. index 00000000000..7c2d8e26dc2
  241. --- /dev/null
  242. +++ b/samples/HostFilteringSample/appsettings.Development.json
  243. @@ -0,0 +1,3 @@
  244. +{
  245. + "AllowedHosts": [ "localhost", "127.0.0.1", "[::1]" ]
  246. +}
  247. diff --git a/samples/HostFilteringSample/appsettings.Production.json b/samples/HostFilteringSample/appsettings.Production.json
  248. new file mode 100644
  249. index 00000000000..f2fc90c3900
  250. --- /dev/null
  251. +++ b/samples/HostFilteringSample/appsettings.Production.json
  252. @@ -0,0 +1,3 @@
  253. +{
  254. + "AllowedHosts": [ "example.com", "localhost" ]
  255. +}
  256. diff --git a/samples/HostFilteringSample/appsettings.json b/samples/HostFilteringSample/appsettings.json
  257. new file mode 100644
  258. index 00000000000..29091fa5825
  259. --- /dev/null
  260. +++ b/samples/HostFilteringSample/appsettings.json
  261. @@ -0,0 +1,3 @@
  262. +{
  263. +
  264. +}
  265. \ No newline at end of file
  266. diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringBuilderExtensions.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringBuilderExtensions.cs
  267. new file mode 100644
  268. index 00000000000..b8722de1c7f
  269. --- /dev/null
  270. +++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringBuilderExtensions.cs
  271. @@ -0,0 +1,32 @@
  272. +// Copyright (c) .NET Foundation. All rights reserved.
  273. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  274. +
  275. +using System;
  276. +using Microsoft.AspNetCore.HostFiltering;
  277. +
  278. +namespace Microsoft.AspNetCore.Builder
  279. +{
  280. + /// <summary>
  281. + /// Extension methods for the HostFiltering middleware.
  282. + /// </summary>
  283. + public static class HostFilteringBuilderExtensions
  284. + {
  285. + /// <summary>
  286. + /// Adds middleware for filtering requests by allowed host headers. Invalid requests will be rejected with a
  287. + /// 400 status code.
  288. + /// </summary>
  289. + /// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
  290. + /// <returns>The original <see cref="IApplicationBuilder"/>.</returns>
  291. + public static IApplicationBuilder UseHostFiltering(this IApplicationBuilder app)
  292. + {
  293. + if (app == null)
  294. + {
  295. + throw new ArgumentNullException(nameof(app));
  296. + }
  297. +
  298. + app.UseMiddleware<HostFilteringMiddleware>();
  299. +
  300. + return app;
  301. + }
  302. + }
  303. +}
  304. diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs
  305. new file mode 100644
  306. index 00000000000..e1e38eca378
  307. --- /dev/null
  308. +++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs
  309. @@ -0,0 +1,166 @@
  310. +// Copyright (c) .NET Foundation. All rights reserved.
  311. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  312. +
  313. +using System;
  314. +using System.Collections.Generic;
  315. +using System.Linq;
  316. +using System.Text;
  317. +using System.Threading.Tasks;
  318. +using Microsoft.AspNetCore.Http;
  319. +using Microsoft.Extensions.Logging;
  320. +using Microsoft.Extensions.Options;
  321. +using Microsoft.Extensions.Primitives;
  322. +using Microsoft.Net.Http.Headers;
  323. +
  324. +namespace Microsoft.AspNetCore.HostFiltering
  325. +{
  326. + /// <summary>
  327. + /// A middleware used to filter requests by their Host header.
  328. + /// </summary>
  329. + public class HostFilteringMiddleware
  330. + {
  331. + // Matches Http.Sys.
  332. + private static readonly byte[] DefaultResponse = Encoding.ASCII.GetBytes(
  333. + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\"http://www.w3.org/TR/html4/strict.dtd\">\r\n"
  334. + + "<HTML><HEAD><TITLE>Bad Request</TITLE>\r\n"
  335. + + "<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=us-ascii\"></ HEAD >\r\n"
  336. + + "<BODY><h2>Bad Request - Invalid Hostname</h2>\r\n"
  337. + + "<hr><p>HTTP Error 400. The request hostname is invalid.</p>\r\n"
  338. + + "</BODY></HTML>");
  339. +
  340. + private readonly RequestDelegate _next;
  341. + private readonly ILogger<HostFilteringMiddleware> _logger;
  342. + private readonly HostFilteringOptions _options;
  343. + private IList<StringSegment> _allowedHosts;
  344. + private bool? _allowAnyNonEmptyHost;
  345. +
  346. + /// <summary>
  347. + /// A middleware used to filter requests by their Host header.
  348. + /// </summary>
  349. + /// <param name="next"></param>
  350. + /// <param name="logger"></param>
  351. + /// <param name="options"></param>
  352. + public HostFilteringMiddleware(RequestDelegate next, ILogger<HostFilteringMiddleware> logger,
  353. + IOptions<HostFilteringOptions> options)
  354. + {
  355. + _next = next ?? throw new ArgumentNullException(nameof(next));
  356. + _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  357. + _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
  358. + }
  359. +
  360. + /// <summary>
  361. + /// Processes requests
  362. + /// </summary>
  363. + /// <param name="context"></param>
  364. + /// <returns></returns>
  365. + public Task Invoke(HttpContext context)
  366. + {
  367. + EnsureConfigured();
  368. +
  369. + if (!CheckHost(context))
  370. + {
  371. + context.Response.StatusCode = 400;
  372. + if (_options.IncludeFailureMessage)
  373. + {
  374. + context.Response.ContentLength = DefaultResponse.Length;
  375. + context.Response.ContentType = "text/html";
  376. + return context.Response.Body.WriteAsync(DefaultResponse, 0, DefaultResponse.Length);
  377. + }
  378. + return Task.CompletedTask;
  379. + }
  380. +
  381. + return _next(context);
  382. + }
  383. +
  384. + private void EnsureConfigured()
  385. + {
  386. + if (_allowAnyNonEmptyHost == true || _allowedHosts?.Count > 0)
  387. + {
  388. + return;
  389. + }
  390. +
  391. + var allowedHosts = new List<StringSegment>();
  392. + if (_options.AllowedHosts?.Count > 0 && !TryProcessHosts(_options.AllowedHosts, allowedHosts))
  393. + {
  394. + _logger.LogDebug("Wildcard detected, all requests with hosts will be allowed.");
  395. + _allowAnyNonEmptyHost = true;
  396. + return;
  397. + }
  398. +
  399. + if (allowedHosts.Count == 0)
  400. + {
  401. + throw new InvalidOperationException("No allowed hosts were configured.");
  402. + }
  403. +
  404. + _logger.LogDebug("Allowed hosts: " + string.Join("; ", allowedHosts));
  405. + _allowedHosts = allowedHosts;
  406. + }
  407. +
  408. + // returns false if any wildcards were found
  409. + private bool TryProcessHosts(IEnumerable<string> incoming, IList<StringSegment> results)
  410. + {
  411. + foreach (var entry in incoming)
  412. + {
  413. + // Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode.
  414. + var host = new HostString(entry).ToUriComponent();
  415. +
  416. + if (IsTopLevelWildcard(host))
  417. + {
  418. + // Disable filtering
  419. + return false;
  420. + }
  421. +
  422. + if (!results.Contains(host, StringSegmentComparer.OrdinalIgnoreCase))
  423. + {
  424. + results.Add(host);
  425. + }
  426. + }
  427. +
  428. + return true;
  429. + }
  430. +
  431. + private bool IsTopLevelWildcard(string host)
  432. + {
  433. + return (string.Equals("*", host, StringComparison.Ordinal) // HttpSys wildcard
  434. + || string.Equals("[::]", host, StringComparison.Ordinal) // Kestrel wildcard, IPv6 Any
  435. + || string.Equals("0.0.0.0", host, StringComparison.Ordinal)); // IPv4 Any
  436. + }
  437. +
  438. + // This does not duplicate format validations that are expected to be performed by the host.
  439. + private bool CheckHost(HttpContext context)
  440. + {
  441. + var host = new StringSegment(context.Request.Headers[HeaderNames.Host].ToString()).Trim();
  442. +
  443. + if (StringSegment.IsNullOrEmpty(host))
  444. + {
  445. + // Http/1.0 does not require the host header.
  446. + // Http/1.1 requires the header but the value may be empty.
  447. + if (!_options.AllowEmptyHosts)
  448. + {
  449. + _logger.LogInformation($"{context.Request.Protocol} request rejected due to missing or empty host header.");
  450. + return false;
  451. + }
  452. + if (_logger.IsEnabled(LogLevel.Debug))
  453. + {
  454. + _logger.LogDebug($"{context.Request.Protocol} request allowed with missing or empty host header.");
  455. + }
  456. + return true;
  457. + }
  458. +
  459. + if (_allowAnyNonEmptyHost == true)
  460. + {
  461. + _logger.LogTrace($"All hosts are allowed.");
  462. + return true;
  463. + }
  464. +
  465. + if (HostString.MatchesAny(host, _allowedHosts))
  466. + {
  467. + _logger.LogTrace($"The host '{host}' matches an allowed host.");
  468. + return true;
  469. + }
  470. +
  471. + _logger.LogInformation($"The host '{host}' does not match an allowed host.");
  472. + return false;
  473. + }
  474. + }
  475. +}
  476. diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringOptions.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringOptions.cs
  477. new file mode 100644
  478. index 00000000000..1645591579e
  479. --- /dev/null
  480. +++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringOptions.cs
  481. @@ -0,0 +1,44 @@
  482. +// Copyright (c) .NET Foundation. All rights reserved.
  483. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  484. +
  485. +using System.Collections.Generic;
  486. +
  487. +namespace Microsoft.AspNetCore.HostFiltering
  488. +{
  489. + /// <summary>
  490. + /// Options for the HostFiltering middleware
  491. + /// </summary>
  492. + public class HostFilteringOptions
  493. + {
  494. + /// <summary>
  495. + /// The hosts headers that are allowed to access this site. At least one value is required.
  496. + /// </summary>
  497. + /// <remarks>
  498. + /// <list type="bullet">
  499. + /// <item><description>Port numbers must be excluded.</description></item>
  500. + /// <item><description>A top level wildcard "*" allows all non-empty hosts.</description></item>
  501. + /// <item><description>Subdomain wildcards are permitted. E.g. "*.example.com" matches subdomains like foo.example.com,
  502. + /// but not the parent domain example.com.</description></item>
  503. + /// <item><description>Unicode host names are allowed but will be converted to punycode for matching.</description></item>
  504. + /// <item><description>IPv6 addresses must include their bounding brackets and be in their normalized form.</description></item>
  505. + /// </list>
  506. + /// </remarks>
  507. + public IList<string> AllowedHosts { get; set; } = new List<string>();
  508. +
  509. + /// <summary>
  510. + /// Indicates if requests without hosts are allowed. The default is true.
  511. + /// </summary>
  512. + /// <remarks>
  513. + /// HTTP/1.0 does not require a host header.
  514. + /// Http/1.1 requires a host header, but says the value may be empty.
  515. + /// </remarks>
  516. + public bool AllowEmptyHosts { get; set; } = true;
  517. +
  518. + // Note if this were disabled then things like the status code middleware may try to re-execute
  519. + // the request. This is a low level protocol violation, pretty error pages should not be required.
  520. + /// <summary>
  521. + /// Indicates if the 400 response should include a default message or be empty. This is enabled by default.
  522. + /// </summary>
  523. + public bool IncludeFailureMessage { get; set; } = true;
  524. + }
  525. +}
  526. diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringServicesExtensions.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringServicesExtensions.cs
  527. new file mode 100644
  528. index 00000000000..81b48444a8e
  529. --- /dev/null
  530. +++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringServicesExtensions.cs
  531. @@ -0,0 +1,36 @@
  532. +// Copyright (c) .NET Foundation. All rights reserved.
  533. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  534. +
  535. +using System;
  536. +using Microsoft.AspNetCore.HostFiltering;
  537. +using Microsoft.Extensions.DependencyInjection;
  538. +
  539. +namespace Microsoft.AspNetCore.Builder
  540. +{
  541. + /// <summary>
  542. + /// Extension methods for the host filtering middleware.
  543. + /// </summary>
  544. + public static class HostFilteringServicesExtensions
  545. + {
  546. + /// <summary>
  547. + /// Adds services and options for the host filtering middleware.
  548. + /// </summary>
  549. + /// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
  550. + /// <param name="configureOptions">A delegate to configure the <see cref="HostFilteringOptions"/>.</param>
  551. + /// <returns></returns>
  552. + public static IServiceCollection AddHostFiltering(this IServiceCollection services, Action<HostFilteringOptions> configureOptions)
  553. + {
  554. + if (services == null)
  555. + {
  556. + throw new ArgumentNullException(nameof(services));
  557. + }
  558. + if (configureOptions == null)
  559. + {
  560. + throw new ArgumentNullException(nameof(configureOptions));
  561. + }
  562. +
  563. + services.Configure(configureOptions);
  564. + return services;
  565. + }
  566. + }
  567. +}
  568. diff --git a/src/Microsoft.AspNetCore.HostFiltering/Microsoft.AspNetCore.HostFiltering.csproj b/src/Microsoft.AspNetCore.HostFiltering/Microsoft.AspNetCore.HostFiltering.csproj
  569. new file mode 100644
  570. index 00000000000..ec6d6423407
  571. --- /dev/null
  572. +++ b/src/Microsoft.AspNetCore.HostFiltering/Microsoft.AspNetCore.HostFiltering.csproj
  573. @@ -0,0 +1,18 @@
  574. +<Project Sdk="Microsoft.NET.Sdk">
  575. +
  576. + <PropertyGroup>
  577. + <Description>
  578. + ASP.NET Core middleware for filtering out requests with unknown HTTP host headers.
  579. + </Description>
  580. + <TargetFramework>netstandard2.0</TargetFramework>
  581. + <GenerateDocumentationFile>true</GenerateDocumentationFile>
  582. + <PackageTags>aspnetcore</PackageTags>
  583. + </PropertyGroup>
  584. +
  585. + <ItemGroup>
  586. + <PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
  587. + <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
  588. + <PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
  589. + <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
  590. + </ItemGroup>
  591. +</Project>
  592. diff --git a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs
  593. index d5e074de57d..decda81d58e 100644
  594. --- a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs
  595. +++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersMiddleware.cs
  596. @@ -2,6 +2,7 @@
  597. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  598. using System;
  599. +using System.Collections.Generic;
  600. using System.Linq;
  601. using System.Net;
  602. using System.Runtime.CompilerServices;
  603. @@ -11,6 +12,7 @@ using Microsoft.AspNetCore.Http;
  604. using Microsoft.AspNetCore.HttpOverrides.Internal;
  605. using Microsoft.Extensions.Logging;
  606. using Microsoft.Extensions.Options;
  607. +using Microsoft.Extensions.Primitives;
  608. namespace Microsoft.AspNetCore.HttpOverrides
  609. {
  610. @@ -22,6 +24,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
  611. private readonly ForwardedHeadersOptions _options;
  612. private readonly RequestDelegate _next;
  613. private readonly ILogger _logger;
  614. + private bool _allowAllHosts;
  615. + private IList<StringSegment> _allowedHosts;
  616. static ForwardedHeadersMiddleware()
  617. {
  618. @@ -85,6 +89,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
  619. _options = options.Value;
  620. _logger = loggerFactory.CreateLogger<ForwardedHeadersMiddleware>();
  621. _next = next;
  622. +
  623. + PreProcessHosts();
  624. }
  625. private static void EnsureOptionNotNullorWhitespace(string value, string propertyName)
  626. @@ -95,6 +101,43 @@ namespace Microsoft.AspNetCore.HttpOverrides
  627. }
  628. }
  629. + private void PreProcessHosts()
  630. + {
  631. + if (_options.AllowedHosts == null || _options.AllowedHosts.Count == 0)
  632. + {
  633. + _allowAllHosts = true;
  634. + return;
  635. + }
  636. +
  637. + var allowedHosts = new List<StringSegment>();
  638. + foreach (var entry in _options.AllowedHosts)
  639. + {
  640. + // Punycode. Http.Sys requires you to register Unicode hosts, but the headers contain punycode.
  641. + var host = new HostString(entry).ToUriComponent();
  642. +
  643. + if (IsTopLevelWildcard(host))
  644. + {
  645. + // Disable filtering
  646. + _allowAllHosts = true;
  647. + return;
  648. + }
  649. +
  650. + if (!allowedHosts.Contains(host, StringSegmentComparer.OrdinalIgnoreCase))
  651. + {
  652. + allowedHosts.Add(host);
  653. + }
  654. + }
  655. +
  656. + _allowedHosts = allowedHosts;
  657. + }
  658. +
  659. + private bool IsTopLevelWildcard(string host)
  660. + {
  661. + return (string.Equals("*", host, StringComparison.Ordinal) // HttpSys wildcard
  662. + || string.Equals("[::]", host, StringComparison.Ordinal) // Kestrel wildcard, IPv6 Any
  663. + || string.Equals("0.0.0.0", host, StringComparison.Ordinal)); // IPv4 Any
  664. + }
  665. +
  666. public Task Invoke(HttpContext context)
  667. {
  668. ApplyForwarders(context);
  669. @@ -231,7 +274,8 @@ namespace Microsoft.AspNetCore.HttpOverrides
  670. if (checkHost)
  671. {
  672. - if (!string.IsNullOrEmpty(set.Host) && TryValidateHost(set.Host))
  673. + if (!string.IsNullOrEmpty(set.Host) && TryValidateHost(set.Host)
  674. + && (_allowAllHosts || HostString.MatchesAny(set.Host, _allowedHosts)))
  675. {
  676. applyChanges = true;
  677. currentValues.Host = set.Host;
  678. diff --git a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs
  679. index cbe00baaa19..3507d9bea55 100644
  680. --- a/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs
  681. +++ b/src/Microsoft.AspNetCore.HttpOverrides/ForwardedHeadersOptions.cs
  682. @@ -67,6 +67,22 @@ namespace Microsoft.AspNetCore.Builder
  683. /// </summary>
  684. public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) };
  685. + /// <summary>
  686. + /// The allowed values from x-forwarded-host. If the list is empty then all hosts are allowed.
  687. + /// Failing to restrict this these values may allow an attacker to spoof links generated by your service.
  688. + /// </summary>
  689. + /// <remarks>
  690. + /// <list type="bullet">
  691. + /// <item><description>Port numbers must be excluded.</description></item>
  692. + /// <item><description>A top level wildcard "*" allows all non-empty hosts.</description></item>
  693. + /// <item><description>Subdomain wildcards are permitted. E.g. "*.example.com" matches subdomains like foo.example.com,
  694. + /// but not the parent domain example.com.</description></item>
  695. + /// <item><description>Unicode host names are allowed but will be converted to punycode for matching.</description></item>
  696. + /// <item><description>IPv6 addresses must include their bounding brackets and be in their normalized form.</description></item>
  697. + /// </list>
  698. + /// </remarks>
  699. + public IList<string> AllowedHosts { get; set; } = new List<string>();
  700. +
  701. /// <summary>
  702. /// Require the number of header values to be in sync between the different headers being processed.
  703. /// The default is 'false'.
  704. diff --git a/src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs b/src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs
  705. index 429f0f0ee22..c6f804763ff 100644
  706. --- a/src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs
  707. +++ b/src/Microsoft.AspNetCore.HttpsPolicy/HttpsRedirectionBuilderExtensions.cs
  708. @@ -2,11 +2,9 @@
  709. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  710. using System;
  711. +using System.Collections.Generic;
  712. using Microsoft.AspNetCore.Hosting.Server.Features;
  713. using Microsoft.AspNetCore.HttpsPolicy;
  714. -using Microsoft.Extensions.Configuration;
  715. -using Microsoft.Extensions.DependencyInjection;
  716. -using Microsoft.Extensions.Options;
  717. namespace Microsoft.AspNetCore.Builder
  718. {
  719. @@ -20,15 +18,13 @@ namespace Microsoft.AspNetCore.Builder
  720. /// </summary>
  721. /// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
  722. /// <returns>The <see cref="IApplicationBuilder"/> for HttpsRedirection.</returns>
  723. - /// <remarks>
  724. - /// HTTPS Enforcement interanlly uses the UrlRewrite middleware to redirect HTTP requests to HTTPS.
  725. - /// </remarks>
  726. public static IApplicationBuilder UseHttpsRedirection(this IApplicationBuilder app)
  727. {
  728. if (app == null)
  729. {
  730. throw new ArgumentNullException(nameof(app));
  731. }
  732. +
  733. var serverAddressFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
  734. if (serverAddressFeature != null)
  735. {
  736. diff --git a/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs b/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs
  737. new file mode 100644
  738. index 00000000000..2fc7b8c7137
  739. --- /dev/null
  740. +++ b/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs
  741. @@ -0,0 +1,183 @@
  742. +// Copyright (c) .NET Foundation. All rights reserved.
  743. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  744. +
  745. +using System;
  746. +using System.Threading.Tasks;
  747. +using Microsoft.AspNetCore.Builder;
  748. +using Microsoft.AspNetCore.Hosting;
  749. +using Microsoft.AspNetCore.TestHost;
  750. +using Microsoft.Extensions.Primitives;
  751. +using Microsoft.Net.Http.Headers;
  752. +using Xunit;
  753. +
  754. +namespace Microsoft.AspNetCore.HostFiltering
  755. +{
  756. + public class HostFilteringMiddlewareTests
  757. + {
  758. + [Fact]
  759. + public async Task MissingConfigThrows()
  760. + {
  761. + var builder = new WebHostBuilder()
  762. + .Configure(app =>
  763. + {
  764. + app.UseHostFiltering();
  765. + });
  766. + await Assert.ThrowsAsync<InvalidOperationException>(() => new TestServer(builder).SendAsync(_ => { }));
  767. + }
  768. +
  769. + [Theory]
  770. + [InlineData(true, 200)]
  771. + [InlineData(false, 400)]
  772. + public async Task AllowsMissingHost(bool allowed, int status)
  773. + {
  774. + var builder = new WebHostBuilder()
  775. + .ConfigureServices(services =>
  776. + {
  777. + services.AddHostFiltering(options =>
  778. + {
  779. + options.AllowEmptyHosts = allowed;
  780. + options.AllowedHosts.Add("Localhost");
  781. + });
  782. + })
  783. + .Configure(app =>
  784. + {
  785. + app.Use((ctx, next) =>
  786. + {
  787. + ctx.Request.Headers.Remove(HeaderNames.Host);
  788. + return next();
  789. + });
  790. + app.UseHostFiltering();
  791. + app.Run(c =>
  792. + {
  793. + Assert.False(c.Request.Headers.TryGetValue(HeaderNames.Host, out var host));
  794. + return Task.CompletedTask;
  795. + });
  796. + });
  797. + var server = new TestServer(builder);
  798. + var response = await server.CreateClient().GetAsync("/");
  799. + Assert.Equal(status, (int)response.StatusCode);
  800. + }
  801. +
  802. + [Theory]
  803. + [InlineData(true, 200)]
  804. + [InlineData(false, 400)]
  805. + public async Task AllowsEmptyHost(bool allowed, int status)
  806. + {
  807. + var builder = new WebHostBuilder()
  808. + .ConfigureServices(services =>
  809. + {
  810. + services.AddHostFiltering(options =>
  811. + {
  812. + options.AllowEmptyHosts = allowed;
  813. + options.AllowedHosts.Add("Localhost");
  814. + });
  815. + })
  816. + .Configure(app =>
  817. + {
  818. + app.Use((ctx, next) =>
  819. + {
  820. + ctx.Request.Headers[HeaderNames.Host] = " ";
  821. + return next();
  822. + });
  823. + app.UseHostFiltering();
  824. + app.Run(c =>
  825. + {
  826. + Assert.True(c.Request.Headers.TryGetValue(HeaderNames.Host, out var host));
  827. + Assert.True(StringValues.Equals(" ", host));
  828. + return Task.CompletedTask;
  829. + });
  830. + app.Run(c => Task.CompletedTask);
  831. + });
  832. + var server = new TestServer(builder);
  833. + var response = await server.CreateClient().GetAsync("/");
  834. + Assert.Equal(status, (int)response.StatusCode);
  835. + }
  836. +
  837. + [Theory]
  838. + [InlineData("localHost", "localhost")]
  839. + [InlineData("localHost", "*")] // Any - Used by HttpSys
  840. + [InlineData("localHost", "[::]")] // IPv6 Any - This is what Kestrel reports when binding to *
  841. + [InlineData("localHost", "0.0.0.0")] // IPv4 Any
  842. + [InlineData("localhost:9090", "example.com;localHost")]
  843. + [InlineData("example.com:443", "example.com;localhost")]
  844. + [InlineData("localHost:80", "localhost;")]
  845. + [InlineData("foo.eXample.com:443", "*.exampLe.com")]
  846. + [InlineData("f.eXample.com:443", "*.exampLe.com")]
  847. + [InlineData("127.0.0.1", "127.0.0.1")]
  848. + [InlineData("127.0.0.1:443", "127.0.0.1")]
  849. + [InlineData("xn--c1yn36f:443", "xn--c1yn36f")]
  850. + [InlineData("xn--c1yn36f:443", "點看")]
  851. + [InlineData("[::ABC]", "[::aBc]")]
  852. + [InlineData("[::1]:80", "[::1]")]
  853. + public async Task AllowsSpecifiedHost(string host, string allowedHost)
  854. + {
  855. + var builder = new WebHostBuilder()
  856. + .ConfigureServices(services =>
  857. + {
  858. + services.AddHostFiltering(options =>
  859. + {
  860. + options.AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
  861. + });
  862. + })
  863. + .Configure(app =>
  864. + {
  865. + app.Use((ctx, next) =>
  866. + {
  867. + // TestHost's ClientHandler doesn't let you set the host header, only the host in the URI
  868. + // and that would over-normalize some of our test conditions like casing.
  869. + ctx.Request.Headers[HeaderNames.Host] = host;
  870. + return next();
  871. + });
  872. + app.UseHostFiltering();
  873. + app.Run(c => Task.CompletedTask);
  874. + });
  875. + var server = new TestServer(builder);
  876. + var response = await server.CreateRequest("/").GetAsync();
  877. + Assert.Equal(200, (int)response.StatusCode);
  878. + }
  879. +
  880. + [Theory]
  881. + [InlineData("example.com", "localhost")]
  882. + [InlineData("localhost:9090", "example.com;")]
  883. + [InlineData(";", "example.com;localhost")]
  884. + [InlineData(";:80", "example.com;localhost")]
  885. + [InlineData(":80", "localhost")]
  886. + [InlineData(":", "localhost")]
  887. + [InlineData("example.com:443", "*.example.com")]
  888. + [InlineData(".example.com:443", "*.example.com")]
  889. + [InlineData("foo.com:443", "*.example.com")]
  890. + [InlineData("foo.example.com.bar:443", "*.example.com")]
  891. + [InlineData(".com:443", "*.com")]
  892. + // Unicode in the host shouldn't be allowed without punycode anyways. This match fails because the middleware converts
  893. + // its input to punycode.
  894. + [InlineData("點看", "點看")]
  895. + [InlineData("[::1", "[::1]")]
  896. + [InlineData("[::1:80", "[::1]")]
  897. + public async Task RejectsMismatchedHosts(string host, string allowedHost)
  898. + {
  899. + var builder = new WebHostBuilder()
  900. + .ConfigureServices(services =>
  901. + {
  902. + services.AddHostFiltering(options =>
  903. + {
  904. + options.AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
  905. + });
  906. + })
  907. + .Configure(app =>
  908. + {
  909. + app.Use((ctx, next) =>
  910. + {
  911. + // TestHost's ClientHandler doesn't let you set the host header, only the host in the URI
  912. + // and that would reject some of our test conditions.
  913. + ctx.Request.Headers[HeaderNames.Host] = host;
  914. + return next();
  915. + });
  916. + app.UseHostFiltering();
  917. + app.Run(c => throw new NotImplementedException("App"));
  918. + });
  919. + var server = new TestServer(builder);
  920. + var response = await server.CreateRequest("/").GetAsync();
  921. + Assert.Equal(400, (int)response.StatusCode);
  922. + }
  923. + }
  924. +}
  925. diff --git a/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj b/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj
  926. new file mode 100644
  927. index 00000000000..d71805c949d
  928. --- /dev/null
  929. +++ b/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj
  930. @@ -0,0 +1,11 @@
  931. +<Project Sdk="Microsoft.NET.Sdk">
  932. +
  933. + <PropertyGroup>
  934. + <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
  935. + </PropertyGroup>
  936. +
  937. + <ItemGroup>
  938. + <ProjectReference Include="..\..\src\Microsoft.AspNetCore.HostFiltering\Microsoft.AspNetCore.HostFiltering.csproj" />
  939. + </ItemGroup>
  940. +
  941. +</Project>
  942. diff --git a/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs b/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs
  943. index 81d6ccf15a0..f88243c9e77 100644
  944. --- a/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs
  945. +++ b/test/Microsoft.AspNetCore.HttpOverrides.Tests/ForwardedHeadersMiddlewareTest.cs
  946. @@ -1,12 +1,14 @@
  947. // Copyright (c) .NET Foundation. All rights reserved.
  948. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  949. +using System;
  950. using System.Linq;
  951. using System.Net;
  952. using System.Threading.Tasks;
  953. using Microsoft.AspNetCore.Builder;
  954. using Microsoft.AspNetCore.Hosting;
  955. using Microsoft.AspNetCore.TestHost;
  956. +using Microsoft.Net.Http.Headers;
  957. using Xunit;
  958. namespace Microsoft.AspNetCore.HttpOverrides
  959. @@ -392,6 +394,119 @@ namespace Microsoft.AspNetCore.HttpOverrides
  960. Assert.True(assertsExecuted);
  961. }
  962. + [Theory]
  963. + [InlineData("localHost", "localhost")]
  964. + [InlineData("localHost", "*")] // Any - Used by HttpSys
  965. + [InlineData("localHost", "[::]")] // IPv6 Any - This is what Kestrel reports when binding to *
  966. + [InlineData("localHost", "0.0.0.0")] // IPv4 Any
  967. + [InlineData("localhost:9090", "example.com;localHost")]
  968. + [InlineData("example.com:443", "example.com;localhost")]
  969. + [InlineData("localHost:80", "localhost;")]
  970. + [InlineData("foo.eXample.com:443", "*.exampLe.com")]
  971. + [InlineData("f.eXample.com:443", "*.exampLe.com")]
  972. + [InlineData("127.0.0.1", "127.0.0.1")]
  973. + [InlineData("127.0.0.1:443", "127.0.0.1")]
  974. + [InlineData("xn--c1yn36f:443", "xn--c1yn36f")]
  975. + [InlineData("xn--c1yn36f:443", "點看")]
  976. + [InlineData("[::ABC]", "[::aBc]")]
  977. + [InlineData("[::1]:80", "[::1]")]
  978. + public async Task XForwardedHostAllowsSpecifiedHost(string host, string allowedHost)
  979. + {
  980. + bool assertsExecuted = false;
  981. + var builder = new WebHostBuilder()
  982. + .Configure(app =>
  983. + {
  984. + app.UseForwardedHeaders(new ForwardedHeadersOptions
  985. + {
  986. + ForwardedHeaders = ForwardedHeaders.XForwardedHost,
  987. + AllowedHosts = allowedHost.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
  988. + });
  989. + app.Run(context =>
  990. + {
  991. + Assert.Equal(host, context.Request.Headers[HeaderNames.Host]);
  992. + assertsExecuted = true;
  993. + return Task.FromResult(0);
  994. + });
  995. + });
  996. + var server = new TestServer(builder);
  997. + var response = await server.SendAsync(ctx =>
  998. + {
  999. + ctx.Request.Headers["X-forwarded-Host"] = host;
  1000. + });
  1001. + Assert.True(assertsExecuted);
  1002. + }
  1003. +
  1004. + [Theory]
  1005. + [InlineData("example.com", "localhost")]
  1006. + [InlineData("localhost:9090", "example.com;")]
  1007. + [InlineData(";", "example.com;localhost")]
  1008. + [InlineData(";:80", "example.com;localhost")]
  1009. + [InlineData(":80", "localhost")]
  1010. + [InlineData(":", "localhost")]
  1011. + [InlineData("example.com:443", "*.example.com")]
  1012. + [InlineData(".example.com:443", "*.example.com")]
  1013. + [InlineData("foo.com:443", "*.example.com")]
  1014. + [InlineData("foo.example.com.bar:443", "*.example.com")]
  1015. + [InlineData(".com:443", "*.com")]
  1016. + // Unicode in the host shouldn't be allowed without punycode anyways. This match fails because the middleware converts
  1017. + // its input to punycode.
  1018. + [InlineData("點看", "點看")]
  1019. + [InlineData("[::1", "[::1]")]
  1020. + [InlineData("[::1:80", "[::1]")]
  1021. + public async Task XForwardedHostFailsMismatchedHosts(string host, string allowedHost)
  1022. + {
  1023. + bool assertsExecuted = false;
  1024. + var builder = new WebHostBuilder()
  1025. + .Configure(app =>
  1026. + {
  1027. + app.UseForwardedHeaders(new ForwardedHeadersOptions
  1028. + {
  1029. + ForwardedHeaders = ForwardedHeaders.XForwardedHost,
  1030. + AllowedHosts = new[] { allowedHost }
  1031. + });
  1032. + app.Run(context =>
  1033. + {
  1034. + Assert.NotEqual<string>(host, context.Request.Headers[HeaderNames.Host]);
  1035. + assertsExecuted = true;
  1036. + return Task.FromResult(0);
  1037. + });
  1038. + });
  1039. + var server = new TestServer(builder);
  1040. + var response = await server.SendAsync(ctx =>
  1041. + {
  1042. + ctx.Request.Headers["X-forwarded-Host"] = host;
  1043. + });
  1044. + Assert.True(assertsExecuted);
  1045. + }
  1046. +
  1047. + [Fact]
  1048. + public async Task XForwardedHostStopsAtFirstUnspecifiedHost()
  1049. + {
  1050. + bool assertsExecuted = false;
  1051. + var builder = new WebHostBuilder()
  1052. + .Configure(app =>
  1053. + {
  1054. + app.UseForwardedHeaders(new ForwardedHeadersOptions
  1055. + {
  1056. + ForwardedHeaders = ForwardedHeaders.XForwardedHost,
  1057. + ForwardLimit = 10,
  1058. + AllowedHosts = new[] { "bar.com", "*.foo.com" }
  1059. + });
  1060. + app.Run(context =>
  1061. + {
  1062. + Assert.Equal("bar.foo.com:432", context.Request.Headers[HeaderNames.Host]);
  1063. + assertsExecuted = true;
  1064. + return Task.FromResult(0);
  1065. + });
  1066. + });
  1067. + var server = new TestServer(builder);
  1068. + var response = await server.SendAsync(ctx =>
  1069. + {
  1070. + ctx.Request.Headers["X-forwarded-Host"] = "stuff:523, bar.foo.com:432, bar.com:80";
  1071. + });
  1072. + Assert.True(assertsExecuted);
  1073. + }
  1074. +
  1075. [Theory]
  1076. [InlineData(0, "h1", "http")]
  1077. [InlineData(1, "", "http")]
  1078. diff --git a/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs b/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs
  1079. index 9d482151ca2..b55c1952729 100644
  1080. --- a/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs
  1081. +++ b/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HttpsRedirectionMiddlewareTests.cs
  1082. @@ -22,9 +22,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  1083. public async Task SetOptions_DefaultsSetCorrectly()
  1084. {
  1085. var builder = new WebHostBuilder()
  1086. - .ConfigureServices(services =>
  1087. - {
  1088. - })
  1089. .Configure(app =>
  1090. {
  1091. app.UseHttpsRedirection();
  1092. @@ -34,9 +31,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  1093. });
  1094. });
  1095. - var featureCollection = new FeatureCollection();
  1096. - featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
  1097. - var server = new TestServer(builder, featureCollection);
  1098. + var server = new TestServer(builder);
  1099. var client = server.CreateClient();
  1100. var request = new HttpRequestMessage(HttpMethod.Get, "");
  1101. @@ -75,9 +70,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  1102. });
  1103. });
  1104. - var featureCollection = new FeatureCollection();
  1105. - featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
  1106. - var server = new TestServer(builder, featureCollection);
  1107. + var server = new TestServer(builder);
  1108. var client = server.CreateClient();
  1109. var request = new HttpRequestMessage(HttpMethod.Get, "");
  1110. @@ -115,9 +108,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  1111. });
  1112. });
  1113. - var featureCollection = new FeatureCollection();
  1114. - featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
  1115. - var server = new TestServer(builder, featureCollection);
  1116. + var server = new TestServer(builder);
  1117. var client = server.CreateClient();
  1118. var request = new HttpRequestMessage(HttpMethod.Get, "");
  1119. @@ -182,12 +173,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  1120. public async Task SetServerAddressesFeature_SingleHttpsAddress_Success()
  1121. {
  1122. var builder = new WebHostBuilder()
  1123. - .ConfigureServices(services =>
  1124. - {
  1125. - services.AddHttpsRedirection(options =>
  1126. - {
  1127. - });
  1128. - })
  1129. .Configure(app =>
  1130. {
  1131. app.UseHttpsRedirection();
  1132. @@ -215,12 +200,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  1133. public async Task SetServerAddressesFeature_MultipleHttpsAddresses_ThrowInMiddleware()
  1134. {
  1135. var builder = new WebHostBuilder()
  1136. - .ConfigureServices(services =>
  1137. - {
  1138. - services.AddHttpsRedirection(options =>
  1139. - {
  1140. - });
  1141. - })
  1142. .Configure(app =>
  1143. {
  1144. app.UseHttpsRedirection();
  1145. @@ -248,12 +227,6 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  1146. public async Task SetServerAddressesFeature_MultipleHttpsAddressesWithSamePort_Success()
  1147. {
  1148. var builder = new WebHostBuilder()
  1149. - .ConfigureServices(services =>
  1150. - {
  1151. - services.AddHttpsRedirection(options =>
  1152. - {
  1153. - });
  1154. - })
  1155. .Configure(app =>
  1156. {
  1157. app.UseHttpsRedirection();