Skip to content

Commit dafd01a

Browse files
Support tab characters in headers and unify validation code between servers. (#40671)
* Support tab characters in headers and unify validation code between servers. * Preserve non-ASCII header value char treatment in http.sys.
1 parent f87f8b5 commit dafd01a

File tree

11 files changed

+49
-9
lines changed

11 files changed

+49
-9
lines changed

src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<Compile Include="$(SharedSourceRoot)HttpSys\**\*.cs" />
1919
<Compile Include="$(SharedSourceRoot)Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
2020
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\StringUtilities.cs" LinkBase="ServerInfrastructure\StringUtilities.cs" />
21+
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\HttpCharacters.cs" LinkBase="ServerInfrastructure\HttpCharacters.cs" />
2122
<Compile Include="$(SharedSourceRoot)TaskToApm.cs" />
2223
</ItemGroup>
2324

src/Servers/HttpSys/test/FunctionalTests/ResponseHeaderTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,27 @@ public async Task ResponseHeaders_ServerSendsCustomHeaders_Success()
113113
}
114114
}
115115

116+
[ConditionalFact]
117+
public async Task ResponseHeaders_ServerSendsNonAsciiHeaders_Success()
118+
{
119+
string address;
120+
using (Utilities.CreateHttpServer(out address, httpContext =>
121+
{
122+
var responseInfo = httpContext.Features.Get<IHttpResponseFeature>();
123+
var responseHeaders = responseInfo.Headers;
124+
responseHeaders["Custom-Header1"] = new string[] { "Dašta" };
125+
return Task.FromResult(0);
126+
}))
127+
{
128+
var socketsHttpHandler = new SocketsHttpHandler() { ResponseHeaderEncodingSelector = (_, _) => Encoding.UTF8 };
129+
var httpClient = new HttpClient(socketsHttpHandler);
130+
var response = await httpClient.GetAsync(address);
131+
response.EnsureSuccessStatusCode();
132+
Assert.True(response.Headers.TryGetValues("Custom-Header1", out var header));
133+
Assert.Equal("Dašta", header.Single());
134+
}
135+
}
136+
116137
[ConditionalFact]
117138
public async Task ResponseHeaders_ServerSendsConnectionClose_Closed()
118139
{

src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.IO.Pipelines;
99
using System.Threading.Tasks;
1010
using Microsoft.AspNetCore.Connections;
11+
using Microsoft.AspNetCore.Http;
1112
using Microsoft.AspNetCore.Http.Features;
1213
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1314

src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
using System.Runtime.InteropServices;
1111
using System.Text;
1212
using Microsoft.AspNetCore.Http;
13-
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1413
using Microsoft.Extensions.Primitives;
1514
using Microsoft.Net.Http.Headers;
1615

src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Diagnostics;
77
using System.Runtime.CompilerServices;
88
using System.Runtime.InteropServices;
9+
using Microsoft.AspNetCore.Http;
910
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1011

1112
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1717
using Microsoft.Extensions.Primitives;
1818
using Microsoft.Net.Http.Headers;
19+
using HttpCharacters = Microsoft.AspNetCore.Http.HttpCharacters;
1920
using HttpMethods = Microsoft.AspNetCore.Http.HttpMethods;
2021

2122
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Microsoft.Extensions.Logging;
1616
using Microsoft.Extensions.Primitives;
1717
using Microsoft.Net.Http.Headers;
18+
using HttpCharacters = Microsoft.AspNetCore.Http.HttpCharacters;
1819

1920
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
2021
{

src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.AspNetCore.Connections;
1212
using Microsoft.AspNetCore.Hosting.Server;
1313
using Microsoft.AspNetCore.Hosting.Server.Features;
14+
using Microsoft.AspNetCore.Http;
1415
using Microsoft.AspNetCore.Http.Features;
1516
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
1617
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;

src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,18 @@ public void AddingNonAsciiCharactersWithCustomEncoderWorks(string value)
241241
((IDictionary<string, StringValues>)responseHeaders).Add("Unknown", value);
242242
}
243243

244+
[Fact]
245+
public void AddingTabCharactersToHeaderPropertyWorks()
246+
{
247+
var responseHeaders = (IHeaderDictionary)new HttpResponseHeaders();
248+
249+
// Known special header
250+
responseHeaders.Allow = "Da\tta";
251+
252+
// Unknown header fallback
253+
responseHeaders.Accept = "Da\tta";
254+
}
255+
244256
[Fact]
245257
public void ThrowsWhenAddingHeaderAfterReadOnlyIsSet()
246258
{

src/Shared/HttpSys/RequestProcessing/HeaderCollection.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,12 +272,10 @@ public static void ValidateHeaderCharacters(string headerCharacters)
272272
{
273273
if (headerCharacters != null)
274274
{
275-
foreach (var ch in headerCharacters)
275+
var invalid = HttpCharacters.IndexOfInvalidFieldValueCharExtended(headerCharacters);
276+
if (invalid >= 0)
276277
{
277-
if (ch < 0x20)
278-
{
279-
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Invalid control character in header: 0x{0:X2}", (byte)ch));
280-
}
278+
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Invalid control character in header: 0x{0:X2}", headerCharacters[invalid]));
281279
}
282280
}
283281
}

0 commit comments

Comments
 (0)