소스 검색

Merge aspnet/HttpAbstractions release/2.2

Nate McMaster 7 년 전
부모
커밋
0f64aa5c01
45개의 변경된 파일599개의 추가작업 그리고 170개의 파일을 삭제
  1. 64 46
      eng/Baseline.props
  2. 2 2
      src/Http/Authentication.Abstractions/src/TokenExtensions.cs
  3. 14 4
      src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs
  4. 3 3
      src/Http/Headers/src/ContentDispositionHeaderValue.cs
  5. 1 1
      src/Http/Headers/src/HeaderUtilities.cs
  6. 1 1
      src/Http/Headers/src/HttpRuleParser.cs
  7. 11 1
      src/Http/Headers/src/MediaTypeHeaderValue.cs
  8. 1 1
      src/Http/Headers/src/RangeItemHeaderValue.cs
  9. 1 1
      src/Http/Headers/test/ContentDispositionHeaderValueTest.cs
  10. 1 1
      src/Http/Headers/test/CookieHeaderValueTest.cs
  11. 4 4
      src/Http/Headers/test/EntityTagHeaderValueTest.cs
  12. 7 4
      src/Http/Headers/test/MediaTypeHeaderValueTest.cs
  13. 6 6
      src/Http/Headers/test/NameValueHeaderValueTest.cs
  14. 2 2
      src/Http/Headers/test/RangeConditionHeaderValueTest.cs
  15. 1 1
      src/Http/Headers/test/SetCookieHeaderValueTest.cs
  16. 4 4
      src/Http/Headers/test/StringWithQualityHeaderValueTest.cs
  17. 2 2
      src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs
  18. 1 1
      src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs
  19. 53 0
      src/Http/Http.Abstractions/src/Extensions/ResponseTrailerExtensions.cs
  20. 13 13
      src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs
  21. 1 1
      src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs
  22. 6 1
      src/Http/Http.Abstractions/src/HostString.cs
  23. 2 2
      src/Http/Http.Abstractions/src/PathString.cs
  24. 1 1
      src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs
  25. 2 2
      src/Http/Http.Abstractions/test/QueryStringTests.cs
  26. 4 4
      src/Http/Http.Abstractions/test/UseMiddlewareTest.cs
  27. 1 1
      src/Http/Http.Features/src/FeatureReferences.cs
  28. 10 0
      src/Http/Http.Features/src/IHttpResponseTrailersFeature.cs
  29. 6 1
      src/Http/Http/src/Features/FormFeature.cs
  30. 6 3
      src/Http/Http/src/HttpContextAccessor.cs
  31. 4 0
      src/Http/Http/src/HttpContextFactory.cs
  32. 3 3
      src/Http/Http/test/DefaultHttpContextTests.cs
  33. 17 0
      src/Http/Http/test/Features/FormFeatureTests.cs
  34. 1 1
      src/Http/Http/test/HeaderDictionaryTests.cs
  35. 197 0
      src/Http/Http/test/HttpContextAccessorTests.cs
  36. 21 1
      src/Http/Http/test/HttpContextFactoryTests.cs
  37. 12 12
      src/Http/Http/test/ResponseCookiesTest.cs
  38. 2 2
      src/Http/Owin/src/OwinExtensions.cs
  39. 2 2
      src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs
  40. 1 1
      src/Http/Owin/test/OwinEnvironmentTests.cs
  41. 8 8
      src/Http/WebUtilities/src/FormReader.cs
  42. 86 12
      src/Http/WebUtilities/src/HttpResponseStreamWriter.cs
  43. 1 1
      src/Http/WebUtilities/src/QueryHelpers.cs
  44. 12 12
      src/Http/WebUtilities/test/MultipartReaderTests.cs
  45. 1 1
      src/Http/samples/SampleApp/SampleApp.csproj

+ 64 - 46
eng/Baseline.props

@@ -4,6 +4,24 @@
     <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
     <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
     <AspNetCoreBaselineVersion>2.2.0</AspNetCoreBaselineVersion>
     <AspNetCoreBaselineVersion>2.2.0</AspNetCoreBaselineVersion>
   </PropertyGroup>
   </PropertyGroup>
+  <!-- Package: Microsoft.AspNetCore.Authentication.Abstractions-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Authentication.Abstractions' ">
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Authentication.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.2.0, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Authentication.Core-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Authentication.Core' ">
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Authentication.Core' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Authentication.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http" Version="[2.2.0, )" />
+  </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Connections.Abstractions-->
   <!-- Package: Microsoft.AspNetCore.Connections.Abstractions-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Connections.Abstractions' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Connections.Abstractions' ">
     <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
     <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
@@ -98,48 +116,48 @@
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Hosting.Abstractions-->
   <!-- Package: Microsoft.AspNetCore.Hosting.Abstractions-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Abstractions' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Abstractions' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.2.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Hosting.Server.Abstractions-->
   <!-- Package: Microsoft.AspNetCore.Hosting.Server.Abstractions-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Server.Abstractions' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Server.Abstractions' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Server.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Server.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Features" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Features" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="[2.2.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Hosting.WindowsServices-->
   <!-- Package: Microsoft.AspNetCore.Hosting.WindowsServices-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' AND '$(TargetFramework)' == 'net461' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' AND '$(TargetFramework)' == 'net461' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.2.0, )" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.2.0, )" />
     <BaselinePackageReference Include="System.ServiceProcess.ServiceController" Version="[4.5.0, )" />
     <BaselinePackageReference Include="System.ServiceProcess.ServiceController" Version="[4.5.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Hosting-->
   <!-- Package: Microsoft.AspNetCore.Hosting-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Http" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Configuration" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Logging" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Configuration" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Logging" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.2.0, )" />
     <BaselinePackageReference Include="System.Diagnostics.DiagnosticSource" Version="[4.5.0, )" />
     <BaselinePackageReference Include="System.Diagnostics.DiagnosticSource" Version="[4.5.0, )" />
     <BaselinePackageReference Include="System.Reflection.Metadata" Version="[1.6.0, )" />
     <BaselinePackageReference Include="System.Reflection.Metadata" Version="[1.6.0, )" />
   </ItemGroup>
   </ItemGroup>
@@ -152,39 +170,39 @@
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Http.Abstractions-->
   <!-- Package: Microsoft.AspNetCore.Http.Abstractions-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Abstractions' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Abstractions' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Features" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Features" Version="[2.2.0, )" />
     <BaselinePackageReference Include="System.Text.Encodings.Web" Version="[4.5.0, )" />
     <BaselinePackageReference Include="System.Text.Encodings.Web" Version="[4.5.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Http.Extensions-->
   <!-- Package: Microsoft.AspNetCore.Http.Extensions-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Extensions' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Extensions' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Extensions' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Extensions' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="[2.2.0, )" />
     <BaselinePackageReference Include="System.Buffers" Version="[4.5.0, )" />
     <BaselinePackageReference Include="System.Buffers" Version="[4.5.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Http.Features-->
   <!-- Package: Microsoft.AspNetCore.Http.Features-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Features' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Features' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Features' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Features' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.Extensions.Primitives" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Primitives" Version="[2.2.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Http-->
   <!-- Package: Microsoft.AspNetCore.Http-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.ObjectPool" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.ObjectPool" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.2.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.JsonPatch-->
   <!-- Package: Microsoft.AspNetCore.JsonPatch-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.JsonPatch' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.JsonPatch' ">
@@ -196,10 +214,10 @@
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Owin-->
   <!-- Package: Microsoft.AspNetCore.Owin-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Owin' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Owin' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Owin' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Owin' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Http" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http" Version="[2.2.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Server.Kestrel.Core-->
   <!-- Package: Microsoft.AspNetCore.Server.Kestrel.Core-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Server.Kestrel.Core' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Server.Kestrel.Core' ">
@@ -291,11 +309,11 @@
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.TestHost-->
   <!-- Package: Microsoft.AspNetCore.TestHost-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.TestHost' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.TestHost' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.TestHost' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.TestHost' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.1.1, )" />
-    <BaselinePackageReference Include="System.IO.Pipelines" Version="[4.5.0, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.2.0, )" />
+    <BaselinePackageReference Include="System.IO.Pipelines" Version="[4.5.2, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.WebSockets-->
   <!-- Package: Microsoft.AspNetCore.WebSockets-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebSockets' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebSockets' ">
@@ -309,18 +327,18 @@
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.WebUtilities-->
   <!-- Package: Microsoft.AspNetCore.WebUtilities-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebUtilities' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebUtilities' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebUtilities' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebUtilities' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.2.0, )" />
     <BaselinePackageReference Include="System.Text.Encodings.Web" Version="[4.5.0, )" />
     <BaselinePackageReference Include="System.Text.Encodings.Web" Version="[4.5.0, )" />
   </ItemGroup>
   </ItemGroup>
   <!-- Package: Microsoft.Net.Http.Headers-->
   <!-- Package: Microsoft.Net.Http.Headers-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.Net.Http.Headers' ">
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.Net.Http.Headers' ">
-    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+    <BaselinePackageVersion>2.2.0</BaselinePackageVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.Net.Http.Headers' AND '$(TargetFramework)' == 'netstandard2.0' ">
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.Net.Http.Headers' AND '$(TargetFramework)' == 'netstandard2.0' ">
-    <BaselinePackageReference Include="Microsoft.Extensions.Primitives" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Primitives" Version="[2.2.0, )" />
     <BaselinePackageReference Include="System.Buffers" Version="[4.5.0, )" />
     <BaselinePackageReference Include="System.Buffers" Version="[4.5.0, )" />
   </ItemGroup>
   </ItemGroup>
-</Project>
+</Project>

