diff --git a/src/Components/test/E2ETest/Tests/FormsTest.cs b/src/Components/test/E2ETest/Tests/FormsTest.cs
index 6db7988271..4bdfcb78b8 100644
--- a/src/Components/test/E2ETest/Tests/FormsTest.cs
+++ b/src/Components/test/E2ETest/Tests/FormsTest.cs
@@ -10,6 +10,7 @@ using BasicTestApp.FormsTest;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
+using Microsoft.AspNetCore.Testing;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
@@ -190,6 +191,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
}
[Fact]
+ [Flaky("https://github.com/dotnet/aspnetcore-internal/issues/3615", FlakyOn.Helix.All)]
public void InputDateInteractsWithEditContext_NonNullableDateTime()
{
var appElement = MountTypicalValidationComponent();
diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/Microsoft.AspNetCore.Components.WebAssembly.Templates.csproj b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/Microsoft.AspNetCore.Components.WebAssembly.Templates.csproj
new file mode 100644
index 0000000000..efde20de6d
--- /dev/null
+++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/Microsoft.AspNetCore.Components.WebAssembly.Templates.csproj
@@ -0,0 +1,49 @@
+
+
+ $(RepoRoot)src\Components\WebAssembly\
+
+
+
+ $(DefaultNetCoreTargetFramework)
+ true
+ Templates for ASP.NET Core Blazor WebAssembly projects.
+ $(PackageTags);blazor;spa
+ $(ComponentsWebAssemblyVersionPrefix)
+
+
+
+
+
+ DefaultNetCoreTargetFramework=$(DefaultNetCoreTargetFramework);
+ MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion);
+ MicrosoftEntityFrameworkCoreSqlitePackageVersion=$(MicrosoftEntityFrameworkCoreSqlitePackageVersion);
+ MicrosoftEntityFrameworkCoreToolsPackageVersion=$(MicrosoftEntityFrameworkCoreToolsPackageVersion);
+ MicrosoftExtensionsHttpPackageVersion=$(MicrosoftExtensionsHttpPackageVersion);
+ SystemNetHttpJsonPackageVersion=$(SystemNetHttpJsonPackageVersion)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj b/src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj
index 9877d26985..501aa01125 100644
--- a/src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj
+++ b/src/Servers/HttpSys/src/Microsoft.AspNetCore.Server.HttpSys.csproj
@@ -14,9 +14,7 @@
-
-
-
+
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
index 96c283c551..ed798ba12e 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
@@ -76,8 +76,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
IntPtr pInProcessHandler,
IISServerOptions options,
IISHttpServer server,
- ILogger logger)
- : base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler))
+ ILogger logger,
+ bool useLatin1)
+ : base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler), useLatin1: useLatin1)
{
_memoryPool = memoryPool;
_pInProcessHandler = pInProcessHandler;
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs
index ecd3a86e75..070e13c48f 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs
@@ -3,6 +3,7 @@
using System;
using System.Buffers;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
@@ -17,8 +18,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{
private readonly IHttpApplication _application;
- public IISHttpContextOfT(MemoryPool memoryPool, IHttpApplication application, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server, ILogger logger)
- : base(memoryPool, pInProcessHandler, options, server, logger)
+ public IISHttpContextOfT(MemoryPool memoryPool, IHttpApplication application, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server, ILogger logger, bool useLatin1)
+ : base(memoryPool, pInProcessHandler, options, server, logger, useLatin1)
{
_application = application;
}
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpServer.cs b/src/Servers/IIS/IIS/src/Core/IISHttpServer.cs
index ef4c3aabb8..7724f18300 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpServer.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpServer.cs
@@ -214,11 +214,14 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private class IISContextFactory : IISContextFactory
{
+ private const string Latin1Suppport = "Microsoft.AspNetCore.Server.IIS.Latin1RequestHeaders";
+
private readonly IHttpApplication _application;
private readonly MemoryPool _memoryPool;
private readonly IISServerOptions _options;
private readonly IISHttpServer _server;
private readonly ILogger _logger;
+ private readonly bool _useLatin1;
public IISContextFactory(MemoryPool memoryPool, IHttpApplication application, IISServerOptions options, IISHttpServer server, ILogger logger)
{
@@ -227,11 +230,12 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_options = options;
_server = server;
_logger = logger;
+ AppContext.TryGetSwitch(Latin1Suppport, out _useLatin1);
}
public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler)
{
- return new IISHttpContextOfT(_memoryPool, _application, pInProcessHandler, _options, _server, _logger);
+ return new IISHttpContextOfT(_memoryPool, _application, pInProcessHandler, _options, _server, _logger, _useLatin1);
}
}
}
diff --git a/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj b/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj
index 04fbc62328..d262f5cd0b 100644
--- a/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj
+++ b/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj
@@ -20,7 +20,8 @@
-
+
+
diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/Latin1Tests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/Latin1Tests.cs
new file mode 100644
index 0000000000..646b9c9bd9
--- /dev/null
+++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/Latin1Tests.cs
@@ -0,0 +1,82 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
+using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
+using Microsoft.AspNetCore.Testing;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess
+{
+ [Collection(PublishedSitesCollection.Name)]
+ public class Latin1Tests : IISFunctionalTestBase
+ {
+ public Latin1Tests(PublishedSitesFixture fixture) : base(fixture)
+ {
+ }
+
+ [ConditionalFact]
+ [RequiresNewHandler]
+ public async Task Latin1Works()
+ {
+ var deploymentParameters = Fixture.GetBaseDeploymentParameters();
+ deploymentParameters.TransformArguments((a, _) => $"{a} AddLatin1");
+
+ var deploymentResult = await DeployAsync(deploymentParameters);
+
+ var client = new HttpClient(new LoggingHandler(new WinHttpHandler() { SendTimeout = TimeSpan.FromMinutes(3) }, deploymentResult.Logger));
+
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, $"{deploymentResult.ApplicationBaseUri}Latin1");
+ requestMessage.Headers.Add("foo", "£");
+
+ var result = await client.SendAsync(requestMessage);
+ Assert.Equal(HttpStatusCode.OK, result.StatusCode);
+ }
+
+ [ConditionalFact]
+ [RequiresNewHandler]
+ public async Task Latin1ReplacedWithoutAppContextSwitch()
+ {
+ var deploymentParameters = Fixture.GetBaseDeploymentParameters();
+ deploymentParameters.TransformArguments((a, _) => $"{a}");
+
+ var deploymentResult = await DeployAsync(deploymentParameters);
+
+ var client = new HttpClient(new LoggingHandler(new WinHttpHandler() { SendTimeout = TimeSpan.FromMinutes(3) }, deploymentResult.Logger));
+
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, $"{deploymentResult.ApplicationBaseUri}InvalidCharacter");
+ requestMessage.Headers.Add("foo", "£");
+
+ var result = await client.SendAsync(requestMessage);
+ Assert.Equal(HttpStatusCode.OK, result.StatusCode);
+ }
+
+ [ConditionalFact]
+ [RequiresNewHandler]
+ public async Task Latin1InvalidCharacters_HttpSysRejects()
+ {
+ var deploymentParameters = Fixture.GetBaseDeploymentParameters();
+ deploymentParameters.TransformArguments((a, _) => $"{a} AddLatin1");
+
+ var deploymentResult = await DeployAsync(deploymentParameters);
+
+ using (var connection = new TestConnection(deploymentResult.HttpClient.BaseAddress.Port))
+ {
+ await connection.Send(
+ "GET /ReadAndFlushEcho HTTP/1.1",
+ "Host: localhost",
+ "Connection: close",
+ "foo: £\0a",
+ "",
+ "");
+
+ await connection.ReceiveStartsWith("HTTP/1.1 400 Bad Request");
+ }
+ }
+ }
+}
diff --git a/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj
index 5d689a6bee..0999c4903e 100644
--- a/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj
+++ b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj
@@ -1,4 +1,4 @@
-
+
$(DefaultNetCoreTargetFramework)
@@ -25,6 +25,7 @@
+
diff --git a/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj
index 530f6f8bbe..4a5234c6bb 100644
--- a/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj
+++ b/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj
@@ -1,4 +1,4 @@
-
+
$(DefaultNetCoreTargetFramework)
@@ -33,6 +33,7 @@
+
diff --git a/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj
index 07b99fa8d5..6ab82203b9 100644
--- a/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj
+++ b/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj
@@ -1,4 +1,4 @@
-
+
$(DefaultNetCoreTargetFramework)
@@ -27,6 +27,7 @@
+
diff --git a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj
index d874d0093e..f5a90fc3f3 100644
--- a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj
+++ b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj
@@ -1,4 +1,4 @@
-
+
@@ -29,6 +29,7 @@
+
diff --git a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Program.cs b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Program.cs
index 4b9889a141..0132efc2e7 100644
--- a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Program.cs
+++ b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Program.cs
@@ -145,9 +145,9 @@ namespace TestSite
}
return 0;
+#if !FORWARDCOMPAT
case "ThrowInStartupGenericHost":
{
-#if !FORWARDCOMPAT
var host = new HostBuilder().ConfigureWebHost((c) =>
{
c.ConfigureLogging((_, factory) =>
@@ -161,9 +161,26 @@ namespace TestSite
host.Build().Run();
-#endif
+ return 0;
}
- return 0;
+ case "AddLatin1":
+ {
+ AppContext.SetSwitch("Microsoft.AspNetCore.Server.IIS.Latin1RequestHeaders", isEnabled: true);
+ var host = new HostBuilder().ConfigureWebHost((c) =>
+ {
+ c.ConfigureLogging((_, factory) =>
+ {
+ factory.AddConsole();
+ factory.AddFilter("Console", level => level >= LogLevel.Information);
+ })
+ .UseIIS()
+ .UseStartup();
+ });
+
+ host.Build().Run();
+ return 0;
+ }
+#endif
default:
return StartServer();
diff --git a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs
index b2ee3c1456..03fc108b92 100644
--- a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs
+++ b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs
@@ -1016,5 +1016,19 @@ namespace TestSite
await context.Response.WriteAsync(httpsPort.HasValue ? httpsPort.Value.ToString() : "NOVALUE");
}
+
+ public Task Latin1(HttpContext context)
+ {
+ var value = context.Request.Headers["foo"];
+ Assert.Equal("£", value);
+ return Task.CompletedTask;
+ }
+
+ public Task InvalidCharacter(HttpContext context)
+ {
+ var value = context.Request.Headers["foo"];
+ Assert.Equal("�", value);
+ return Task.CompletedTask;
+ }
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs
index 38cd1961ff..39f38b4e21 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs
@@ -133,84 +133,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
}
}
- private static string GetAsciiOrUTF8StringNonNullCharacters(this Span span)
- => GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan)span);
-
- public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan span)
- {
- if (span.IsEmpty)
- {
- return string.Empty;
- }
-
- fixed (byte* source = &MemoryMarshal.GetReference(span))
- {
- var resultString = string.Create(span.Length, new IntPtr(source), s_getAsciiOrUtf8StringNonNullCharacters);
-
- // If resultString is marked, perform UTF-8 encoding
- if (resultString[0] == '\0')
- {
- // null characters are considered invalid
- if (span.IndexOf((byte)0) != -1)
- {
- throw new InvalidOperationException();
- }
-
- try
- {
- resultString = HeaderValueEncoding.GetString(span);
- }
- catch (DecoderFallbackException)
- {
- throw new InvalidOperationException();
- }
- }
-
- return resultString;
- }
- }
-
- private static readonly SpanAction s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiOrUTF8StringNonNullCharacters;
-
- private static unsafe void GetAsciiOrUTF8StringNonNullCharacters(Span buffer, IntPtr state)
- {
- fixed (char* output = &MemoryMarshal.GetReference(buffer))
- {
- // This version if AsciiUtilities returns null if there are any null (0 byte) characters
- // in the string
- if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
- {
- // Mark resultString for UTF-8 encoding
- output[0] = '\0';
- }
- }
- }
-
- private static unsafe string GetLatin1StringNonNullCharacters(this ReadOnlySpan span)
- {
- if (span.IsEmpty)
- {
- return string.Empty;
- }
-
- var resultString = new string('\0', span.Length);
-
- fixed (char* output = resultString)
- fixed (byte* buffer = span)
- {
- // This returns false if there are any null (0 byte) characters in the string.
- if (!StringUtilities.TryGetLatin1String(buffer, output, span.Length))
- {
- // null characters are considered invalid
- throw new InvalidOperationException();
- }
- }
-
- return resultString;
- }
-
- public static string GetRequestHeaderStringNonNullCharacters(this ReadOnlySpan span, bool useLatin1) =>
- useLatin1 ? GetLatin1StringNonNullCharacters(span) : GetAsciiOrUTF8StringNonNullCharacters(span);
+ public static string GetRequestHeaderStringNonNullCharacters(this Span span, bool useLatin1) =>
+ useLatin1 ? span.GetLatin1StringNonNullCharacters() : span.GetAsciiOrUTF8StringNonNullCharacters(HeaderValueEncoding);
public static string GetAsciiStringEscaped(this ReadOnlySpan span, int maxChars)
{
diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
index 73f77bb773..59c2a87e41 100644
--- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
+++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/Shared/HttpSys/RequestProcessing/HeaderEncoding.cs b/src/Shared/HttpSys/RequestProcessing/HeaderEncoding.cs
index a5294c5eb7..14eff1ebce 100644
--- a/src/Shared/HttpSys/RequestProcessing/HeaderEncoding.cs
+++ b/src/Shared/HttpSys/RequestProcessing/HeaderEncoding.cs
@@ -3,19 +3,24 @@
using System;
using System.Text;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static class HeaderEncoding
{
- // It should just be ASCII or ANSI, but they break badly with un-expected values. We use UTF-8 because it's the same for
- // ASCII, and because some old client would send UTF8 Host headers and expect UTF8 Location responses
- // (e.g. IE and HttpWebRequest on intranets).
- private static Encoding Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
+ private static readonly Encoding Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
- internal static unsafe string GetString(byte* pBytes, int byteCount)
+ internal static unsafe string GetString(byte* pBytes, int byteCount, bool useLatin1)
{
- return Encoding.GetString(new ReadOnlySpan(pBytes, byteCount));
+ if (useLatin1)
+ {
+ return new ReadOnlySpan(pBytes, byteCount).GetLatin1StringNonNullCharacters();
+ }
+ else
+ {
+ return new ReadOnlySpan(pBytes, byteCount).GetAsciiOrUTF8StringNonNullCharacters(Encoding);
+ }
}
internal static byte[] GetBytes(string myString)
diff --git a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs
index 88a078fbe2..b9a75b4ba9 100644
--- a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs
+++ b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs
@@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
private const int AlignmentPadding = 8;
private const int DefaultBufferSize = 4096 - AlignmentPadding;
private IntPtr _originalBufferAddress;
+ private bool _useLatin1;
private HttpApiTypes.HTTP_REQUEST* _nativeRequest;
private IMemoryOwner _backingBuffer;
private MemoryHandle _memoryHandle;
@@ -61,8 +62,9 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
}
// To be used by IIS Integration.
- internal NativeRequestContext(HttpApiTypes.HTTP_REQUEST* request)
+ internal NativeRequestContext(HttpApiTypes.HTTP_REQUEST* request, bool useLatin1)
{
+ _useLatin1 = useLatin1;
_nativeRequest = request;
_bufferAlignment = 0;
_permanentlyPinned = true;
@@ -155,7 +157,8 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
}
else if (verb == HttpApiTypes.HTTP_VERB.HttpVerbUnknown && NativeRequest->pUnknownVerb != null)
{
- return HeaderEncoding.GetString(NativeRequest->pUnknownVerb, NativeRequest->UnknownVerbLength);
+ // Never use Latin1 for the VERB
+ return HeaderEncoding.GetString(NativeRequest->pUnknownVerb, NativeRequest->UnknownVerbLength, useLatin1: false);
}
return null;
@@ -321,7 +324,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
// pRawValue will point to empty string ("\0")
if (pKnownHeader->RawValueLength > 0)
{
- value = HeaderEncoding.GetString(pKnownHeader->pRawValue + fixup, pKnownHeader->RawValueLength);
+ value = HeaderEncoding.GetString(pKnownHeader->pRawValue + fixup, pKnownHeader->RawValueLength, _useLatin1);
}
return value;
@@ -359,11 +362,11 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
// pRawValue will be null.
if (pUnknownHeader->pName != null && pUnknownHeader->NameLength > 0)
{
- var headerName = HeaderEncoding.GetString(pUnknownHeader->pName + fixup, pUnknownHeader->NameLength);
+ var headerName = HeaderEncoding.GetString(pUnknownHeader->pName + fixup, pUnknownHeader->NameLength, _useLatin1);
string headerValue;
if (pUnknownHeader->pRawValue != null && pUnknownHeader->RawValueLength > 0)
{
- headerValue = HeaderEncoding.GetString(pUnknownHeader->pRawValue + fixup, pUnknownHeader->RawValueLength);
+ headerValue = HeaderEncoding.GetString(pUnknownHeader->pRawValue + fixup, pUnknownHeader->RawValueLength, _useLatin1);
}
else
{
diff --git a/src/Shared/ServerInfrastructure/StringUtilities.cs b/src/Shared/ServerInfrastructure/StringUtilities.cs
index 5468ae3c9a..0d556f9fe4 100644
--- a/src/Shared/ServerInfrastructure/StringUtilities.cs
+++ b/src/Shared/ServerInfrastructure/StringUtilities.cs
@@ -15,6 +15,85 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
internal static class StringUtilities
{
+ private static string GetAsciiOrUTF8StringNonNullCharacters(this Span span, Encoding defaultEncoding)
+ => GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan)span);
+
+ public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan span, Encoding defaultEncoding)
+ {
+ if (span.IsEmpty)
+ {
+ return string.Empty;
+ }
+
+ fixed (byte* source = &MemoryMarshal.GetReference(span))
+ {
+ var resultString = string.Create(span.Length, new IntPtr(source), s_getAsciiOrUtf8StringNonNullCharacters);
+
+ // If resultString is marked, perform UTF-8 encoding
+ if (resultString[0] == '\0')
+ {
+ // null characters are considered invalid
+ if (span.IndexOf((byte)0) != -1)
+ {
+ throw new InvalidOperationException();
+ }
+
+ try
+ {
+ resultString = defaultEncoding.GetString(span);
+ }
+ catch (DecoderFallbackException)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ return resultString;
+ }
+
+ return resultString;
+ }
+
+
+ private static readonly SpanAction s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiOrUTF8StringNonNullCharacters;
+
+ private static unsafe void GetAsciiOrUTF8StringNonNullCharacters(Span buffer, IntPtr state)
+ {
+ fixed (char* output = &MemoryMarshal.GetReference(buffer))
+ {
+ // This version if AsciiUtilities returns null if there are any null (0 byte) characters
+ // in the string
+ if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
+ {
+ // Mark resultString for UTF-8 encoding
+ output[0] = '\0';
+ }
+ }
+ }
+
+ public static unsafe string GetLatin1StringNonNullCharacters(this Span span)
+ {
+ if (span.IsEmpty)
+ {
+ return string.Empty;
+ }
+
+ var resultString = new string('\0', span.Length);
+
+ fixed (char* output = resultString)
+ fixed (byte* buffer = span)
+ {
+ // This returns false if there are any null (0 byte) characters in the string.
+ if (!TryGetLatin1String(buffer, output, span.Length))
+ {
+ // null characters are considered invalid
+ throw new InvalidOperationException();
+ }
+ }
+
+ return resultString;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static unsafe bool TryGetAsciiString(byte* input, char* output, int count)
{
@@ -626,7 +705,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
// These must be explicity typed as ReadOnlySpan
// They then become a non-allocating mappings to the data section of the assembly.
- // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
+ // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
ReadOnlySpan shuffleMaskData = new byte[16]
{
0xF, 0xF, 3, 0xF,
diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj
index b80599128b..43f275a361 100644
--- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj
+++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj
@@ -25,6 +25,7 @@
+