Browse Source

Add Latin1 header support to HttpSys (#34568)

* Add Latin1 header support to HttpSys

* Remove unneeded using

* Update comment
William Godbe 4 years ago
parent
commit
e034ff60a8

+ 9 - 0
src/Servers/HttpSys/src/HttpSysOptions.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Globalization;
+using System.Text;
 using Microsoft.AspNetCore.Http.Features;
 
 namespace Microsoft.AspNetCore.Server.HttpSys
@@ -229,6 +230,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         /// </remarks>
         public bool UnsafePreferInlineScheduling { get; set; }
 
+        /// <summary>
+        /// Configures request headers to use <see cref="Encoding.Latin1"/> encoding.
+        /// </summary>
+        /// <remarks>
+        /// Defaults to `false`, in which case <see cref="Encoding.UTF8"/> will be used. />.
+        /// </remarks>
+        public bool UseLatin1RequestHeaders { get; set; }
+
         // Not called when attaching to an existing queue.
         internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
         {

+ 2 - 0
src/Servers/HttpSys/src/PublicAPI.Unshipped.txt

@@ -42,6 +42,8 @@ Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.Timeouts.get -> Microsoft.Asp
 Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.UnsafePreferInlineScheduling.get -> bool
 Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.UnsafePreferInlineScheduling.set -> void
 Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.UrlPrefixes.get -> Microsoft.AspNetCore.Server.HttpSys.UrlPrefixCollection!
+Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.UseLatin1RequestHeaders.get -> bool
+Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.UseLatin1RequestHeaders.set -> void
 Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestDelegationFeature.DelegateRequest(Microsoft.AspNetCore.Server.HttpSys.DelegationRule! destination) -> void
 Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestInfoFeature.RequestInfo.get -> System.Collections.Generic.IReadOnlyDictionary<int, System.ReadOnlyMemory<byte>>!
 Microsoft.AspNetCore.Server.HttpSys.IServerDelegationFeature.CreateDelegationRule(string! queueName, string! urlPrefix) -> Microsoft.AspNetCore.Server.HttpSys.DelegationRule!

+ 1 - 1
src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs

@@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         private bool _initialized;
 
         public RequestContext(HttpSysListener server, uint? bufferSize, ulong requestId)
-            : base(server.MemoryPool, bufferSize, requestId)
+            : base(server.MemoryPool, bufferSize, requestId, server.Options.UseLatin1RequestHeaders)
         {
             Server = server;
             AllowSynchronousIO = server.Options.AllowSynchronousIO;

+ 77 - 7
src/Servers/HttpSys/test/FunctionalTests/Listener/RequestHeaderTests.cs

@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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;
@@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
             using (var server = Utilities.CreateHttpServer(out address))
             {
                 string[] customValues = new string[] { "custom1, and custom测试2", "custom3" };
-                Task responseTask = SendRequestAsync(address, "Custom-Header", customValues);
+                Task responseTask = SendRequestAsync(address, "Custom-Header", customValues, Encoding.UTF8);
 
                 var context = await server.AcceptAsync(Utilities.DefaultTimeout);
                 var requestHeaders = context.Request.Headers;
@@ -43,6 +43,75 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
             }
         }
 
+        [ConditionalFact]
+        public async Task RequestHeaders_Latin1Replaced()
+        {
+            string address;
+            using (var server = Utilities.CreateHttpServer(out address))
+            {
+                string[] customValues = new string[] { "£" };
+                Task responseTask = SendRequestAsync(address, "Custom-Header", customValues, Encoding.Latin1);
+
+                var context = await server.AcceptAsync(Utilities.DefaultTimeout);
+                var requestHeaders = context.Request.Headers;
+                Assert.Equal(4, requestHeaders.Count);
+                Assert.Equal(new Uri(address).Authority, requestHeaders["Host"]);
+                Assert.Equal(new[] { new Uri(address).Authority }, requestHeaders.GetValues("Host"));
+                Assert.Equal("close", requestHeaders["Connection"]);
+                Assert.Equal(new[] { "close" }, requestHeaders.GetValues("Connection"));
+                // Apparently Http.Sys squashes request headers together.
+                Assert.Equal("�", requestHeaders["Custom-Header"]);
+                Assert.Equal(new[] { "�" }, requestHeaders.GetValues("Custom-Header"));
+                Assert.Equal("spacervalue", requestHeaders["Spacer-Header"]);
+                Assert.Equal(new[] { "spacervalue" }, requestHeaders.GetValues("Spacer-Header"));
+                context.Dispose();
+
+                await responseTask;
+            }
+        }
+
+        [ConditionalFact]
+        public async Task RequestHeaders_ClientSendsLatin1Headers_Success()
+        {
+            string address;
+            using (var server = Utilities.CreateHttpServer(out address))
+            {
+                server.Options.UseLatin1RequestHeaders = true;
+                string[] customValues = new string[] { "£" };
+                Task responseTask = SendRequestAsync(address, "Custom-Header", customValues, Encoding.Latin1);
+
+                var context = await server.AcceptAsync(Utilities.DefaultTimeout);
+                var requestHeaders = context.Request.Headers;
+                Assert.Equal(4, requestHeaders.Count);
+                Assert.Equal(new Uri(address).Authority, requestHeaders["Host"]);
+                Assert.Equal(new[] { new Uri(address).Authority }, requestHeaders.GetValues("Host"));
+                Assert.Equal("close", requestHeaders["Connection"]);
+                Assert.Equal(new[] { "close" }, requestHeaders.GetValues("Connection"));
+                // Apparently Http.Sys squashes request headers together.
+                Assert.Equal("£", requestHeaders["Custom-Header"]);
+                Assert.Equal(new[] { "£" }, requestHeaders.GetValues("Custom-Header"));
+                Assert.Equal("spacervalue", requestHeaders["Spacer-Header"]);
+                Assert.Equal(new[] { "spacervalue" }, requestHeaders.GetValues("Spacer-Header"));
+                context.Dispose();
+
+                await responseTask;
+            }
+        }
+
+        [ConditionalFact]
+        public async Task RequestHeaders_ClientSendsBadLatin1Headers_Rejected()
+        {
+            string address;
+            using (var server = Utilities.CreateHttpServer(out address))
+            {
+                server.Options.UseLatin1RequestHeaders = true;
+                string[] customValues = new string[] { "£\0a" };
+                var responseTask = SendRequestAsync(address, "Custom-Header", customValues, Encoding.Latin1);
+                var response = await responseTask;
+                Assert.StartsWith("400", response.Substring(9));
+            }
+        }
+
         [ConditionalFact]
         public async Task RequestHeaders_ClientSendsKnownHeaderWithNoValue_Success()
         {
@@ -50,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
             using (var server = Utilities.CreateHttpServer(out address))
             {
                 string[] customValues = new string[] { "" };
-                Task responseTask = SendRequestAsync(address, "If-None-Match", customValues);
+                Task responseTask = SendRequestAsync(address, "If-None-Match", customValues, Encoding.UTF8);
 
                 var context = await server.AcceptAsync(Utilities.DefaultTimeout);
                 var requestHeaders = context.Request.Headers;
@@ -75,7 +144,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
             using (var server = Utilities.CreateHttpServer(out address))
             {
                 string[] customValues = new string[] { "" };
-                Task responseTask = SendRequestAsync(address, "Custom-Header", customValues);
+                Task responseTask = SendRequestAsync(address, "Custom-Header", customValues, Encoding.UTF8);
 
                 var context = await server.AcceptAsync(Utilities.DefaultTimeout);
                 var requestHeaders = context.Request.Headers;
@@ -93,7 +162,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
             }
         }
 
-        private async Task SendRequestAsync(string address, string customHeader, string[] customValues)
+        private async Task<string> SendRequestAsync(string address, string customHeader, string[] customValues, Encoding encoding)
         {
             var uri = new Uri(address);
             StringBuilder builder = new StringBuilder();
@@ -110,7 +179,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
             }
             builder.AppendLine();
 
-            byte[] request = Encoding.UTF8.GetBytes(builder.ToString());
+            byte[] request = encoding.GetBytes(builder.ToString());
 
             Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
             socket.Connect(uri.Host, uri.Port);
@@ -120,6 +189,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
             byte[] response = new byte[1024 * 5];
             await Task.Run(() => socket.Receive(response));
             socket.Dispose();
+            return encoding.GetString(response);
         }
     }
-}
+}

+ 2 - 1
src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs

@@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
         private bool PermanentlyPinned => _permanentlyPinned;
 
         // To be used by HttpSys
-        internal NativeRequestContext(MemoryPool<byte> memoryPool, uint? bufferSize, ulong requestId)
+        internal NativeRequestContext(MemoryPool<byte> memoryPool, uint? bufferSize, ulong requestId, bool useLatin1)
         {
             // TODO:
             // Apparently the HttpReceiveHttpRequest memory alignment requirements for non - ARM processors
@@ -60,6 +60,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
             _nativeRequest = (HttpApiTypes.HTTP_REQUEST*)((long)_memoryHandle.Pointer + _bufferAlignment);
 
             RequestId = requestId;
+            _useLatin1 = useLatin1;
         }
 
         // To be used by IIS Integration.