Skip to content

Commit 46cf0e1

Browse files
committed
Add tests
1 parent f81da62 commit 46cf0e1

File tree

6 files changed

+149
-15
lines changed

6 files changed

+149
-15
lines changed

src/Servers/Kestrel/Core/src/KestrelServer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ private void ValidateOptions()
377377

378378
if (Options.RequestHeaderEncodingSelector is null)
379379
{
380-
throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.RequestHeaderEncodingSelector)} must not be null.");
380+
throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.RequestHeaderEncodingSelector)} must not be set to null.");
381381
}
382382
}
383383

src/Servers/Kestrel/Core/src/KestrelServerOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ public void ConfigureEndpointDefaults(Action<ListenOptions> configureOptions)
148148
return DefaultLatin1RequestHeaderEncodingSelector;
149149
}
150150

151-
return RequestHeaderEncodingSelector;
151+
return RequestHeaderEncodingSelector
152+
?? throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(RequestHeaderEncodingSelector)} must not be set to null.");
152153
}
153154

154155
internal void ApplyEndpointDefaults(ListenOptions listenOptions)

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

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Http;
99
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
1010
using Microsoft.Extensions.Primitives;
11+
using Microsoft.Net.Http.Headers;
1112
using Xunit;
1213
using static CodeGenerator.KnownHeaders;
1314

@@ -307,11 +308,10 @@ public void AppendThrowsWhenHeaderNameContainsNonASCIICharacters()
307308
var headers = new HttpRequestHeaders();
308309
const string key = "\u00141\u00F3d\017c";
309310

310-
var encoding = Encoding.GetEncoding("iso-8859-1");
311311
#pragma warning disable CS0618 // Type or member is obsolete
312312
var exception = Assert.Throws<BadHttpRequestException>(
313313
#pragma warning restore CS0618 // Type or member is obsolete
314-
() => headers.Append(encoding.GetBytes(key), Encoding.ASCII.GetBytes("value")));
314+
() => headers.Append(Encoding.Latin1.GetBytes(key), Encoding.ASCII.GetBytes("value")));
315315
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
316316
}
317317

@@ -473,7 +473,7 @@ public void ValueReuseLatin1NotConfusedForUtf16AndStillRejected(bool reuseValue,
473473
Assert.Throws<InvalidOperationException>(() =>
474474
{
475475
var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan();
476-
var nextSpan = Encoding.GetEncoding("iso-8859-1").GetBytes(headerValueUtf16Latin1CrossOver).AsSpan();
476+
var nextSpan = Encoding.Latin1.GetBytes(headerValueUtf16Latin1CrossOver).AsSpan();
477477

478478
Assert.False(nextSpan.SequenceEqual(Encoding.ASCII.GetBytes(headerValueUtf16Latin1CrossOver)));
479479

@@ -517,19 +517,24 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH
517517
headerValueUtf16Latin1CrossOver = new string(headerValue.AsSpan().Slice(0, i + 1));
518518
}
519519

520-
headers.Reset();
521-
522520
var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan();
523-
var latinValueSpan = Encoding.GetEncoding("iso-8859-1").GetBytes(headerValueUtf16Latin1CrossOver).AsSpan();
521+
var latinValueSpan = Encoding.Latin1.GetBytes(headerValueUtf16Latin1CrossOver).AsSpan();
524522

525523
Assert.False(latinValueSpan.SequenceEqual(Encoding.ASCII.GetBytes(headerValueUtf16Latin1CrossOver)));
526524

525+
headers.Reset();
526+
headers.Append(headerName, latinValueSpan);
527+
headers.OnHeadersComplete();
528+
var parsedHeaderValue1 = ((IHeaderDictionary)headers)[header.Name].ToString();
529+
530+
headers.Reset();
527531
headers.Append(headerName, latinValueSpan);
528532
headers.OnHeadersComplete();
529-
var parsedHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString();
533+
var parsedHeaderValue2 = ((IHeaderDictionary)headers)[header.Name].ToString();
530534

531-
Assert.Equal(headerValueUtf16Latin1CrossOver, parsedHeaderValue);
532-
Assert.NotSame(headerValueUtf16Latin1CrossOver, parsedHeaderValue);
535+
Assert.Equal(headerValueUtf16Latin1CrossOver, parsedHeaderValue1);
536+
Assert.Equal(parsedHeaderValue1, parsedHeaderValue2);
537+
Assert.NotSame(parsedHeaderValue1, parsedHeaderValue2);
533538
}
534539

535540
// Reset back to Ascii
@@ -541,11 +546,12 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH
541546
[MemberData(nameof(KnownRequestHeaders))]
542547
public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeader header)
543548
{
544-
var selector = useLatin1 ?
545-
KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector :
546-
KestrelServerOptions.DefaultRequestHeaderEncodingSelector;
549+
var kso = new KestrelServerOptions
550+
{
551+
Latin1RequestHeaders = useLatin1,
552+
};
547553

548-
var headers = new HttpRequestHeaders(encodingSelector: selector);
554+
var headers = new HttpRequestHeaders(encodingSelector: kso.GetRequestHeaderEncodingSelector());
549555

550556
var valueArray = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1
551557
for (var i = 0; i < valueArray.Length; i++)
@@ -573,6 +579,53 @@ public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeade
573579
}
574580
}
575581

