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();
// 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();
}
@ -193,9 +193,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
// check and skip it
if (rawUrlInBytes.Length > 0 && rawUrlInBytes[rawUrlInBytes.Length - 1] == 0)
{
var newRawUrlInBytes = new byte[rawUrlInBytes.Length - 1];
Array.Copy(rawUrlInBytes, newRawUrlInBytes, newRawUrlInBytes.Length);
rawUrlInBytes = newRawUrlInBytes;
rawUrlInBytes = rawUrlInBytes.Slice(0, rawUrlInBytes.Length - 1);
}
var originalPath = RequestUriBuilder.DecodeAndUnescapePath(rawUrlInBytes);

View File

@ -138,17 +138,14 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
return null;
}
internal byte[] GetRawUrlInBytes()
internal Span<byte> GetRawUrlInBytes()
{
if (NativeRequest->pRawUrl != null && NativeRequest->RawUrlLength > 0)
{
var result = new byte[NativeRequest->RawUrlLength];
Marshal.Copy((IntPtr)NativeRequest->pRawUrl, result, 0, NativeRequest->RawUrlLength);
return result;
return new Span<byte>(NativeRequest->pRawUrl, NativeRequest->RawUrlLength);
}
return null;
return default;
}
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.
using System;
@ -8,12 +8,10 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
{
internal static class RawUrlHelper
{
private static readonly byte[] _forwardSlashPath = Encoding.ASCII.GetBytes("/");
/// <summary>
/// Find the segment of the URI byte array which represents the path.
/// </summary>
public static ArraySegment<byte> GetPath(byte[] raw)
public static Span<byte> GetPath(Span<byte> raw)
{
// performance
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 '/'
// 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.
pathStartIndex = Find(raw, authorityStartIndex, '/');
pathStartIndex = Find(raw, authorityStartIndex, (byte)'/');
if (pathStartIndex == -1)
{
// e.g. for request lines like: 'GET http://myserver' (no final '/')
// At this point we can return a path with a slash.
return new ArraySegment<byte>(_forwardSlashPath);
return default;
}
}
else
@ -67,7 +65,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
scan++;
}
return new ArraySegment<byte>(raw, pathStartIndex, scan - pathStartIndex);
return raw.Slice(pathStartIndex, scan - pathStartIndex);
}
/// <summary>
@ -75,7 +73,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// </summary>
/// <param name="raw">The byte array represents the raw URI</param>
/// <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)
{
@ -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;
}
return idx + begin;
}
return -1;
return idx;
}
}
}

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.
using System;
using System.Diagnostics;
using System.Text;
namespace Microsoft.AspNetCore.HttpSys.Internal
@ -17,23 +18,19 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true);
public static string DecodeAndUnescapePath(byte[] rawUrlBytes)
public static string DecodeAndUnescapePath(Span<byte> rawUrlBytes)
{
if (rawUrlBytes == null)
{
throw new ArgumentNullException(nameof(rawUrlBytes));
}
if (rawUrlBytes.Length == 0)
{
throw new ArgumentException("Length of the URL cannot be zero.", nameof(rawUrlBytes));
}
Debug.Assert(rawUrlBytes.Length == 0, "Length of the URL cannot be zero.");
var rawPath = RawUrlHelper.GetPath(rawUrlBytes);
if (rawPath.Length == 0)
{
return "/";
}
var unescapedPath = Unescape(rawPath);
return UTF8.GetString(unescapedPath.Array, unescapedPath.Offset, unescapedPath.Count);
return UTF8.GetString(unescapedPath);
}
/// <summary>
@ -41,19 +38,16 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// </summary>
/// <param name="rawPath">The raw path string to be unescaped</param>
/// <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
var reader = rawPath.Offset;
var reader = 0;
// the slot to write the unescaped byte
var writer = rawPath.Offset;
var writer = 0;
// the end of the path
var end = rawPath.Offset + rawPath.Count;
// the byte array
var buffer = rawPath.Array;
var end = rawPath.Length;
while (true)
{
@ -62,7 +56,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
break;
}
if (rawPath.Array[reader] == '%')
if (rawPath[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
// be scanned after the process. A failed decoding means the chars
// 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;
}
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>
@ -96,7 +90,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// <param name="writer">The place to write to</param>
/// <param name="end">The end of the buffer</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,
// 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;
}
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)
{
@ -260,7 +254,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// <param name="end">The end of the buffer</param>
/// <param name="buffer">The byte array</param>
/// <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++] != '%')
{
@ -300,7 +294,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
/// <param name="end">The end of the buffer</param>
/// <param name="buffer">The byte array</param>
/// <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)
{