Spanify path -> string logic (#9628)

- Use spans instead of byte[] and array segments when turning the URL into a string
This commit is contained in:
David Fowler 2019-04-22 15:53:56 -07:00 committed by GitHub
parent c745585d4c
commit 5f3bdba584
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 39 additions and 55 deletions

View File

@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
var rawUrlInBytes = GetRawUrlInBytes(); var rawUrlInBytes = GetRawUrlInBytes();
// Pre Windows 10 RS2 applicationInitialization request might not have pRawUrl set, fallback to cocked url // Pre Windows 10 RS2 applicationInitialization request might not have pRawUrl set, fallback to cocked url
if (rawUrlInBytes == null) if (rawUrlInBytes.Length == 0)
{ {
return GetCookedUrl().GetAbsPath(); return GetCookedUrl().GetAbsPath();
} }
@ -193,9 +193,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
// check and skip it // check and skip it
if (rawUrlInBytes.Length > 0 && rawUrlInBytes[rawUrlInBytes.Length - 1] == 0) if (rawUrlInBytes.Length > 0 && rawUrlInBytes[rawUrlInBytes.Length - 1] == 0)
{ {
var newRawUrlInBytes = new byte[rawUrlInBytes.Length - 1]; rawUrlInBytes = rawUrlInBytes.Slice(0, rawUrlInBytes.Length - 1);
Array.Copy(rawUrlInBytes, newRawUrlInBytes, newRawUrlInBytes.Length);
rawUrlInBytes = newRawUrlInBytes;
} }
var originalPath = RequestUriBuilder.DecodeAndUnescapePath(rawUrlInBytes); var originalPath = RequestUriBuilder.DecodeAndUnescapePath(rawUrlInBytes);

View File

@ -138,17 +138,14 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
return null; return null;
} }
internal byte[] GetRawUrlInBytes() internal Span<byte> GetRawUrlInBytes()
{ {
if (NativeRequest->pRawUrl != null && NativeRequest->RawUrlLength > 0) if (NativeRequest->pRawUrl != null && NativeRequest->RawUrlLength > 0)
{ {
var result = new byte[NativeRequest->RawUrlLength]; return new Span<byte>(NativeRequest->pRawUrl, NativeRequest->RawUrlLength);
Marshal.Copy((IntPtr)NativeRequest->pRawUrl, result, 0, NativeRequest->RawUrlLength);
return result;
} }
return null; return default;
} }
internal CookedUrl GetCookedUrl() internal CookedUrl GetCookedUrl()

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -8,12 +8,10 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
{ {
internal static class RawUrlHelper internal static class RawUrlHelper
{ {
private static readonly byte[] _forwardSlashPath = Encoding.ASCII.GetBytes("/");
/// <summary> /// <summary>
/// Find the segment of the URI byte array which represents the path. /// Find the segment of the URI byte array which represents the path.
/// </summary> /// </summary>
public static ArraySegment<byte> GetPath(byte[] raw) public static Span<byte> GetPath(Span<byte> raw)
{ {
// performance // performance
var pathStartIndex = 0; var pathStartIndex = 0;
@ -38,12 +36,12 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
// and http.sys behavior: If the Uri contains a query, there must be at least one '/' // and http.sys behavior: If the Uri contains a query, there must be at least one '/'
// between the authority and the '?' character: It's safe to just look for the first // between the authority and the '?' character: It's safe to just look for the first
// '/' after the authority to determine the beginning of the path. // '/' after the authority to determine the beginning of the path.
pathStartIndex = Find(raw, authorityStartIndex, '/'); pathStartIndex = Find(raw, authorityStartIndex, (byte)'/');
if (pathStartIndex == -1) if (pathStartIndex == -1)
{ {
// e.g. for request lines like: 'GET http://myserver' (no final '/') // e.g. for request lines like: 'GET http://myserver' (no final '/')
// At this point we can return a path with a slash. // At this point we can return a path with a slash.
return new ArraySegment<byte>(_forwardSlashPath); return default;
} }
} }
else else
@ -67,7 +65,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
scan++; scan++;
} }
return new ArraySegment<byte>(raw, pathStartIndex, scan - pathStartIndex); return raw.Slice(pathStartIndex, scan - pathStartIndex);
} }
/// <summary> /// <summary>
@ -75,7 +73,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// </summary> /// </summary>
/// <param name="raw">The byte array represents the raw URI</param> /// <param name="raw">The byte array represents the raw URI</param>
/// <returns>Length of the matched bytes, 0 if it is not matched.</returns> /// <returns>Length of the matched bytes, 0 if it is not matched.</returns>
private static int FindHttpOrHttps(byte[] raw) private static int FindHttpOrHttps(Span<byte> raw)
{ {
if (raw.Length < 7) if (raw.Length < 7)
{ {
@ -135,17 +133,14 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
} }
} }
private static int Find(byte[] raw, int begin, char target) private static int Find(Span<byte> raw, int begin, byte target)
{ {
for (var idx = begin; idx < raw.Length; ++idx) var idx = raw.Slice(begin).IndexOf(target);
if (idx != -1)
{ {
if (raw[idx] == target) return idx + begin;
{
return idx;
}
} }
return idx;
return -1;
} }
} }
} }

View File

