#123 Default headers to UTF8

This commit is contained in:
Chris R 2016-01-08 12:05:36 -08:00
parent d46de60542
commit 0f01399955
4 changed files with 52 additions and 92 deletions

View File

@ -21,94 +21,34 @@
// </copyright>
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Net.Http.Server
{
// we use this static class as a helper class to encode/decode HTTP headers.
// what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF
// and a byte in the range 0x00-0xFF (which is the range that can hit the network).
// The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow.
// It doesn't work for string -> byte[] because of best-fit-mapping problems.
internal static class HeaderEncoding
{
internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount)
{
fixed (byte* pBytes = bytes)
return GetString(pBytes + byteIndex, byteCount);
}
// 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);
internal static unsafe string GetString(sbyte* pBytes, int byteCount)
{
return GetString((byte*)pBytes, byteCount);
// net451: return new string(pBytes, 0, byteCount, Encoding);
var charCount = Encoding.GetCharCount((byte*)pBytes, byteCount);
var chars = new char[charCount];
fixed (char* pChars = chars)
{
var count = Encoding.GetChars((byte*)pBytes, byteCount, pChars, charCount);
System.Diagnostics.Debug.Assert(count == charCount);
}
return new string(chars);
}
internal static unsafe string GetString(byte* pBytes, int byteCount)
internal static byte[] GetBytes(string myString)
{
if (byteCount < 1)
{
return string.Empty;
}
string s = new String('\0', byteCount);
fixed (char* pStr = s)
{
char* pString = pStr;
while (byteCount >= 8)
{
pString[0] = (char)pBytes[0];
pString[1] = (char)pBytes[1];
pString[2] = (char)pBytes[2];
pString[3] = (char)pBytes[3];
pString[4] = (char)pBytes[4];
pString[5] = (char)pBytes[5];
pString[6] = (char)pBytes[6];
pString[7] = (char)pBytes[7];
pString += 8;
pBytes += 8;
byteCount -= 8;
}
for (int i = 0; i < byteCount; i++)
{
pString[i] = (char)pBytes[i];
}
}
return s;
}
internal static int GetByteCount(string myString)
{
return myString.Length;
}
internal static unsafe void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex)
{
if (myString.Length == 0)
{
return;
}
fixed (byte* bufferPointer = bytes)
{
byte* newBufferPointer = bufferPointer + byteIndex;
int finalIndex = charIndex + charCount;
while (charIndex < finalIndex)
{
*newBufferPointer++ = (byte)myString[charIndex++];
}
}
}
internal static unsafe byte[] GetBytes(string myString)
{
byte[] bytes = new byte[myString.Length];
if (myString.Length != 0)
{
GetBytes(myString, 0, myString.Length, bytes, 0);
}
return bytes;
return Encoding.GetBytes(myString);
}
}
}

View File

@ -401,11 +401,10 @@ namespace Microsoft.Net.Http.Server
cachePolicy.SecondsToLive = (uint)Math.Min(cacheTtl.Value.Ticks / TimeSpan.TicksPerSecond, Int32.MaxValue);
}
byte[] reasonPhraseBytes = new byte[HeaderEncoding.GetByteCount(reasonPhrase)];
byte[] reasonPhraseBytes = HeaderEncoding.GetBytes(reasonPhrase);
fixed (byte* pReasonPhrase = reasonPhraseBytes)
{
_nativeResponse.Response_V1.ReasonLength = (ushort)reasonPhraseBytes.Length;
HeaderEncoding.GetBytes(reasonPhrase, 0, reasonPhraseBytes.Length, reasonPhraseBytes, 0);
_nativeResponse.Response_V1.pReason = (sbyte*)pReasonPhrase;
fixed (HttpApi.HTTP_RESPONSE_V2* pResponse = &_nativeResponse)
{
@ -622,18 +621,16 @@ namespace Microsoft.Net.Http.Server
for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++)
{
// Add Name
bytes = new byte[HeaderEncoding.GetByteCount(headerName)];
bytes = HeaderEncoding.GetBytes(headerName);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].NameLength = (ushort)bytes.Length;
HeaderEncoding.GetBytes(headerName, 0, bytes.Length, bytes, 0);
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].pName = (sbyte*)gcHandle.AddrOfPinnedObject();
// Add Value
headerValue = headerValues[headerValueIndex] ?? string.Empty;
bytes = new byte[HeaderEncoding.GetByteCount(headerValue)];
bytes = HeaderEncoding.GetBytes(headerValue);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].RawValueLength = (ushort)bytes.Length;
HeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0);
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].pRawValue = (sbyte*)gcHandle.AddrOfPinnedObject();
@ -643,9 +640,8 @@ namespace Microsoft.Net.Http.Server
else if (headerPair.Value.Count == 1)
{
headerValue = headerValues[0] ?? string.Empty;
bytes = new byte[HeaderEncoding.GetByteCount(headerValue)];
bytes = HeaderEncoding.GetBytes(headerValue);
pKnownHeaders[lookup].RawValueLength = (ushort)bytes.Length;
HeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0);
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
pKnownHeaders[lookup].pRawValue = (sbyte*)gcHandle.AddrOfPinnedObject();
@ -677,9 +673,8 @@ namespace Microsoft.Net.Http.Server
{
// Add Value
headerValue = headerValues[headerValueIndex] ?? string.Empty;
bytes = new byte[HeaderEncoding.GetByteCount(headerValue)];
bytes = HeaderEncoding.GetBytes(headerValue);
nativeHeaderValues[header.KnownHeaderCount].RawValueLength = (ushort)bytes.Length;
HeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0);
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
nativeHeaderValues[header.KnownHeaderCount].pRawValue = (sbyte*)gcHandle.AddrOfPinnedObject();

View File

@ -774,9 +774,8 @@ namespace Microsoft.Net.Http.Server
{
// Add Value
string headerValue = authChallenges[headerValueIndex];
byte[] bytes = new byte[HeaderEncoding.GetByteCount(headerValue)];
byte[] bytes = HeaderEncoding.GetBytes(headerValue);
nativeHeaderValues[header.KnownHeaderCount].RawValueLength = (ushort)bytes.Length;
HeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0);
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
nativeHeaderValues[header.KnownHeaderCount].pRawValue = (sbyte*)gcHandle.AddrOfPinnedObject();

View File

@ -1,7 +1,6 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
@ -64,7 +63,34 @@ namespace Microsoft.Net.Http.Server
await responseTask;
}
}
[Fact]
public async Task RequestHeaders_ClientSendsUtf8Headers_Success()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
string[] customValues = new string[] { "custom1, and custom测试2", "custom3" };
Task responseTask = SendRequestAsync(address, "Custom-Header", customValues);
var context = await server.GetContextAsync();
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("custom1, and custom测试2, custom3", requestHeaders["Custom-Header"]);
Assert.Equal(new[] { "custom1", "and custom测试2", "custom3" }, requestHeaders.GetValues("Custom-Header"));
Assert.Equal("spacervalue, spacervalue", requestHeaders["Spacer-Header"]);
Assert.Equal(new[] { "spacervalue", "spacervalue" }, requestHeaders.GetValues("Spacer-Header"));
context.Dispose();
await responseTask;
}
}
private async Task<string> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
@ -90,7 +116,7 @@ namespace Microsoft.Net.Http.Server
}
builder.AppendLine();
byte[] request = Encoding.ASCII.GetBytes(builder.ToString());
byte[] request = Encoding.UTF8.GetBytes(builder.ToString());
Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect(uri.Host, uri.Port);