+ 2 - 2
src/Http/Authentication.Abstractions/src/TokenExtensions.cs

@@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Authentication
         /// Returns all of the AuthenticationTokens contained in the properties.
         /// Returns all of the AuthenticationTokens contained in the properties.
         /// </summary>
         /// </summary>
         /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
         /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
-        /// <returns>The authentication toekns.</returns>
+        /// <returns>The authentication tokens.</returns>
         public static IEnumerable<AuthenticationToken> GetTokens(this AuthenticationProperties properties)
         public static IEnumerable<AuthenticationToken> GetTokens(this AuthenticationProperties properties)
         {
         {
             if (properties == null)
             if (properties == null)
@@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Authentication
         /// <param name="context">The <see cref="HttpContext"/> context.</param>
         /// <param name="context">The <see cref="HttpContext"/> context.</param>
         /// <param name="tokenName">The name of the token.</param>
         /// <param name="tokenName">The name of the token.</param>
         /// <returns>The value of the token.</returns>
         /// <returns>The value of the token.</returns>
-        public static Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string tokenName) 
+        public static Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string tokenName)
             => auth.GetTokenAsync(context, scheme: null, tokenName: tokenName);
             => auth.GetTokenAsync(context, scheme: null, tokenName: tokenName);
 
 
         /// <summary>
         /// <summary>

+ 14 - 4
src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs

@@ -3,6 +3,7 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Options;
 using Microsoft.Extensions.Options;
@@ -49,6 +50,9 @@ namespace Microsoft.AspNetCore.Authentication
 
 
         private readonly IDictionary<string, AuthenticationScheme> _schemes;
         private readonly IDictionary<string, AuthenticationScheme> _schemes;
         private readonly List<AuthenticationScheme> _requestHandlers;
         private readonly List<AuthenticationScheme> _requestHandlers;
+        // Used as a safe return value for enumeration apis
+        private IEnumerable<AuthenticationScheme> _schemesCopy = Array.Empty<AuthenticationScheme>();
+        private IEnumerable<AuthenticationScheme> _requestHandlersCopy = Array.Empty<AuthenticationScheme>();
 
 
         private Task<AuthenticationScheme> GetDefaultSchemeAsync()
         private Task<AuthenticationScheme> GetDefaultSchemeAsync()
             => _options.DefaultScheme != null
             => _options.DefaultScheme != null
@@ -102,7 +106,7 @@ namespace Microsoft.AspNetCore.Authentication
         /// <summary>
         /// <summary>
         /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
         /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
         /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
         /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
-        /// Otherwise this will fallback to <see cref="GetDefaultSignInSchemeAsync"/> if that supoorts sign out.
+        /// Otherwise this will fallback to <see cref="GetDefaultSignInSchemeAsync"/> if that supports sign out.
         /// </summary>
         /// </summary>
         /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
         /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
         public virtual Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync()
         public virtual Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync()
@@ -123,7 +127,7 @@ namespace Microsoft.AspNetCore.Authentication
         /// </summary>
         /// </summary>
         /// <returns>The schemes in priority order for request handling</returns>
         /// <returns>The schemes in priority order for request handling</returns>
         public virtual Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
         public virtual Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
-            => Task.FromResult<IEnumerable<AuthenticationScheme>>(_requestHandlers);
+            => Task.FromResult(_requestHandlersCopy);
 
 
         /// <summary>
         /// <summary>
         /// Registers a scheme for use by <see cref="IAuthenticationService"/>. 
         /// Registers a scheme for use by <see cref="IAuthenticationService"/>. 
@@ -144,8 +148,10 @@ namespace Microsoft.AspNetCore.Authentication
                 if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
                 if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
                 {
                 {
                     _requestHandlers.Add(scheme);
                     _requestHandlers.Add(scheme);
+                    _requestHandlersCopy = _requestHandlers.ToArray();
                 }
                 }
                 _schemes[scheme.Name] = scheme;
                 _schemes[scheme.Name] = scheme;
+                _schemesCopy = _schemes.Values.ToArray();
             }
             }
         }
         }
 
 
@@ -164,13 +170,17 @@ namespace Microsoft.AspNetCore.Authentication
                 if (_schemes.ContainsKey(name))
                 if (_schemes.ContainsKey(name))
                 {
                 {
                     var scheme = _schemes[name];
                     var scheme = _schemes[name];
-                    _requestHandlers.Remove(scheme);
+                    if (_requestHandlers.Remove(scheme))
+                    {
+                        _requestHandlersCopy = _requestHandlers.ToArray();
+                    }
                     _schemes.Remove(name);
                     _schemes.Remove(name);
+                    _schemesCopy = _schemes.Values.ToArray();
                 }
                 }
             }
             }
         }
         }
 
 
         public virtual Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
         public virtual Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
-            => Task.FromResult<IEnumerable<AuthenticationScheme>>(_schemes.Values);
+            => Task.FromResult(_schemesCopy);
     }
     }
 }
 }

+ 3 - 3
src/Http/Headers/src/ContentDispositionHeaderValue.cs

@@ -155,7 +155,7 @@ namespace Microsoft.Net.Http.Headers
         {
         {
             if (!StringSegment.IsNullOrEmpty(fileName))
             if (!StringSegment.IsNullOrEmpty(fileName))
             {
             {
-                FileName = Sanatize(fileName);
+                FileName = Sanitize(fileName);
             }
             }
             else
             else
             {
             {
@@ -166,7 +166,7 @@ namespace Microsoft.Net.Http.Headers
 
 
         /// <summary>
         /// <summary>
         /// Sets the FileName parameter using encodings appropriate for MIME headers.
         /// Sets the FileName parameter using encodings appropriate for MIME headers.
-        /// The FileNameStar paraemter is removed.
+        /// The FileNameStar parameter is removed.
         /// </summary>
         /// </summary>
         /// <param name="fileName"></param>
         /// <param name="fileName"></param>
         public void SetMimeFileName(StringSegment fileName)
         public void SetMimeFileName(StringSegment fileName)
@@ -434,7 +434,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         // Replaces characters not suitable for HTTP headers with '_' rather than MIME encoding them.
         // Replaces characters not suitable for HTTP headers with '_' rather than MIME encoding them.
-        private StringSegment Sanatize(StringSegment input)
+        private StringSegment Sanitize(StringSegment input)
         {
         {
             var result = input;
             var result = input;
 
 

+ 1 - 1
src/Http/Headers/src/HeaderUtilities.cs

@@ -143,7 +143,7 @@ namespace Microsoft.Net.Http.Headers
                 }
                 }
             }
             }
 
 
-            // Since we never re-use a "found" value in 'y', we expecte 'alreadyFound' to have all fields set to 'true'.
+            // Since we never re-use a "found" value in 'y', we expected 'alreadyFound' to have all fields set to 'true'.
             // Otherwise the two collections can't be equal and we should not get here.
             // Otherwise the two collections can't be equal and we should not get here.
             Contract.Assert(Contract.ForAll(alreadyFound, value => { return value; }),
             Contract.Assert(Contract.ForAll(alreadyFound, value => { return value; }),
                 "Expected all values in 'alreadyFound' to be true since collections are considered equal.");
                 "Expected all values in 'alreadyFound' to be true since collections are considered equal.");

+ 1 - 1
src/Http/Headers/src/HttpRuleParser.cs

@@ -225,7 +225,7 @@ namespace Microsoft.Net.Http.Headers
                 return HttpParseResult.NotParsed;
                 return HttpParseResult.NotParsed;
             }
             }
 
 
-            // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char)
+            // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char)
             // If so, check whether the character is in the range 0-127. If not, it's an invalid value.
             // If so, check whether the character is in the range 0-127. If not, it's an invalid value.
             if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127))
             if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127))
             {
             {

+ 11 - 1
src/Http/Headers/src/MediaTypeHeaderValue.cs

@@ -650,6 +650,7 @@ namespace Microsoft.Net.Http.Headers
             {
             {
                 return true;
                 return true;
             }
             }
+
             if (set.Suffix.HasValue)
             if (set.Suffix.HasValue)
             {
             {
                 if (Suffix.HasValue)
                 if (Suffix.HasValue)
@@ -663,7 +664,10 @@ namespace Microsoft.Net.Http.Headers
             }
             }
             else
             else
             {
             {
-                return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase);
+                // If this subtype or suffix matches the subtype of the set,
+                // it is considered a subtype.
+                // Ex: application/json > application/val+json
+                return MatchesEitherSubtypeOrSuffix(set);
             }
             }
         }
         }
 
 
@@ -673,6 +677,12 @@ namespace Microsoft.Net.Http.Headers
                 set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
                 set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
         }
         }
 
 