582+
[Fact]
583+
public void CanSpecifyEncodingBasedOnHeaderName()
584+
{
585+
const string headerValue = "Hello \u03a0";
586+
var acceptNameBytes = Encoding.ASCII.GetBytes(HeaderNames.Accept);
587+
var cookieNameBytes = Encoding.ASCII.GetBytes(HeaderNames.Cookie);
588+
var headerValueBytes = Encoding.UTF8.GetBytes(headerValue);
589+
590+
var headers = new HttpRequestHeaders(encodingSelector: headerName =>
591+
{
592+
// For known headers, the HeaderNames value is passed in.
593+
if (ReferenceEquals(headerName, HeaderNames.Accept))
594+
{
595+
return Encoding.GetEncoding("ASCII", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
596+
}
597+
598+
return Encoding.UTF8;
599+
});
600+
601+
Assert.Throws<InvalidOperationException>(() => headers.Append(acceptNameBytes, headerValueBytes));
602+
headers.Append(cookieNameBytes, headerValueBytes);
603+
headers.OnHeadersComplete();
604+
605+
var parsedAcceptHeaderValue = ((IHeaderDictionary)headers)[HeaderNames.Accept].ToString();
606+
var parsedCookieHeaderValue = ((IHeaderDictionary)headers)[HeaderNames.Cookie].ToString();
607+
608+
Assert.Empty(parsedAcceptHeaderValue);
609+
Assert.Equal(headerValue, parsedCookieHeaderValue);
610+
}
611+
612+
[Fact]
613+
public void CanSpecifyEncodingForContentLength()
614+
{
615+
var contentLengthNameBytes = Encoding.ASCII.GetBytes(HeaderNames.ContentLength);
616+
// Always 32 bits per code point, so not a superset of ASCII
617+
var contentLengthValueBytes = Encoding.UTF32.GetBytes("1337");
618+
619+
var headers = new HttpRequestHeaders(encodingSelector: _ => Encoding.UTF32);
620+
headers.Append(contentLengthNameBytes, contentLengthValueBytes);
621+
headers.OnHeadersComplete();
622+
623+
Assert.Equal(1337, headers.ContentLength);
624+
625+
Assert.Throws<InvalidOperationException>(() =>
626+
new HttpRequestHeaders().Append(contentLengthNameBytes, contentLengthValueBytes));
627+
}
628+
576629
[Fact]
577630
public void ValueReuseNeverWhenUnknownHeader()
578631
{

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,22 @@ public void StartWithMultipleTransportFactoriesDoesNotThrow()
245245
StartDummyApplication(server);
246246
}
247247

248+
[Fact]
249+
public void StartWithNullRequestHeaderEncodingSelectorThrows()
250+
{
251+
var kso = CreateServerOptions();
252+
kso.RequestHeaderEncodingSelector = null;
253+
254+
var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false };
255+
256+
using var server = CreateServer(kso, testLogger);
257+
258+
var ex = Assert.Throws<InvalidOperationException>(() => StartDummyApplication(server));
259+
Assert.Contains(nameof(KestrelServerOptions.RequestHeaderEncodingSelector), ex.Message);
260+
261+
Assert.Equal(1, testLogger.CriticalErrorsLogged);
262+
}
263+
248264
[Fact]
249265
public async Task StopAsyncCallsCompleteWhenFirstCallCompletes()
250266
{

src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Security.Authentication;
99
using System.Security.Cryptography.X509Certificates;
10+
using System.Text;
1011
using Microsoft.AspNetCore.Hosting;
1112
using Microsoft.AspNetCore.Server.Kestrel.Core;
1213
using Microsoft.AspNetCore.Server.Kestrel.Https;
@@ -606,6 +607,33 @@ public void Latin1RequestHeadersReadFromConfig()
606607
Assert.False(options.Latin1RequestHeaders);
607608
options.Configure(config).Load();
608609
Assert.True(options.Latin1RequestHeaders);
610+
Assert.Same(KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector());
611+
}
612+
613+
[Fact]
614+
public void Latin1RequestHeadersReadFromConfigCanBeOverriddenBySettingRequestHeaderEncodingSelector()
615+
{
616+
var options = CreateServerOptions();
617+
var config = new ConfigurationBuilder().AddInMemoryCollection().Build();
618+
619+
Assert.False(options.Latin1RequestHeaders);
620+
options.Configure(config).Load();
621+
Assert.False(options.Latin1RequestHeaders);
622+
623+
options = CreateServerOptions();
624+
config = new ConfigurationBuilder().AddInMemoryCollection(new[]
625+
{
626+
new KeyValuePair<string, string>("Latin1RequestHeaders", "true"),
627+
}).Build();
628+
629+
Assert.False(options.Latin1RequestHeaders);
630+
options.Configure(config).Load();
631+
Assert.True(options.Latin1RequestHeaders);
632+
633+
Assert.NotSame(options.RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector());
634+
635+
options.RequestHeaderEncodingSelector = _ => Encoding.ASCII;
636+
Assert.Same(options.RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector());
609637
}
610638