@ -1,7 +1,8 @@
// 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.Text; using System.Text;
namespace Microsoft.AspNetCore.HttpSys.Internal namespace Microsoft.AspNetCore.HttpSys.Internal
@ -17,23 +18,19 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
encoderShouldEmitUTF8Identifier: false, encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true); throwOnInvalidBytes: true);
public static string DecodeAndUnescapePath(byte[] rawUrlBytes) public static string DecodeAndUnescapePath(Span<byte> rawUrlBytes)
{ {
if (rawUrlBytes == null) Debug.Assert(rawUrlBytes.Length == 0, "Length of the URL cannot be zero.");
{
throw new ArgumentNullException(nameof(rawUrlBytes));
}
if (rawUrlBytes.Length == 0)
{
throw new ArgumentException("Length of the URL cannot be zero.", nameof(rawUrlBytes));
}
var rawPath = RawUrlHelper.GetPath(rawUrlBytes); var rawPath = RawUrlHelper.GetPath(rawUrlBytes);
if (rawPath.Length == 0)
{
return "/";
}
var unescapedPath = Unescape(rawPath); var unescapedPath = Unescape(rawPath);
return UTF8.GetString(unescapedPath.Array, unescapedPath.Offset, unescapedPath.Count); return UTF8.GetString(unescapedPath);
} }
/// <summary> /// <summary>
@ -41,19 +38,16 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// </summary> /// </summary>
/// <param name="rawPath">The raw path string to be unescaped</param> /// <param name="rawPath">The raw path string to be unescaped</param>
/// <returns>The unescaped path string</returns> /// <returns>The unescaped path string</returns>
private static ArraySegment<byte> Unescape(ArraySegment<byte> rawPath) private static ReadOnlySpan<byte> Unescape(Span<byte> rawPath)
{ {
// the slot to read the input // the slot to read the input
var reader = rawPath.Offset; var reader = 0;
// the slot to write the unescaped byte // the slot to write the unescaped byte
var writer = rawPath.Offset; var writer = 0;
// the end of the path // the end of the path
var end = rawPath.Offset + rawPath.Count; var end = rawPath.Length;
// the byte array
var buffer = rawPath.Array;
while (true) while (true)
{ {
@ -62,7 +56,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
break; break;
} }
if (rawPath.Array[reader] == '%') if (rawPath[reader] == '%')
{ {
var decodeReader = reader; var decodeReader = reader;
@ -73,20 +67,20 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
// The decodeReader iterator is always moved to the first byte not yet // The decodeReader iterator is always moved to the first byte not yet
// be scanned after the process. A failed decoding means the chars // be scanned after the process. A failed decoding means the chars
// between the reader and decodeReader can be copied to output untouched. // between the reader and decodeReader can be copied to output untouched.
if (!DecodeCore(ref decodeReader, ref writer, end, buffer)) if (!DecodeCore(ref decodeReader, ref writer, end, rawPath))
{ {
Copy(reader, decodeReader, ref writer, buffer); Copy(reader, decodeReader, ref writer, rawPath);
} }
reader = decodeReader; reader = decodeReader;
} }
else else
{ {
buffer[writer++] = buffer[reader++]; rawPath[writer++] = rawPath[reader++];
} }
} }
return new ArraySegment<byte>(buffer, rawPath.Offset, writer - rawPath.Offset); return rawPath.Slice(0, writer);
} }
/// <summary> /// <summary>
@ -96,7 +90,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// <param name="writer">The place to write to</param> /// <param name="writer">The place to write to</param>
/// <param name="end">The end of the buffer</param> /// <param name="end">The end of the buffer</param>
/// <param name="buffer">The byte array</param> /// <param name="buffer">The byte array</param>
private static bool DecodeCore(ref int reader, ref int writer, int end, byte[] buffer) private static bool DecodeCore(ref int reader, ref int writer, int end, Span<byte> buffer)
{ {
// preserves the original head. if the percent-encodings cannot be interpreted as sequence of UTF-8 octets, // preserves the original head. if the percent-encodings cannot be interpreted as sequence of UTF-8 octets,
// bytes from this till the last scanned one will be copied to the memory pointed by writer. // bytes from this till the last scanned one will be copied to the memory pointed by writer.
@ -232,7 +226,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
return true; return true;
} }
private static void Copy(int begin, int end, ref int writer, byte[] buffer) private static void Copy(int begin, int end, ref int writer, Span<byte> buffer)
{ {
while (begin != end) while (begin != end)
{ {
@ -260,7 +254,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// <param name="end">The end of the buffer</param> /// <param name="end">The end of the buffer</param>
/// <param name="buffer">The byte array</param> /// <param name="buffer">The byte array</param>
/// <returns>The unescaped byte if success. Otherwise return -1.</returns> /// <returns>The unescaped byte if success. Otherwise return -1.</returns>
private static int? UnescapePercentEncoding(ref int scan, int end, byte[] buffer) private static int? UnescapePercentEncoding(ref int scan, int end, ReadOnlySpan<byte> buffer)
{ {
if (buffer[scan++] != '%') if (buffer[scan++] != '%')
{ {
@ -300,7 +294,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// <param name="end">The end of the buffer</param> /// <param name="end">The end of the buffer</param>
/// <param name="buffer">The byte array</param> /// <param name="buffer">The byte array</param>
/// <returns>The hexadecimal value if successes, otherwise -1.</returns> /// <returns>The hexadecimal value if successes, otherwise -1.</returns>
private static int? ReadHex(ref int scan, int end, byte[] buffer) private static int? ReadHex(ref int scan, int end, ReadOnlySpan<byte> buffer)
{ {
if (scan == end) if (scan == end)
{ {