+        private bool MatchesEitherSubtypeOrSuffix(MediaTypeHeaderValue set)
+        {
+            return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase) ||
+                set.SubType.Equals(Suffix, StringComparison.OrdinalIgnoreCase);
+        }
+
         private bool MatchesParameters(MediaTypeHeaderValue set)
         private bool MatchesParameters(MediaTypeHeaderValue set)
         {
         {
             if (set._parameters != null && set._parameters.Count != 0)
             if (set._parameters != null && set._parameters.Count != 0)

+ 1 - 1
src/Http/Headers/src/RangeItemHeaderValue.cs

@@ -169,7 +169,7 @@ namespace Microsoft.Net.Http.Headers
             current = current + fromLength;
             current = current + fromLength;
             current = current + HttpRuleParser.GetWhitespaceLength(input, current);
             current = current + HttpRuleParser.GetWhitespaceLength(input, current);
 
 
-            // Afer the first value, the '-' character must follow.
+            // After the first value, the '-' character must follow.
             if ((current == input.Length) || (input[current] != '-'))
             if ((current == input.Length) || (input[current] != '-'))
             {
             {
                 // We need a '-' character otherwise this can't be a valid range.
                 // We need a '-' character otherwise this can't be a valid range.

+ 1 - 1
src/Http/Headers/test/ContentDispositionHeaderValueTest.cs

@@ -465,7 +465,7 @@ namespace Microsoft.Net.Http.Headers
         {
         {
             { "inline", new ContentDispositionHeaderValue("inline") }, // @"This should be equivalent to not including the header at all."
             { "inline", new ContentDispositionHeaderValue("inline") }, // @"This should be equivalent to not including the header at all."
             { "inline;", new ContentDispositionHeaderValue("inline") },
             { "inline;", new ContentDispositionHeaderValue("inline") },
-            { "inline;name=", new ContentDispositionHeaderValue("inline") { Parameters = { new NameValueHeaderValue("name", "") } } }, // TODO: passing in a null value causes a strange assert on CoreCLR before the test even starts. Not reproducable in the body of a test.
+            { "inline;name=", new ContentDispositionHeaderValue("inline") { Parameters = { new NameValueHeaderValue("name", "") } } }, // TODO: passing in a null value causes a strange assert on CoreCLR before the test even starts. Not reproducible in the body of a test.
             { "inline;name=value", new ContentDispositionHeaderValue("inline") { Name  = "value" } },
             { "inline;name=value", new ContentDispositionHeaderValue("inline") { Name  = "value" } },
             { "inline;name=value;", new ContentDispositionHeaderValue("inline") { Name  = "value" } },
             { "inline;name=value;", new ContentDispositionHeaderValue("inline") { Name  = "value" } },
             { "inline;name=value;", new ContentDispositionHeaderValue("inline") { Name  = "value" } },
             { "inline;name=value;", new ContentDispositionHeaderValue("inline") { Name  = "value" } },

+ 1 - 1
src/Http/Headers/test/CookieHeaderValueTest.cs

@@ -286,7 +286,7 @@ namespace Microsoft.Net.Http.Headers
         public void CookieHeaderValue_ParseList_ExcludesInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
         public void CookieHeaderValue_ParseList_ExcludesInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
         {
         {
             var results = CookieHeaderValue.ParseList(input);
             var results = CookieHeaderValue.ParseList(input);
-            // ParseList aways returns a list, even if empty. TryParseList may return null (via out).
+            // ParseList always returns a list, even if empty. TryParseList may return null (via out).
             Assert.Equal(cookies ?? new List<CookieHeaderValue>(), results);
             Assert.Equal(cookies ?? new List<CookieHeaderValue>(), results);
         }
         }
 
 

+ 4 - 4
src/Http/Headers/test/EntityTagHeaderValueTest.cs

@@ -403,7 +403,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
+        public void ParseList_WithSomeInvalidValues_ExcludesInvalidValues()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -433,7 +433,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ParseStrictList_WithSomeInvlaidValues_Throws()
+        public void ParseStrictList_WithSomeInvalidValues_Throws()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -451,7 +451,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void TryParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
+        public void TryParseList_WithSomeInvalidValues_ExcludesInvalidValues()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -482,7 +482,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
+        public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {

+ 7 - 4
src/Http/Headers/test/MediaTypeHeaderValueTest.cs

@@ -617,7 +617,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
+        public void ParseList_WithSomeInvalidValues_IgnoresInvalidValues()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -640,7 +640,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ParseStrictList_WithSomeInvlaidValues_Throws()
+        public void ParseStrictList_WithSomeInvalidValues_Throws()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -651,7 +651,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void TryParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
+        public void TryParseList_WithSomeInvalidValues_IgnoresInvalidValues()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -676,7 +676,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
+        public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -750,6 +750,8 @@ namespace Microsoft.Net.Http.Headers
         [InlineData("application/entity+json", "application/entity+json")]
         [InlineData("application/entity+json", "application/entity+json")]
         [InlineData("application/*+json", "application/entity+json")]
         [InlineData("application/*+json", "application/entity+json")]
         [InlineData("application/*+json", "application/*+json")]
         [InlineData("application/*+json", "application/*+json")]
+        [InlineData("application/json", "application/problem+json")]
+        [InlineData("application/json", "application/vnd.restful+json")]
         [InlineData("application/*", "application/*+JSON")]
         [InlineData("application/*", "application/*+JSON")]
         [InlineData("application/vnd.github+json", "application/vnd.github+json")]
         [InlineData("application/vnd.github+json", "application/vnd.github+json")]
         [InlineData("application/*", "application/entity+JSON")]
         [InlineData("application/*", "application/entity+JSON")]
@@ -774,6 +776,7 @@ namespace Microsoft.Net.Http.Headers
         [InlineData("application/*+*", "application/json")]
         [InlineData("application/*+*", "application/json")]
         [InlineData("application/entity+*", "application/entity+json")] // We don't allow suffixes to be wildcards
         [InlineData("application/entity+*", "application/entity+json")] // We don't allow suffixes to be wildcards
         [InlineData("application/*+*", "application/entity+json")] // We don't allow suffixes to be wildcards
         [InlineData("application/*+*", "application/entity+json")] // We don't allow suffixes to be wildcards
+        [InlineData("application/entity+json", "application/entity")]
         public void IsSubSetOfWithSuffixes_NegativeCases(string set, string subset)
         public void IsSubSetOfWithSuffixes_NegativeCases(string set, string subset)
         {
         {
             // Arrange
             // Arrange

+ 6 - 6
src/Http/Headers/test/NameValueHeaderValueTest.cs

@@ -60,7 +60,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void Copy_NameOnly_SuccesfullyCopied()
+        public void Copy_NameOnly_SuccessfullyCopied()
         {
         {
             var pair0 = new NameValueHeaderValue("name");
             var pair0 = new NameValueHeaderValue("name");
             var pair1 = pair0.Copy();
             var pair1 = pair0.Copy();
@@ -95,7 +95,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void Copy_NameAndValue_SuccesfullyCopied()
+        public void Copy_NameAndValue_SuccessfullyCopied()
         {
         {
             var pair0 = new NameValueHeaderValue("name", "value");
             var pair0 = new NameValueHeaderValue("name", "value");
             var pair1 = pair0.Copy();
             var pair1 = pair0.Copy();
@@ -466,7 +466,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
+        public void ParseList_WithSomeInvalidValues_ExcludesInvalidValues()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -502,7 +502,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ParseStrictList_WithSomeInvlaidValues_Throws()
+        public void ParseStrictList_WithSomeInvalidValues_Throws()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -520,7 +520,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void TryParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
+        public void TryParseList_WithSomeInvalidValues_ExcludesInvalidValues()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -557,7 +557,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
+        public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {

+ 2 - 2
src/Http/Headers/test/RangeConditionHeaderValueTest.cs

@@ -39,7 +39,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ToString_UseDifferentrangeConditions_AllSerializedCorrectly()
+        public void ToString_UseDifferentRangeConditions_AllSerializedCorrectly()
         {
         {
             var rangeCondition = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"x\""));
             var rangeCondition = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"x\""));
             Assert.Equal("\"x\"", rangeCondition.ToString());
             Assert.Equal("\"x\"", rangeCondition.ToString());
@@ -49,7 +49,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void GetHashCode_UseSameAndDifferentrangeConditions_SameOrDifferentHashCodes()
+        public void GetHashCode_UseSameAndDifferentRangeConditions_SameOrDifferentHashCodes()
         {
         {
             var rangeCondition1 = new RangeConditionHeaderValue("\"x\"");
             var rangeCondition1 = new RangeConditionHeaderValue("\"x\"");
             var rangeCondition2 = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"x\""));
             var rangeCondition2 = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"x\""));

+ 1 - 1
src/Http/Headers/test/SetCookieHeaderValueTest.cs

@@ -389,7 +389,7 @@ namespace Microsoft.Net.Http.Headers
         public void SetCookieHeaderValue_ParseList_ExcludesInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
         public void SetCookieHeaderValue_ParseList_ExcludesInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
         {
         {
             var results = SetCookieHeaderValue.ParseList(input);
             var results = SetCookieHeaderValue.ParseList(input);
-            // ParseList aways returns a list, even if empty. TryParseList may return null (via out).
+            // ParseList always returns a list, even if empty. TryParseList may return null (via out).
             Assert.Equal(cookies ?? new List<SetCookieHeaderValue>(), results);
             Assert.Equal(cookies ?? new List<SetCookieHeaderValue>(), results);
         }
         }
 
 

+ 4 - 4
src/Http/Headers/test/StringWithQualityHeaderValueTest.cs

@@ -354,7 +354,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
+        public void ParseList_WithSomeInvalidValues_IgnoresInvalidValues()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -392,7 +392,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void ParseStrictList_WithSomeInvlaidValues_Throws()
+        public void ParseStrictList_WithSomeInvalidValues_Throws()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -412,7 +412,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void TryParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
+        public void TryParseList_WithSomeInvalidValues_IgnoresInvalidValues()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {
@@ -451,7 +451,7 @@ namespace Microsoft.Net.Http.Headers
         }
         }
 
 
         [Fact]
         [Fact]
-        public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
+        public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
         {
         {
             var inputs = new[]
             var inputs = new[]
             {
             {

+ 2 - 2
src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs

@@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http;
 namespace Microsoft.AspNetCore.Builder.Extensions
 namespace Microsoft.AspNetCore.Builder.Extensions
 {
 {
     /// <summary>
     /// <summary>
-    /// Respresents a middleware that maps a request path to a sub-request pipeline.
+    /// Represents a middleware that maps a request path to a sub-request pipeline.
     /// </summary>
     /// </summary>
     public class MapMiddleware
     public class MapMiddleware
     {
     {
@@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Builder.Extensions
         private readonly MapOptions _options;
         private readonly MapOptions _options;
 
 
         /// <summary>
         /// <summary>
-        /// Creates a new instace of <see cref="MapMiddleware"/>.
+        /// Creates a new instance of <see cref="MapMiddleware"/>.
         /// </summary>
         /// </summary>
         /// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
         /// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
         /// <param name="options">The middleware options.</param>
         /// <param name="options">The middleware options.</param>

+ 1 - 1
src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs

@@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http;
 namespace Microsoft.AspNetCore.Builder.Extensions
 namespace Microsoft.AspNetCore.Builder.Extensions
 {
 {
     /// <summary>
     /// <summary>
-    /// Respresents a middleware that runs a sub-request pipeline when a given predicate is matched.
+    /// Represents a middleware that runs a sub-request pipeline when a given predicate is matched.
     /// </summary>
     /// </summary>
     public class MapWhenMiddleware
     public class MapWhenMiddleware
     {
     {

+ 53 - 0
src/Http/Http.Abstractions/src/Extensions/ResponseTrailerExtensions.cs

@@ -0,0 +1,53 @@
+// 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.Features;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public static class ResponseTrailerExtensions
+    {
+        private const string Trailer = "Trailer";
+
+        /// <summary>
+        /// Adds the given trailer name to the 'Trailer' response header. This must happen before the response headers are sent.
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="trailerName"></param>
+        public static void DeclareTrailer(this HttpResponse response, string trailerName)
+        {
+            response.Headers.AppendCommaSeparatedValues(Trailer, trailerName);
+        }
+
+        /// <summary>
+        /// Indicates if the server supports sending trailer headers for this response.
+        /// </summary>
+        /// <param name="response"></param>
+        /// <returns></returns>
+        public static bool SupportsTrailers(this HttpResponse response)
+        {
+            var feature = response.HttpContext.Features.Get<IHttpResponseTrailersFeature>();
+            return feature?.Trailers != null && !feature.Trailers.IsReadOnly;
+        }
+
+        /// <summary>
+        /// Adds the given trailer header to the trailers collection to be sent at the end of the response body.
+        /// Check <see cref="SupportsTrailers" /> or an InvalidOperationException may be thrown.
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="trailerName"></param>
+        /// <param name="trailerValues"></param>
+        public static void AppendTrailer(this HttpResponse response, string trailerName, StringValues trailerValues)
+        {
+            var feature = response.HttpContext.Features.Get<IHttpResponseTrailersFeature>();
+            if (feature?.Trailers == null || feature.Trailers.IsReadOnly)
+            {
+                throw new InvalidOperationException("Trailers are not supported for this response.");
+            }
+
+            feature.Trailers.Append(trailerName, trailerValues);
+        }
+    }
+}

+ 13 - 13
src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs

@@ -74,13 +74,13 @@ namespace Microsoft.AspNetCore.Builder
                     throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
                     throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
                 }
                 }
 
 
-                var methodinfo = invokeMethods[0];
-                if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
+                var methodInfo = invokeMethods[0];
+                if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
                 {
                 {
                     throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
                     throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
                 }
                 }
 
 
-                var parameters = methodinfo.GetParameters();
+                var parameters = methodInfo.GetParameters();
                 if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
                 if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
                 {
                 {
                     throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
                     throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
@@ -92,10 +92,10 @@ namespace Microsoft.AspNetCore.Builder
                 var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
                 var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
                 if (parameters.Length == 1)
                 if (parameters.Length == 1)
                 {
                 {
-                    return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
+                    return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
                 }
                 }
 
 
-                var factory = Compile<object>(methodinfo, parameters);
+                var factory = Compile<object>(methodInfo, parameters);
 
 
                 return context =>
                 return context =>
                 {
                 {
@@ -142,13 +142,13 @@ namespace Microsoft.AspNetCore.Builder
             });
             });
         }
         }
 
 
-        private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
+        private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodInfo, ParameterInfo[] parameters)
         {
         {
             // If we call something like
             // If we call something like
             //
             //
             // public class Middleware
             // public class Middleware
             // {
             // {
-            //    public Task Invoke(HttpContext context, ILoggerFactory loggeryFactory)
+            //    public Task Invoke(HttpContext context, ILoggerFactory loggerFactory)
             //    {
             //    {
             //
             //
             //    }
             //    }
@@ -158,14 +158,14 @@ namespace Microsoft.AspNetCore.Builder
             // We'll end up with something like this:
             // We'll end up with something like this:
             //   Generic version:
             //   Generic version:
             //
             //
-            //   Task Invoke(Middleware instance, HttpContext httpContext, IServiceprovider provider)
+            //   Task Invoke(Middleware instance, HttpContext httpContext, IServiceProvider provider)
             //   {
             //   {
             //      return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
             //      return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
             //   }
             //   }
 
 
             //   Non generic version:
             //   Non generic version:
             //
             //
-            //   Task Invoke(object instance, HttpContext httpContext, IServiceprovider provider)
+            //   Task Invoke(object instance, HttpContext httpContext, IServiceProvider provider)
             //   {
             //   {
             //      return ((Middleware)instance).Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
             //      return ((Middleware)instance).Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
             //   }
             //   }
@@ -190,7 +190,7 @@ namespace Microsoft.AspNetCore.Builder
                 {
                 {
                     providerArg,
                     providerArg,
                     Expression.Constant(parameterType, typeof(Type)),
                     Expression.Constant(parameterType, typeof(Type)),
-                    Expression.Constant(methodinfo.DeclaringType, typeof(Type))
+                    Expression.Constant(methodInfo.DeclaringType, typeof(Type))
                 };
                 };
 
 
                 var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);
                 var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);
@@ -198,12 +198,12 @@ namespace Microsoft.AspNetCore.Builder
             }
             }
 
 
             Expression middlewareInstanceArg = instanceArg;
             Expression middlewareInstanceArg = instanceArg;
-            if (methodinfo.DeclaringType != typeof(T))
+            if (methodInfo.DeclaringType != typeof(T))
             {
             {
-                middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodinfo.DeclaringType);
+                middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodInfo.DeclaringType);
             }
             }
 
 
-            var body = Expression.Call(middlewareInstanceArg, methodinfo, methodArguments);
+            var body = Expression.Call(middlewareInstanceArg, methodInfo, methodArguments);
 
 
             var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
             var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
 
 

+ 1 - 1
src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs

@@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Builder.Extensions
         private readonly PathString _pathBase;
         private readonly PathString _pathBase;
 
 
         /// <summary>
         /// <summary>
-        /// Creates a new instace of <see cref="UsePathBaseMiddleware"/>.
+        /// Creates a new instance of <see cref="UsePathBaseMiddleware"/>.
         /// </summary>
         /// </summary>
         /// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
         /// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
         /// <param name="pathBase">The path base to extract.</param>
         /// <param name="pathBase">The path base to extract.</param>

+ 6 - 1
src/Http/Http.Abstractions/src/HostString.cs

@@ -35,7 +35,12 @@ namespace Microsoft.AspNetCore.Http
         /// <param name="port">A positive, greater than 0 value representing the port in the host string.</param>
         /// <param name="port">A positive, greater than 0 value representing the port in the host string.</param>
         public HostString(string host, int port)
         public HostString(string host, int port)
         {
         {
-            if(port <= 0)
+            if (host == null)
+            {
+                throw new ArgumentNullException(nameof(host));
+            }
+
+            if (port <= 0)
             {
             {
                 throw new ArgumentOutOfRangeException(nameof(port), Resources.Exception_PortMustBeGreaterThanZero);
                 throw new ArgumentOutOfRangeException(nameof(port), Resources.Exception_PortMustBeGreaterThanZero);
             }
             }

+ 2 - 2
src/Http/Http.Abstractions/src/PathString.cs

@@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Http
         private readonly string _value;
         private readonly string _value;
 
 
         /// <summary>
         /// <summary>
-        /// Initalize the path string with a given value. This value must be in unescaped format. Use
+        /// Initialize the path string with a given value. This value must be in unescaped format. Use
         /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format.
         /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format.
         /// </summary>
         /// </summary>
         /// <param name="value">The unescaped path to be assigned to the Value property.</param>
         /// <param name="value">The unescaped path to be assigned to the Value property.</param>
@@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Http
                 {
                 {
                     if (!requiresEscaping)
                     if (!requiresEscaping)
                     {
                     {
-                        // the current segument doesn't require escape
+                        // the current segment doesn't require escape
                         if (buffer == null)
                         if (buffer == null)
                         {
                         {
                             buffer = new StringBuilder(_value.Length * 3);
                             buffer = new StringBuilder(_value.Length * 3);

+ 1 - 1
src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs

@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Builder.Extensions
 
 
     public class MapPredicateMiddlewareTests
     public class MapPredicateMiddlewareTests
     {
     {
-        private static readonly Predicate NotImplementedPredicate = new Predicate(envionment => { throw new NotImplementedException(); });
+        private static readonly Predicate NotImplementedPredicate = new Predicate(environment => { throw new NotImplementedException(); });
 
 
         private static Task Success(HttpContext context)
         private static Task Success(HttpContext context)
         {
         {

+ 2 - 2
src/Http/Http.Abstractions/test/QueryStringTests.cs

@@ -59,10 +59,10 @@ namespace Microsoft.AspNetCore.Http.Abstractions
         [InlineData("", "value", "?=value")]
         [InlineData("", "value", "?=value")]
         [InlineData("", "", "?=")]
         [InlineData("", "", "?=")]
         [InlineData("", null, "?=")]
         [InlineData("", null, "?=")]
-        public void CreateNameValue_Success(string name, string value, string exepcted)
+        public void CreateNameValue_Success(string name, string value, string expected)
         {
         {
             var query = QueryString.Create(name, value);
             var query = QueryString.Create(name, value);
-            Assert.Equal(exepcted, query.Value);
+            Assert.Equal(expected, query.Value);
         }
         }
 
 
         [Fact]
         [Fact]

+ 4 - 4
src/Http/Http.Abstractions/test/UseMiddlewareTest.cs

@@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Http
         }
         }
 
 
         [Fact]
         [Fact]
-        public void UseMiddleware_MutlipleInvokeMethods_ThrowsException()
+        public void UseMiddleware_MultipleInvokeMethods_ThrowsException()
         {
         {
             var builder = new ApplicationBuilder(new DummyServiceProvider());
             var builder = new ApplicationBuilder(new DummyServiceProvider());
             builder.UseMiddleware(typeof(MiddlewareMultipleInvokesStub));
             builder.UseMiddleware(typeof(MiddlewareMultipleInvokesStub));
@@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Http
         }
         }
 
 
         [Fact]
         [Fact]
-        public void UseMiddleware_MutlipleInvokeAsyncMethods_ThrowsException()
+        public void UseMiddleware_MultipleInvokeAsyncMethods_ThrowsException()
         {
         {
             var builder = new ApplicationBuilder(new DummyServiceProvider());
             var builder = new ApplicationBuilder(new DummyServiceProvider());
             builder.UseMiddleware(typeof(MiddlewareMultipleInvokeAsyncStub));
             builder.UseMiddleware(typeof(MiddlewareMultipleInvokeAsyncStub));
@@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Http
         }
         }
 
 
         [Fact]
         [Fact]
-        public void UseMiddleware_MutlipleInvokeAndInvokeAsyncMethods_ThrowsException()
+        public void UseMiddleware_MultipleInvokeAndInvokeAsyncMethods_ThrowsException()
         {
         {
             var builder = new ApplicationBuilder(new DummyServiceProvider());
             var builder = new ApplicationBuilder(new DummyServiceProvider());
             builder.UseMiddleware(typeof(MiddlewareMultipleInvokeAndInvokeAsyncStub));
             builder.UseMiddleware(typeof(MiddlewareMultipleInvokeAndInvokeAsyncStub));
@@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Http
         }
         }
 
 
         [Fact]
         [Fact]
-        public void UseMiddlewareWithIvokeWithOutAndRefThrows()
+        public void UseMiddlewareWithInvokeWithOutAndRefThrows()
         {
         {
             var mockServiceProvider = new DummyServiceProvider();
             var mockServiceProvider = new DummyServiceProvider();
             var builder = new ApplicationBuilder(mockServiceProvider);
             var builder = new ApplicationBuilder(mockServiceProvider);

+ 1 - 1
src/Http/Http.Features/src/FeatureReferences.cs

@@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Http.Features
             }
             }
             else if (flush)
             else if (flush)
             {
             {
-                // Cache was cleared, but item retrived from current Collection for version
+                // Cache was cleared, but item retrieved from current Collection for version
                 // so use passed in revision rather than making another virtual call
                 // so use passed in revision rather than making another virtual call
                 Revision = revision;
                 Revision = revision;
             }
             }

+ 10 - 0
src/Http/Http.Features/src/IHttpResponseTrailersFeature.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.AspNetCore.Http.Features
+{
+    public interface IHttpResponseTrailersFeature
+    {
+        IHeaderDictionary Trailers { get; set; }
+    }
+}

+ 6 - 1
src/Http/Http/src/Features/FormFeature.cs

@@ -131,6 +131,11 @@ namespace Microsoft.AspNetCore.Http.Features
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
+            if (_request.ContentLength == 0)
+            {
+                return FormCollection.Empty;
+            }
+
             if (_options.BufferBody)
             if (_options.BufferBody)
             {
             {
                 _request.EnableRewind(_options.MemoryBufferThreshold, _options.BufferBodyLengthLimit);
                 _request.EnableRewind(_options.MemoryBufferThreshold, _options.BufferBodyLengthLimit);
@@ -221,7 +226,7 @@ namespace Microsoft.AspNetCore.Http.Features
                             //
                             //
                             // value
                             // value
 
 
-                            // Do not limit the key name length here because the mulipart headers length limit is already in effect.
+                            // Do not limit the key name length here because the multipart headers length limit is already in effect.
                             var key = formDataSection.Name;
                             var key = formDataSection.Name;
                             var value = await formDataSection.GetValueAsync();
                             var value = await formDataSection.GetValueAsync();
 
 

+ 6 - 3
src/Http/Http/src/HttpContextAccessor.cs

@@ -7,17 +7,20 @@ namespace Microsoft.AspNetCore.Http
 {
 {
     public class HttpContextAccessor : IHttpContextAccessor
     public class HttpContextAccessor : IHttpContextAccessor
     {
     {
-        private static AsyncLocal<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();
+        private static AsyncLocal<(string traceIdentifier, HttpContext context)> _httpContextCurrent = new AsyncLocal<(string traceIdentifier, HttpContext context)>();
 
 
         public HttpContext HttpContext
         public HttpContext HttpContext
         {
         {
             get
             get
             {
             {
-                return _httpContextCurrent.Value;
+                var value = _httpContextCurrent.Value;
+                // Only return the context if the stored request id matches the stored trace identifier
+                // context.TraceIdentifier is cleared by HttpContextFactory.Dispose.
+                return value.traceIdentifier == value.context?.TraceIdentifier ? value.context : null;
             }
             }
             set
             set
             {
             {
-                _httpContextCurrent.Value = value;
+                _httpContextCurrent.Value = (value?.TraceIdentifier, value);
             }
             }
         }
         }
     }
     }

+ 4 - 0
src/Http/Http/src/HttpContextFactory.cs

@@ -53,6 +53,10 @@ namespace Microsoft.AspNetCore.Http
             {
             {
                 _httpContextAccessor.HttpContext = null;
                 _httpContextAccessor.HttpContext = null;
             }
             }
+
+            // Null out the TraceIdentifier here as a sign that this request is done,
+            // the HttpContextAccessor implementation relies on this to detect that the request is over 
+            httpContext.TraceIdentifier = null;
         }
         }
     }
     }
 }
 }

+ 3 - 3
src/Http/Http/test/DefaultHttpContextTests.cs

@@ -157,7 +157,7 @@ namespace Microsoft.AspNetCore.Http
             features.Set<IHttpResponseFeature>(new HttpResponseFeature());
             features.Set<IHttpResponseFeature>(new HttpResponseFeature());
             features.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
             features.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
 
 
-            // featurecollection is set. all cached interfaces are null.
+            // FeatureCollection is set. all cached interfaces are null.
             var context = new DefaultHttpContext(features);
             var context = new DefaultHttpContext(features);
             TestAllCachedFeaturesAreNull(context, features);
             TestAllCachedFeaturesAreNull(context, features);
             Assert.Equal(3, features.Count());
             Assert.Equal(3, features.Count());
@@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Http
             TestAllCachedFeaturesAreSet(context, features);
             TestAllCachedFeaturesAreSet(context, features);
             Assert.NotEqual(3, features.Count());
             Assert.NotEqual(3, features.Count());
 
 
-            // featurecollection is null. and all cached interfaces are null.
+            // FeatureCollection is null. and all cached interfaces are null.
             // only top level is tested because child objects are inaccessible.
             // only top level is tested because child objects are inaccessible.
             context.Uninitialize();
             context.Uninitialize();
             TestCachedFeaturesAreNull(context, null);
             TestCachedFeaturesAreNull(context, null);
@@ -177,7 +177,7 @@ namespace Microsoft.AspNetCore.Http
             newFeatures.Set<IHttpResponseFeature>(new HttpResponseFeature());
             newFeatures.Set<IHttpResponseFeature>(new HttpResponseFeature());
             newFeatures.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
             newFeatures.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
 
 
-            // featurecollection is set to newFeatures. all cached interfaces are null.
+            // FeatureCollection is set to newFeatures. all cached interfaces are null.
             context.Initialize(newFeatures);
             context.Initialize(newFeatures);
             TestAllCachedFeaturesAreNull(context, newFeatures);
             TestAllCachedFeaturesAreNull(context, newFeatures);
             Assert.Equal(3, newFeatures.Count());
             Assert.Equal(3, newFeatures.Count());

+ 17 - 0
src/Http/Http/test/Features/FormFeatureTests.cs

@@ -12,6 +12,23 @@ namespace Microsoft.AspNetCore.Http.Features
 {
 {
     public class FormFeatureTests
     public class FormFeatureTests
     {
     {
+        [Fact]
+        public async Task ReadFormAsync_0ContentLength_ReturnsEmptyForm()
+        {
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.ContentLength = 0;
+
+            var formFeature = new FormFeature(context.Request, new FormOptions());
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = await context.Request.ReadFormAsync();
+
+            Assert.Same(FormCollection.Empty, formCollection);
+        }
+
         [Theory]
         [Theory]
         [InlineData(true)]
         [InlineData(true)]
         [InlineData(false)]
         [InlineData(false)]

+ 1 - 1
src/Http/Http/test/HeaderDictionaryTests.cs

@@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Http
         }
         }
 
 
         [Fact]
         [Fact]
-        public void EmtpyQuotedHeaderSegmentsAreIgnored()
+        public void EmptyQuotedHeaderSegmentsAreIgnored()
         {
         {
             var headers = new HeaderDictionary(
             var headers = new HeaderDictionary(
                new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
                new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)

+ 197 - 0
src/Http/Http/test/HttpContextAccessorTests.cs

@@ -0,0 +1,197 @@
+// 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.WebSockets;
+using System.Reflection;
+using System.Security.Claims;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class HttpContextAccessorTests
+    {
+        [Fact]
+        public async Task HttpContextAccessor_GettingHttpContextReturnsHttpContext()
+        {
+            var accessor = new HttpContextAccessor();
+
+            var context = new DefaultHttpContext();
+            context.TraceIdentifier = "1";
+            accessor.HttpContext = context;
+
+            await Task.Delay(100);
+
+            Assert.Same(context, accessor.HttpContext);
+        }
+
+        [Fact]
+        public void HttpContextAccessor_GettingHttpContextWithOutSettingReturnsNull()
+        {
+            var accessor = new HttpContextAccessor();
+
+            Assert.Null(accessor.HttpContext);
+        }
+
+        [Fact]
+        public async Task HttpContextAccessor_GettingHttpContextReturnsNullHttpContextIfSetToNull()
+        {
+            var accessor = new HttpContextAccessor();
+
+            var context = new DefaultHttpContext();
+            context.TraceIdentifier = "1";
+            accessor.HttpContext = context;
+
+            var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var waitForNullTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var afterNullCheckTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+            ThreadPool.QueueUserWorkItem(async _ =>
+            {
+                // The HttpContext flows with the execution context
+                Assert.Same(context, accessor.HttpContext);
+
+                checkAsyncFlowTcs.SetResult(null);
+
+                await waitForNullTcs.Task;
+
+                try
+                {
+                    Assert.Null(accessor.HttpContext);
+
+                    afterNullCheckTcs.SetResult(null);
+                }
+                catch (Exception ex)
+                {
+                    afterNullCheckTcs.SetException(ex);
+                }
+            });
+
+            await checkAsyncFlowTcs.Task;
+
+            // Null out the accessor
+            accessor.HttpContext = null;
+            context.TraceIdentifier = null;
+
+            waitForNullTcs.SetResult(null);
+
+            Assert.Null(accessor.HttpContext);
+
+            await afterNullCheckTcs.Task;
+        }
+
+        [Fact]
+        public async Task HttpContextAccessor_GettingHttpContextReturnsNullHttpContextIfDifferentTraceIdentifier()
+        {
+            var accessor = new HttpContextAccessor();
+
+            var context = new DefaultHttpContext();
+            context.TraceIdentifier = "1";
+            accessor.HttpContext = context;
+
+            var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var waitForNullTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+            var afterNullCheckTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+            ThreadPool.QueueUserWorkItem(async _ =>
+            {
+                // The HttpContext flows with the execution context
+                Assert.Same(context, accessor.HttpContext);
+
+                checkAsyncFlowTcs.SetResult(null);
+
+                await waitForNullTcs.Task;
+
+                try
+                {
+                    Assert.Null(accessor.HttpContext);
+
+                    afterNullCheckTcs.SetResult(null);
+                }
+                catch (Exception ex)
+                {
+                    afterNullCheckTcs.SetException(ex);
+                }
+            });
+
+            await checkAsyncFlowTcs.Task;
+
+            // Reset the trace identifier on the first request
+            context.TraceIdentifier = null;
+
+            // Set a new http context
+            var context2 = new DefaultHttpContext();
+            context2.TraceIdentifier = "2";
+            accessor.HttpContext = context2;
+
+            waitForNullTcs.SetResult(null);
+
+            Assert.Same(context2, accessor.HttpContext);
+
+            await afterNullCheckTcs.Task;
+        }
+
+        [Fact]
+        public async Task HttpContextAccessor_GettingHttpContextDoesNotFlowIfAccessorSetToNull()
+        {
+            var accessor = new HttpContextAccessor();
+
+            var context = new DefaultHttpContext();
+            context.TraceIdentifier = "1";
+            accessor.HttpContext = context;
+
+            var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+            accessor.HttpContext = null;
+
+            ThreadPool.QueueUserWorkItem(_ =>
+            {
+                try
+                {
+                    // The HttpContext flows with the execution context
+                    Assert.Null(accessor.HttpContext);
+                    checkAsyncFlowTcs.SetResult(null);
+                }
+                catch (Exception ex)
+                {
+                    checkAsyncFlowTcs.SetException(ex);
+                }
+            });
+
+            await checkAsyncFlowTcs.Task;
+        }
+
+        [Fact]
+        public async Task HttpContextAccessor_GettingHttpContextDoesNotFlowIfExecutionContextDoesNotFlow()
+        {
+            var accessor = new HttpContextAccessor();
+
+            var context = new DefaultHttpContext();
+            context.TraceIdentifier = "1";
+            accessor.HttpContext = context;
+
+            var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+            ThreadPool.UnsafeQueueUserWorkItem(_ =>
+            {
+                try
+                {
+                    // The HttpContext flows with the execution context
+                    Assert.Null(accessor.HttpContext);
+                    checkAsyncFlowTcs.SetResult(null);
+                }
+                catch (Exception ex)
+                {
+                    checkAsyncFlowTcs.SetException(ex);
+                }
+            }, null);
+
+            await checkAsyncFlowTcs.Task;
+        }
+    }
+}

+ 21 - 1
src/Http/Http/test/HttpContextFactoryTests.cs

@@ -22,7 +22,27 @@ namespace Microsoft.AspNetCore.Http
             var context = contextFactory.Create(new FeatureCollection());
             var context = contextFactory.Create(new FeatureCollection());
 
 
             // Assert
             // Assert
-            Assert.True(ReferenceEquals(context, accessor.HttpContext));
+            Assert.Same(context, accessor.HttpContext);
+        }
+
+        [Fact]
+        public void DisposeHttpContextSetsHttpContextAccessorToNull()
+        {
+            // Arrange
+            var accessor = new HttpContextAccessor();
+            var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()), accessor);
+
+            // Act
+            var context = contextFactory.Create(new FeatureCollection());
+            var traceIdentifier = context.TraceIdentifier;
+
+            // Assert
+            Assert.Same(context, accessor.HttpContext);
+
+            contextFactory.Dispose(context);
+
+            Assert.Null(accessor.HttpContext);
+            Assert.NotEqual(traceIdentifier, context.TraceIdentifier);
         }
         }
 
 
         [Fact]
         [Fact]

+ 12 - 12
src/Http/Http/test/ResponseCookiesTest.cs

@@ -15,13 +15,13 @@ namespace Microsoft.AspNetCore.Http.Tests
         {
         {
             var headers = new HeaderDictionary();
             var headers = new HeaderDictionary();
             var cookies = new ResponseCookies(headers, null);
             var cookies = new ResponseCookies(headers, null);
-            var testcookie = "TestCookie";
+            var testCookie = "TestCookie";
 
 
-            cookies.Delete(testcookie);
+            cookies.Delete(testCookie);
 
 
             var cookieHeaderValues = headers[HeaderNames.SetCookie];
             var cookieHeaderValues = headers[HeaderNames.SetCookie];
             Assert.Single(cookieHeaderValues);
             Assert.Single(cookieHeaderValues);
-            Assert.StartsWith(testcookie, cookieHeaderValues[0]);
+            Assert.StartsWith(testCookie, cookieHeaderValues[0]);
             Assert.Contains("path=/", cookieHeaderValues[0]);
             Assert.Contains("path=/", cookieHeaderValues[0]);
             Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
             Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
         }
         }
@@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Http.Tests
         {
         {
             var headers = new HeaderDictionary();
             var headers = new HeaderDictionary();
             var cookies = new ResponseCookies(headers, null);
             var cookies = new ResponseCookies(headers, null);
-            var testcookie = "TestCookie";
+            var testCookie = "TestCookie";
             var time = new DateTimeOffset(2000, 1, 1, 1, 1, 1, 1, TimeSpan.Zero);
             var time = new DateTimeOffset(2000, 1, 1, 1, 1, 1, 1, TimeSpan.Zero);
             var options = new CookieOptions
             var options = new CookieOptions
             {
             {
@@ -43,11 +43,11 @@ namespace Microsoft.AspNetCore.Http.Tests
                 SameSite = SameSiteMode.Lax
                 SameSite = SameSiteMode.Lax
             };
             };
 
 
-            cookies.Delete(testcookie, options);
+            cookies.Delete(testCookie, options);
 
 
             var cookieHeaderValues = headers[HeaderNames.SetCookie];
             var cookieHeaderValues = headers[HeaderNames.SetCookie];
             Assert.Single(cookieHeaderValues);
             Assert.Single(cookieHeaderValues);
-            Assert.StartsWith(testcookie, cookieHeaderValues[0]);
+            Assert.StartsWith(testCookie, cookieHeaderValues[0]);
             Assert.Contains("path=/", cookieHeaderValues[0]);
             Assert.Contains("path=/", cookieHeaderValues[0]);
             Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
             Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
             Assert.Contains("secure", cookieHeaderValues[0]);
             Assert.Contains("secure", cookieHeaderValues[0]);
@@ -60,14 +60,14 @@ namespace Microsoft.AspNetCore.Http.Tests
         {
         {
             var headers = new HeaderDictionary();
             var headers = new HeaderDictionary();
             var cookies = new ResponseCookies(headers, null);
             var cookies = new ResponseCookies(headers, null);
-            var testcookie = "TestCookie";
+            var testCookie = "TestCookie";
 
 
-            cookies.Append(testcookie, testcookie);
-            cookies.Delete(testcookie);
+            cookies.Append(testCookie, testCookie);
+            cookies.Delete(testCookie);
 
 
             var cookieHeaderValues = headers[HeaderNames.SetCookie];
             var cookieHeaderValues = headers[HeaderNames.SetCookie];
             Assert.Single(cookieHeaderValues);
             Assert.Single(cookieHeaderValues);
-            Assert.StartsWith(testcookie, cookieHeaderValues[0]);
+            Assert.StartsWith(testCookie, cookieHeaderValues[0]);
             Assert.Contains("path=/", cookieHeaderValues[0]);
             Assert.Contains("path=/", cookieHeaderValues[0]);
             Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
             Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
         }
         }
@@ -80,9 +80,9 @@ namespace Microsoft.AspNetCore.Http.Tests
             var cookieOptions = new CookieOptions();
             var cookieOptions = new CookieOptions();
             var maxAgeTime = TimeSpan.FromHours(1);
             var maxAgeTime = TimeSpan.FromHours(1);
             cookieOptions.MaxAge = TimeSpan.FromHours(1);
             cookieOptions.MaxAge = TimeSpan.FromHours(1);
-            var testcookie = "TestCookie";
+            var testCookie = "TestCookie";
 
 
-            cookies.Append(testcookie, testcookie, cookieOptions);
+            cookies.Append(testCookie, testCookie, cookieOptions);
 
 
             var cookieHeaderValues = headers[HeaderNames.SetCookie];
             var cookieHeaderValues = headers[HeaderNames.SetCookie];
             Assert.Single(cookieHeaderValues);
             Assert.Single(cookieHeaderValues);

+ 2 - 2
src/Http/Owin/src/OwinExtensions.cs

@@ -34,11 +34,11 @@ namespace Microsoft.AspNetCore.Builder
             {
             {
                 Func<RequestDelegate, RequestDelegate> middleware1 = next1 =>
                 Func<RequestDelegate, RequestDelegate> middleware1 = next1 =>
                 {
                 {
-                    AppFunc exitMiddlware = env =>
+                    AppFunc exitMiddleware = env =>
                     {
                     {
                         return next1((HttpContext)env[typeof(HttpContext).FullName]);
                         return next1((HttpContext)env[typeof(HttpContext).FullName]);
                     };
                     };
-                    var app = middleware(exitMiddlware);
+                    var app = middleware(exitMiddleware);
                     return httpContext =>
                     return httpContext =>
                     {
                     {
                         // Use the existing OWIN env if there is one.
                         // Use the existing OWIN env if there is one.

+ 2 - 2
src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs

@@ -103,10 +103,10 @@ namespace Microsoft.AspNetCore.Owin
         // 2. The middleware inserts an alternate Accept signature into the OWIN environment.
         // 2. The middleware inserts an alternate Accept signature into the OWIN environment.
         // 3. The middleware invokes Next and stores Next's Task locally. It then returns an alternate Task to the server.
         // 3. The middleware invokes Next and stores Next's Task locally. It then returns an alternate Task to the server.
         // 4. The OwinFeatureCollection adapts the alternate Accept signature to IHttpWebSocketFeature.AcceptAsync.
         // 4. The OwinFeatureCollection adapts the alternate Accept signature to IHttpWebSocketFeature.AcceptAsync.
-        // 5. A component later in the pipleline invokes IHttpWebSocketFeature.AcceptAsync (mapped to AcceptWebSocketAsync).
+        // 5. A component later in the pipeline invokes IHttpWebSocketFeature.AcceptAsync (mapped to AcceptWebSocketAsync).
         // 6. The middleware calls the OWIN Accept, providing a local callback, and returns an incomplete Task.
         // 6. The middleware calls the OWIN Accept, providing a local callback, and returns an incomplete Task.
         // 7. The middleware completes the alternate Task it returned from Invoke, telling the server that the request pipeline has completed.
         // 7. The middleware completes the alternate Task it returned from Invoke, telling the server that the request pipeline has completed.
-        // 8. The server invokes the middleware's callback, which creats a WebSocket adapter complete's the orriginal Accept Task with it.
+        // 8. The server invokes the middleware's callback, which creates a WebSocket adapter and completes the original Accept Task with it.
         // 9. The middleware waits while the application uses the WebSocket, where the end is signaled by the Next's Task completion.
         // 9. The middleware waits while the application uses the WebSocket, where the end is signaled by the Next's Task completion.
         public static AppFunc AdaptWebSockets(AppFunc next)
         public static AppFunc AdaptWebSockets(AppFunc next)
         {
         {

+ 1 - 1
src/Http/Owin/test/OwinEnvironmentTests.cs

@@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Owin
         }
         }
 
 
         [Fact]
         [Fact]
-        public void OwinEnvironmentImpelmentsGetEnumerator()
+        public void OwinEnvironmentImplementsGetEnumerator()
         {
         {
             var owinEnvironment = new OwinEnvironment(CreateContext());
             var owinEnvironment = new OwinEnvironment(CreateContext());
 
 

+ 8 - 8
src/Http/WebUtilities/src/FormReader.cs

@@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         public KeyValuePair<string, string>? ReadNextPair()
         public KeyValuePair<string, string>? ReadNextPair()
         {
         {
             ReadNextPairImpl();
             ReadNextPairImpl();
-            if (ReadSucceded())
+            if (ReadSucceeded())
             {
             {
                 return new KeyValuePair<string, string>(_currentKey, _currentValue);
                 return new KeyValuePair<string, string>(_currentKey, _currentValue);
             }
             }
@@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         public async Task<KeyValuePair<string, string>?> ReadNextPairAsync(CancellationToken cancellationToken = new CancellationToken())
         public async Task<KeyValuePair<string, string>?> ReadNextPairAsync(CancellationToken cancellationToken = new CancellationToken())
         {
         {
             await ReadNextPairAsyncImpl(cancellationToken);
             await ReadNextPairAsyncImpl(cancellationToken);
-            if (ReadSucceded())
+            if (ReadSucceeded())
             {
             {
                 return new KeyValuePair<string, string>(_currentKey, _currentValue);
                 return new KeyValuePair<string, string>(_currentKey, _currentValue);
             }
             }
@@ -189,11 +189,11 @@ namespace Microsoft.AspNetCore.WebUtilities
             return true;
             return true;
         }
         }
 
 
-        private bool TryReadWord(char seperator, int limit, out string value)
+        private bool TryReadWord(char separator, int limit, out string value)
         {
         {
             do
             do
             {
             {
-                if (ReadChar(seperator, limit, out value))
+                if (ReadChar(separator, limit, out value))
                 {
                 {
                     return true;
                     return true;
                 }
                 }
@@ -201,7 +201,7 @@ namespace Microsoft.AspNetCore.WebUtilities
             return false;
             return false;
         }
         }
 
 
-        private bool ReadChar(char seperator, int limit, out string word)
+        private bool ReadChar(char separator, int limit, out string word)
         {
         {
             // End
             // End
             if (_bufferCount == 0)
             if (_bufferCount == 0)
@@ -213,7 +213,7 @@ namespace Microsoft.AspNetCore.WebUtilities
             var c = _buffer[_bufferOffset++];
             var c = _buffer[_bufferOffset++];
             _bufferCount--;
             _bufferCount--;
 
 
-            if (c == seperator)
+            if (c == separator)
             {
             {
                 word = BuildWord();
                 word = BuildWord();
                 return true;
                 return true;
@@ -283,14 +283,14 @@ namespace Microsoft.AspNetCore.WebUtilities
             return accumulator.GetResults();
             return accumulator.GetResults();
         }
         }
 
 
-        private bool ReadSucceded()
+        private bool ReadSucceeded()
         {
         {
             return _currentKey != null && _currentValue != null;
             return _currentKey != null && _currentValue != null;
         }
         }
 
 
         private void Append(ref KeyValueAccumulator accumulator)
         private void Append(ref KeyValueAccumulator accumulator)
         {
         {
-            if (ReadSucceded())
+            if (ReadSucceeded())
             {
             {
                 accumulator.Append(_currentKey, _currentValue);
                 accumulator.Append(_currentKey, _currentValue);
                 if (accumulator.ValueCount > ValueCountLimit)
                 if (accumulator.ValueCount > ValueCountLimit)

+ 86 - 12
src/Http/WebUtilities/src/HttpResponseStreamWriter.cs

@@ -3,7 +3,9 @@
 
 
 using System;
 using System;
 using System.Buffers;
 using System.Buffers;
+using System.Diagnostics;
 using System.IO;
 using System.IO;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -150,33 +152,65 @@ namespace Microsoft.AspNetCore.WebUtilities
             }
             }
         }
         }
 
 
-        public override async Task WriteAsync(char value)
+        public override Task WriteAsync(char value)
         {
         {
             if (_disposed)
             if (_disposed)
             {
             {
-                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+                return GetObjectDisposedTask();
             }
             }
 
 
             if (_charBufferCount == _charBufferSize)
             if (_charBufferCount == _charBufferSize)
             {
             {
-                await FlushInternalAsync(flushEncoder: false);
+                return WriteAsyncAwaited(value);
+            }
+            else
+            {
+                // Enough room in buffer, no need to go async
+                _charBuffer[_charBufferCount] = value;
+                _charBufferCount++;
+                return Task.CompletedTask;
             }
             }
+        }
+
+        private async Task WriteAsyncAwaited(char value)
+        {
+            Debug.Assert(_charBufferCount == _charBufferSize);
+
+            await FlushInternalAsync(flushEncoder: false);
 
 
             _charBuffer[_charBufferCount] = value;
             _charBuffer[_charBufferCount] = value;
             _charBufferCount++;
             _charBufferCount++;
         }
         }
 
 
-        public override async Task WriteAsync(char[] values, int index, int count)
+        public override Task WriteAsync(char[] values, int index, int count)
         {
         {
             if (_disposed)
             if (_disposed)
             {
             {
-                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+                return GetObjectDisposedTask();
             }
             }
 
 
-            if (values == null)
+            if (values == null || count == 0)
             {
             {
-                return;
+                return Task.CompletedTask;
+            }
+
+            var remaining = _charBufferSize - _charBufferCount;
+            if (remaining >= count)
+            {
+                // Enough room in buffer, no need to go async
+                CopyToCharBuffer(values, ref index, ref count);
+                return Task.CompletedTask;
             }
             }
+            else
+            {
+                return WriteAsyncAwaited(values, index, count);
+            }
+        }
+
+        private async Task WriteAsyncAwaited(char[] values, int index, int count)
+        {
+            Debug.Assert(count > 0);
+            Debug.Assert(_charBufferSize - _charBufferCount > count);
 
 
             while (count > 0)
             while (count > 0)
             {
             {
@@ -186,22 +220,43 @@ namespace Microsoft.AspNetCore.WebUtilities
                 }
                 }
 
 
                 CopyToCharBuffer(values, ref index, ref count);
                 CopyToCharBuffer(values, ref index, ref count);
+                Debug.Assert(count == 0);
             }
             }
         }
         }
 
 
-        public override async Task WriteAsync(string value)
+        public override Task WriteAsync(string value)
         {
         {
             if (_disposed)
             if (_disposed)
             {
             {
-                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+                return GetObjectDisposedTask();
             }
             }
 
 
-            if (value == null)
+            var count = value?.Length ?? 0;
+            if (count == 0)
             {
             {
-                return;
+                return Task.CompletedTask;
             }
             }
 
 
+            var remaining = _charBufferSize - _charBufferCount;
+            if (remaining >= count)
+            {
+                // Enough room in buffer, no need to go async
+                CopyToCharBuffer(value);
+                return Task.CompletedTask;
+            }
+            else
+            {
+                return WriteAsyncAwaited(value);
+            }
+        }
+
+        private async Task WriteAsyncAwaited(string value)
+        {
             var count = value.Length;
             var count = value.Length;
+
+            Debug.Assert(count > 0);
+            Debug.Assert(_charBufferSize - _charBufferCount < count);
+
             var index = 0;
             var index = 0;
             while (count > 0)
             while (count > 0)
             {
             {
@@ -231,7 +286,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         {
         {
             if (_disposed)
             if (_disposed)
             {
             {
-                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+                return GetObjectDisposedTask();
             }
             }
 
 
             return FlushInternalAsync(flushEncoder: true);
             return FlushInternalAsync(flushEncoder: true);
@@ -306,6 +361,19 @@ namespace Microsoft.AspNetCore.WebUtilities
             }
             }
         }
         }
 
 
+        private void CopyToCharBuffer(string value)
+        {
+            Debug.Assert(_charBufferSize - _charBufferCount >= value.Length);
+
+            value.CopyTo(
+                sourceIndex: 0,
+                destination: _charBuffer,
+                destinationIndex: _charBufferCount,
+                count: value.Length);
+
+            _charBufferCount += value.Length;
+        }
+
         private void CopyToCharBuffer(string value, ref int index, ref int count)
         private void CopyToCharBuffer(string value, ref int index, ref int count)
         {
         {
             var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
             var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
@@ -336,5 +404,11 @@ namespace Microsoft.AspNetCore.WebUtilities
             index += remaining;
             index += remaining;
             count -= remaining;
             count -= remaining;
         }
         }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static Task GetObjectDisposedTask()
+        {
+            return Task.FromException(new ObjectDisposedException(nameof(HttpResponseStreamWriter)));
+        }
     }
     }
 }
 }

+ 1 - 1
src/Http/WebUtilities/src/QueryHelpers.cs

@@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.WebUtilities
             var anchorIndex = uri.IndexOf('#');
             var anchorIndex = uri.IndexOf('#');
             var uriToBeAppended = uri;
             var uriToBeAppended = uri;
             var anchorText = "";
             var anchorText = "";
-            // If there is an anchor, then the query string must be inserted before its first occurance.
+            // If there is an anchor, then the query string must be inserted before its first occurence.
             if (anchorIndex != -1)
             if (anchorIndex != -1)
             {
             {
                 anchorText = uri.Substring(anchorIndex);
                 anchorText = uri.Substring(anchorIndex);

+ 12 - 12
src/Http/WebUtilities/test/MultipartReaderTests.cs

@@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_ReadSinglePartBody_Success()
+        public async Task MultipartReader_ReadSinglePartBody_Success()
         {
         {
             var stream = MakeStream(OnePartBody);
             var stream = MakeStream(OnePartBody);
             var reader = new MultipartReader(Boundary, stream);
             var reader = new MultipartReader(Boundary, stream);
@@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_HeaderCountExceeded_Throws()
+        public async Task MultipartReader_HeaderCountExceeded_Throws()
         {
         {
             var stream = MakeStream(OnePartBodyTwoHeaders);
             var stream = MakeStream(OnePartBodyTwoHeaders);
             var reader = new MultipartReader(Boundary, stream)
             var reader = new MultipartReader(Boundary, stream)
@@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_HeadersLengthExceeded_Throws()
+        public async Task MultipartReader_HeadersLengthExceeded_Throws()
         {
         {
             var stream = MakeStream(OnePartBodyTwoHeaders);
             var stream = MakeStream(OnePartBodyTwoHeaders);
             var reader = new MultipartReader(Boundary, stream)
             var reader = new MultipartReader(Boundary, stream)
@@ -149,7 +149,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_ReadSinglePartBodyWithTrailingWhitespace_Success()
+        public async Task MultipartReader_ReadSinglePartBodyWithTrailingWhitespace_Success()
         {
         {
             var stream = MakeStream(OnePartBodyWithTrailingWhitespace);
             var stream = MakeStream(OnePartBodyWithTrailingWhitespace);
             var reader = new MultipartReader(Boundary, stream);
             var reader = new MultipartReader(Boundary, stream);
@@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_ReadSinglePartBodyWithoutLastCRLF_Success()
+        public async Task MultipartReader_ReadSinglePartBodyWithoutLastCRLF_Success()
         {
         {
             var stream = MakeStream(OnePartBodyWithoutFinalCRLF);
             var stream = MakeStream(OnePartBodyWithoutFinalCRLF);
             var reader = new MultipartReader(Boundary, stream);
             var reader = new MultipartReader(Boundary, stream);
@@ -183,7 +183,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_ReadTwoPartBody_Success()
+        public async Task MultipartReader_ReadTwoPartBody_Success()
         {
         {
             var stream = MakeStream(TwoPartBody);
             var stream = MakeStream(TwoPartBody);
             var reader = new MultipartReader(Boundary, stream);
             var reader = new MultipartReader(Boundary, stream);
@@ -209,7 +209,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_ReadTwoPartBodyWithUnicodeFileName_Success()
+        public async Task MultipartReader_ReadTwoPartBodyWithUnicodeFileName_Success()
         {
         {
             var stream = MakeStream(TwoPartBodyWithUnicodeFileName);
             var stream = MakeStream(TwoPartBodyWithUnicodeFileName);
             var reader = new MultipartReader(Boundary, stream);
             var reader = new MultipartReader(Boundary, stream);
@@ -235,7 +235,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_ThreePartBody_Success()
+        public async Task MultipartReader_ThreePartBody_Success()
         {
         {
             var stream = MakeStream(ThreePartBody);
             var stream = MakeStream(ThreePartBody);
             var reader = new MultipartReader(Boundary, stream);
             var reader = new MultipartReader(Boundary, stream);
@@ -270,7 +270,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public void MutipartReader_BufferSizeMustBeLargerThanBoundary_Throws()
+        public void MultipartReader_BufferSizeMustBeLargerThanBoundary_Throws()
         {
         {
             var stream = MakeStream(ThreePartBody);
             var stream = MakeStream(ThreePartBody);
             Assert.Throws<ArgumentOutOfRangeException>(() =>
             Assert.Throws<ArgumentOutOfRangeException>(() =>
@@ -280,7 +280,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_TwoPartBodyIncompleteBuffer_TwoSectionsReadSuccessfullyThirdSectionThrows()
+        public async Task MultipartReader_TwoPartBodyIncompleteBuffer_TwoSectionsReadSuccessfullyThirdSectionThrows()
         {
         {
             var stream = MakeStream(TwoPartBodyIncompleteBuffer);
             var stream = MakeStream(TwoPartBodyIncompleteBuffer);
             var reader = new MultipartReader(Boundary, stream);
             var reader = new MultipartReader(Boundary, stream);
@@ -311,7 +311,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_ReadInvalidUtf8Header_ReplacementCharacters()
+        public async Task MultipartReader_ReadInvalidUtf8Header_ReplacementCharacters()
         {
         {
             var body1 =
             var body1 =
 "--9051914041544843365972754266\r\n" +
 "--9051914041544843365972754266\r\n" +
@@ -346,7 +346,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         }
         }
 
 
         [Fact]
         [Fact]
-        public async Task MutipartReader_ReadInvalidUtf8SurrogateHeader_ReplacementCharacters()
+        public async Task MultipartReader_ReadInvalidUtf8SurrogateHeader_ReplacementCharacters()
         {
         {
             var body1 =
             var body1 =
 "--9051914041544843365972754266\r\n" +
 "--9051914041544843365972754266\r\n" +

+ 1 - 1
src/Http/samples/SampleApp/SampleApp.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
+    <TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
     <OutputType>Exe</OutputType>
     <OutputType>Exe</OutputType>
   </PropertyGroup>
   </PropertyGroup>