611639
[Fact]

src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,6 +2059,42 @@ await connection.ReceiveEnd(
20592059
}
20602060
}
20612061

2062+
[Fact]
2063+
public async Task CustomRequestHeaderEncodingSelectorCanBeConfigured()
2064+
{
2065+
var testContext = new TestServiceContext(LoggerFactory);
2066+
2067+
testContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF32;
2068+
2069+
await using (var server = new TestServer(context =>
2070+
{
2071+
Assert.Equal("£", context.Request.Headers["X-Test"]);
2072+
return Task.CompletedTask;
2073+
}, testContext))
2074+
{
2075+
using (var connection = server.CreateConnection())
2076+
{
2077+
await connection.Send(
2078+
"GET / HTTP/1.1",
2079+
"Host:",
2080+
"X-Test: ");
2081+
2082+
await connection.Stream.WriteAsync(Encoding.UTF32.GetBytes("£")).DefaultTimeout();
2083+
2084+
await connection.Send("",
2085+
"",
2086+
"");
2087+
2088+
await connection.Receive(
2089+
"HTTP/1.1 200 OK",
2090+
$"Date: {testContext.DateHeaderValue}",
2091+
"Content-Length: 0",
2092+
"",
2093+
"");
2094+
}
2095+
}
2096+
}
2097+
20622098
public static TheoryData<string, string> HostHeaderData => HttpParsingData.HostHeaderData;
20632099

20642100
private class IntAsClass

0 commit comments

Comments
 (0)