diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/UrlPathDecoder.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/UrlPathDecoder.cs deleted file mode 100644 index 67004cf24f..0000000000 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/UrlPathDecoder.cs +++ /dev/null @@ -1,312 +0,0 @@ -// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; - -namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http -{ - public class UrlPathDecoder - { - /// - /// Unescapes the string between given memory iterators in place. - /// - /// The iterator points to the beginning of the sequence. - /// The iterator points to the byte behind the end of the sequence. - /// The iterator points to the byte behind the end of the processed sequence. - public static MemoryPoolIterator Unescape(MemoryPoolIterator start, MemoryPoolIterator end) - { - // the slot to read the input - var reader = start; - - // the slot to write the unescaped byte - var writer = reader; - - while (true) - { - if (CompareIterators(ref reader, ref end)) - { - return writer; - } - - if (reader.Peek() == '%') - { - var decodeReader = reader; - - // If decoding process succeeds, the writer iterator will be moved - // to the next write-ready location. On the other hand if the scanned - // percent-encodings cannot be interpreted as sequence of UTF-8 octets, - // these bytes should be copied to output as is. - // 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)) - { - Copy(reader, decodeReader, ref writer); - } - - reader = decodeReader; - } - else - { - writer.Put((byte)reader.Take()); - } - } - } - - /// - /// Unescape the percent-encodings - /// - /// The iterator point to the first % char - /// The place to write to - /// The end of the sequence - private static bool DecodeCore(ref MemoryPoolIterator reader, ref MemoryPoolIterator writer, MemoryPoolIterator end) - { - // 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. - var byte1 = UnescapePercentEncoding(ref reader, end); - - if (byte1 == 0) - { - throw BadHttpRequestException.GetException(RequestRejectionReason.PathContainsNullCharacters); - } - - if (byte1 == -1) - { - return false; - } - - if (byte1 <= 0x7F) - { - // first byte < U+007f, it is a single byte ASCII - writer.Put((byte)byte1); - return true; - } - - int byte2 = 0, byte3 = 0, byte4 = 0; - - // anticipate more bytes - var currentDecodeBits = 0; - var byteCount = 1; - var expectValueMin = 0; - if ((byte1 & 0xE0) == 0xC0) - { - // 110x xxxx, expect one more byte - currentDecodeBits = byte1 & 0x1F; - byteCount = 2; - expectValueMin = 0x80; - } - else if ((byte1 & 0xF0) == 0xE0) - { - // 1110 xxxx, expect two more bytes - currentDecodeBits = byte1 & 0x0F; - byteCount = 3; - expectValueMin = 0x800; - } - else if ((byte1 & 0xF8) == 0xF0) - { - // 1111 0xxx, expect three more bytes - currentDecodeBits = byte1 & 0x07; - byteCount = 4; - expectValueMin = 0x10000; - } - else - { - // invalid first byte - return false; - } - - var remainingBytes = byteCount - 1; - while (remainingBytes > 0) - { - // read following three chars - if (CompareIterators(ref reader, ref end)) - { - return false; - } - - var nextItr = reader; - var nextByte = UnescapePercentEncoding(ref nextItr, end); - if (nextByte == -1) - { - return false; - } - - if ((nextByte & 0xC0) != 0x80) - { - // the follow up byte is not in form of 10xx xxxx - return false; - } - - currentDecodeBits = (currentDecodeBits << 6) | (nextByte & 0x3F); - remainingBytes--; - - if (remainingBytes == 1 && currentDecodeBits >= 0x360 && currentDecodeBits <= 0x37F) - { - // this is going to end up in the range of 0xD800-0xDFFF UTF-16 surrogates that - // are not allowed in UTF-8; - return false; - } - - if (remainingBytes == 2 && currentDecodeBits >= 0x110) - { - // this is going to be out of the upper Unicode bound 0x10FFFF. - return false; - } - - reader = nextItr; - if (byteCount - remainingBytes == 2) - { - byte2 = nextByte; - } - else if (byteCount - remainingBytes == 3) - { - byte3 = nextByte; - } - else if (byteCount - remainingBytes == 4) - { - byte4 = nextByte; - } - } - - if (currentDecodeBits < expectValueMin) - { - // overlong encoding (e.g. using 2 bytes to encode something that only needed 1). - return false; - } - - // all bytes are verified, write to the output - if (byteCount > 0) - { - writer.Put((byte)byte1); - } - if (byteCount > 1) - { - writer.Put((byte)byte2); - } - if (byteCount > 2) - { - writer.Put((byte)byte3); - } - if (byteCount > 3) - { - writer.Put((byte)byte4); - } - - return true; - } - - private static void Copy(MemoryPoolIterator head, MemoryPoolIterator tail, ref MemoryPoolIterator writer) - { - while (!CompareIterators(ref head, ref tail)) - { - writer.Put((byte)head.Take()); - } - } - - /// - /// Read the percent-encoding and try unescape it. - /// - /// The operation first peek at the character the - /// iterator points at. If it is % the is then - /// moved on to scan the following to characters. If the two following - /// characters are hexadecimal literals they will be unescaped and the - /// value will be returned. - /// - /// If the first character is not % the iterator - /// will be removed beyond the location of % and -1 will be returned. - /// - /// If the following two characters can't be successfully unescaped the - /// iterator will be move behind the % and -1 - /// will be returned. - /// - /// The value to read - /// The end of the sequence - /// The unescaped byte if success. Otherwise return -1. - private static int UnescapePercentEncoding(ref MemoryPoolIterator scan, MemoryPoolIterator end) - { - if (scan.Take() != '%') - { - return -1; - } - - var probe = scan; - - int value1 = ReadHex(ref probe, end); - if (value1 == -1) - { - return -1; - } - - int value2 = ReadHex(ref probe, end); - if (value2 == -1) - { - return -1; - } - - if (SkipUnescape(value1, value2)) - { - return -1; - } - - scan = probe; - return (value1 << 4) + value2; - } - - /// - /// Read the next char and convert it into hexadecimal value. - /// - /// The iterator will be moved to the next - /// byte no matter no matter whether the operation successes. - /// - /// The value to read - /// The end of the sequence - /// The hexadecimal value if successes, otherwise -1. - private static int ReadHex(ref MemoryPoolIterator scan, MemoryPoolIterator end) - { - if (CompareIterators(ref scan, ref end)) - { - return -1; - } - - var value = scan.Take(); - var isHead = (((value >= '0') && (value <= '9')) || - ((value >= 'A') && (value <= 'F')) || - ((value >= 'a') && (value <= 'f'))); - - if (!isHead) - { - return -1; - } - - if (value <= '9') - { - return value - '0'; - } - else if (value <= 'F') - { - return (value - 'A') + 10; - } - else // a - f - { - return (value - 'a') + 10; - } - } - - private static bool SkipUnescape(int value1, int value2) - { - // skip %2F - if (value1 == 2 && value2 == 15) - { - return true; - } - - return false; - } - - private static bool CompareIterators(ref MemoryPoolIterator lhs, ref MemoryPoolIterator rhs) - { - // uses ref parameter to save cost of copying - return (lhs.Block == rhs.Block) && (lhs.Index == rhs.Index); - } - } -} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs index 07491b7377..0f4d3cebe5 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs @@ -14,15 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure public struct MemoryPoolIterator { private const int _maxULongByteLength = 20; - private const ulong _xorPowerOfTwoToHighByte = (0x07ul | - 0x06ul << 8 | - 0x05ul << 16 | - 0x04ul << 24 | - 0x03ul << 32 | - 0x02ul << 40 | - 0x01ul << 48 ) + 1; - - private static readonly int _vectorSpan = Vector.Count; [ThreadStatic] private static byte[] _numericBytesScratch; @@ -135,668 +126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure } } while (true); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Skip(int bytesToSkip) - { - var block = _block; - if (block == null && bytesToSkip > 0) - { - ThrowInvalidOperationException_SkipMoreThanAvailable(); - } - - // Always set wasLastBlock before checking .End to avoid race which may cause data loss - var wasLastBlock = block.Next == null; - var following = block.End - _index; - - if (following >= bytesToSkip) - { - _index += bytesToSkip; - return; - } - - if (wasLastBlock) - { - ThrowInvalidOperationException_SkipMoreThanAvailable(); - } - - SkipMultiBlock(bytesToSkip, following); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void SkipMultiBlock(int bytesToSkip, int following) - { - var block = _block; - do - { - bytesToSkip -= following; - block = block.Next; - var index = block.Start; - - // Always set wasLastBlock before checking .End to avoid race which may cause data loss - var wasLastBlock = block.Next == null; - following = block.End - index; - - if (following >= bytesToSkip) - { - _block = block; - _index = index + bytesToSkip; - return; - } - - if (wasLastBlock) - { - ThrowInvalidOperationException_SkipMoreThanAvailable(); - } - } while (true); - } - - private static void ThrowInvalidOperationException_SkipMoreThanAvailable() - { - throw new InvalidOperationException("Attempted to skip more bytes than available."); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Peek() - { - var block = _block; - if (block == null) - { - return -1; - } - - var index = _index; - - // Always set wasLastBlock before checking .End to avoid race which may cause data loss - var wasLastBlock = block.Next == null; - if (index < block.End) - { - return block.Array[index]; - } - - return wasLastBlock ? -1 : PeekMultiBlock(); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private int PeekMultiBlock() - { - var block = _block; - do - { - block = block.Next; - var index = block.Start; - - // Always set wasLastBlock before checking .End to avoid race which may cause data loss - var wasLastBlock = block.Next == null; - - if (index < block.End) - { - return block.Array[index]; - } - if (wasLastBlock) - { - return -1; - } - } while (true); - } - - // NOTE: Little-endian only! - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe bool TryPeekLong(out ulong longValue) - { - longValue = 0; - - var block = _block; - if (block == null) - { - return false; - } - - // Always set wasLastBlock before checking .End to avoid race which may cause data loss - var wasLastBlock = block.Next == null; - var blockBytes = block.End - _index; - - if (blockBytes >= sizeof(ulong)) - { - longValue = *(ulong*)(block.DataFixedPtr + _index); - return true; - } - - return wasLastBlock ? false : TryPeekLongMultiBlock(ref longValue, blockBytes); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe bool TryPeekLongMultiBlock(ref ulong longValue, int blockBytes) - { - // Each block will be filled with at least 2048 bytes before the Next pointer is set, so a long - // will cross at most one block boundary assuming there are at least 8 bytes following the iterator. - var nextBytes = sizeof(ulong) - blockBytes; - - var block = _block; - if (block.Next.End - block.Next.Start < nextBytes) - { - return false; - } - - var nextLong = *(ulong*)(block.Next.DataFixedPtr + block.Next.Start); - - if (blockBytes == 0) - { - // This case can not fall through to the else block since that would cause a 64-bit right shift - // on blockLong which is equivalent to no shift at all instead of shifting in all zeros. - // https://msdn.microsoft.com/en-us/library/xt18et0d.aspx - longValue = nextLong; - } - else - { - var blockLong = *(ulong*)(block.DataFixedPtr + block.End - sizeof(ulong)); - - // Ensure that the right shift has a ulong operand so a logical shift is performed. - longValue = (blockLong >> nextBytes * 8) | (nextLong << blockBytes * 8); - } - - return true; - } - - public int Seek(byte byte0) - { - int bytesScanned; - return Seek(byte0, out bytesScanned); - } - - public unsafe int Seek( - byte byte0, - out int bytesScanned, - int limit = int.MaxValue) - { - bytesScanned = 0; - - var block = _block; - if (block == null || limit <= 0) - { - return -1; - } - - var index = _index; - var wasLastBlock = block.Next == null; - var following = block.End - index; - byte[] array; - var byte0Vector = GetVector(byte0); - - while (true) - { - while (following == 0) - { - if (bytesScanned >= limit || wasLastBlock) - { - _block = block; - _index = index; - return -1; - } - - block = block.Next; - index = block.Start; - wasLastBlock = block.Next == null; - following = block.End - index; - } - array = block.Array; - while (following > 0) - { - // Need unit tests to test Vector path -#if !DEBUG - // Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079 - if (Vector.IsHardwareAccelerated) - { -#endif - if (following >= _vectorSpan) - { - var byte0Equals = Vector.Equals(new Vector(array, index), byte0Vector); - - if (byte0Equals.Equals(Vector.Zero)) - { - if (bytesScanned + _vectorSpan >= limit) - { - _block = block; - // Ensure iterator is left at limit position - _index = index + (limit - bytesScanned); - bytesScanned = limit; - return -1; - } - - bytesScanned += _vectorSpan; - following -= _vectorSpan; - index += _vectorSpan; - continue; - } - - _block = block; - - var firstEqualByteIndex = LocateFirstFoundByte(byte0Equals); - var vectorBytesScanned = firstEqualByteIndex + 1; - - if (bytesScanned + vectorBytesScanned > limit) - { - // Ensure iterator is left at limit position - _index = index + (limit - bytesScanned); - bytesScanned = limit; - return -1; - } - - _index = index + firstEqualByteIndex; - bytesScanned += vectorBytesScanned; - - return byte0; - } - // Need unit tests to test Vector path -#if !DEBUG - } -#endif - - var pCurrent = (block.DataFixedPtr + index); - var pEnd = pCurrent + Math.Min(following, limit - bytesScanned); - do - { - bytesScanned++; - if (*pCurrent == byte0) - { - _block = block; - _index = index; - return byte0; - } - pCurrent++; - index++; - } while (pCurrent < pEnd); - - following = 0; - break; - } - } - } - - public unsafe int Seek( - byte byte0, - ref MemoryPoolIterator limit) - { - var block = _block; - if (block == null) - { - return -1; - } - - var index = _index; - var wasLastBlock = block.Next == null; - var following = block.End - index; - - while (true) - { - while (following == 0) - { - if ((block == limit.Block && index > limit.Index) || - wasLastBlock) - { - _block = block; - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - - block = block.Next; - index = block.Start; - wasLastBlock = block.Next == null; - following = block.End - index; - } - var array = block.Array; - while (following > 0) - { -// Need unit tests to test Vector path -#if !DEBUG - // Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079 - if (Vector.IsHardwareAccelerated) - { -#endif - if (following >= _vectorSpan) - { - var byte0Equals = Vector.Equals(new Vector(array, index), GetVector(byte0)); - - if (byte0Equals.Equals(Vector.Zero)) - { - if (block == limit.Block && index + _vectorSpan > limit.Index) - { - _block = block; - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - - following -= _vectorSpan; - index += _vectorSpan; - continue; - } - - _block = block; - - var firstEqualByteIndex = LocateFirstFoundByte(byte0Equals); - - if (_block == limit.Block && index + firstEqualByteIndex > limit.Index) - { - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - - _index = index + firstEqualByteIndex; - - return byte0; - } -// Need unit tests to test Vector path -#if !DEBUG - } -#endif - - var pCurrent = (block.DataFixedPtr + index); - var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following; - do - { - if (*pCurrent == byte0) - { - _block = block; - _index = index; - return byte0; - } - pCurrent++; - index++; - } while (pCurrent < pEnd); - - following = 0; - break; - } - } - } - - public int Seek(byte byte0, byte byte1) - { - var limit = new MemoryPoolIterator(); - return Seek(byte0, byte1, ref limit); - } - - public unsafe int Seek( - byte byte0, - byte byte1, - ref MemoryPoolIterator limit) - { - var block = _block; - if (block == null) - { - return -1; - } - - var index = _index; - var wasLastBlock = block.Next == null; - var following = block.End - index; - int byteIndex = int.MaxValue; - - while (true) - { - while (following == 0) - { - if ((block == limit.Block && index > limit.Index) || - wasLastBlock) - { - _block = block; - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - block = block.Next; - index = block.Start; - wasLastBlock = block.Next == null; - following = block.End - index; - } - var array = block.Array; - while (following > 0) - { - -// Need unit tests to test Vector path -#if !DEBUG - // Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079 - if (Vector.IsHardwareAccelerated) - { -#endif - if (following >= _vectorSpan) - { - var data = new Vector(array, index); - - var byteEquals = Vector.Equals(data, GetVector(byte0)); - byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte1))); - - if (!byteEquals.Equals(Vector.Zero)) - { - byteIndex = LocateFirstFoundByte(byteEquals); - } - - if (byteIndex == int.MaxValue) - { - following -= _vectorSpan; - index += _vectorSpan; - - if (block == limit.Block && index > limit.Index) - { - _block = block; - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - - continue; - } - - _block = block; - - _index = index + byteIndex; - - if (block == limit.Block && _index > limit.Index) - { - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - - _index = index + byteIndex; - - if (block == limit.Block && _index > limit.Index) - { - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - - return block.Array[index + byteIndex]; - } -// Need unit tests to test Vector path -#if !DEBUG - } -#endif - var pCurrent = (block.DataFixedPtr + index); - var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following; - do - { - if (*pCurrent == byte0) - { - _block = block; - _index = index; - return byte0; - } - if (*pCurrent == byte1) - { - _block = block; - _index = index; - return byte1; - } - pCurrent++; - index++; - } while (pCurrent != pEnd); - - following = 0; - break; - } - } - } - - public int Seek(byte byte0, byte byte1, byte byte2) - { - var limit = new MemoryPoolIterator(); - return Seek(byte0, byte1, byte2, ref limit); - } - - public unsafe int Seek( - byte byte0, - byte byte1, - byte byte2, - ref MemoryPoolIterator limit) - { - var block = _block; - if (block == null) - { - return -1; - } - - var index = _index; - var wasLastBlock = block.Next == null; - var following = block.End - index; - int byteIndex = int.MaxValue; - - while (true) - { - while (following == 0) - { - if ((block == limit.Block && index > limit.Index) || - wasLastBlock) - { - _block = block; - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - block = block.Next; - index = block.Start; - wasLastBlock = block.Next == null; - following = block.End - index; - } - var array = block.Array; - while (following > 0) - { -// Need unit tests to test Vector path -#if !DEBUG - // Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079 - if (Vector.IsHardwareAccelerated) - { -#endif - if (following >= _vectorSpan) - { - var data = new Vector(array, index); - - var byteEquals = Vector.Equals(data, GetVector(byte0)); - byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte1))); - byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte2))); - - if (!byteEquals.Equals(Vector.Zero)) - { - byteIndex = LocateFirstFoundByte(byteEquals); - } - - if (byteIndex == int.MaxValue) - { - following -= _vectorSpan; - index += _vectorSpan; - - if (block == limit.Block && index > limit.Index) - { - _block = block; - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - - continue; - } - - _block = block; - - _index = index + byteIndex; - - if (block == limit.Block && _index > limit.Index) - { - // Ensure iterator is left at limit position - _index = limit.Index; - return -1; - } - - return block.Array[index + byteIndex]; - } -// Need unit tests to test Vector path -#if !DEBUG - } -#endif - var pCurrent = (block.DataFixedPtr + index); - var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following; - do - { - if (*pCurrent == byte0) - { - _block = block; - _index = index; - return byte0; - } - if (*pCurrent == byte1) - { - _block = block; - _index = index; - return byte1; - } - if (*pCurrent == byte2) - { - _block = block; - _index = index; - return byte2; - } - pCurrent++; - index++; - } while (pCurrent != pEnd); - - following = 0; - break; - } - } - } - - /// - /// Locate the first of the found bytes - /// - /// - /// The first index of the result vector - // Force inlining (64 IL bytes, 91 bytes asm) Issue: https://github.com/dotnet/coreclr/issues/7386 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int LocateFirstFoundByte(Vector byteEquals) - { - var vector64 = Vector.AsVectorUInt64(byteEquals); - ulong longValue = 0; - var i = 0; - // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001 - for (; i < Vector.Count; i++) - { - longValue = vector64[i]; - if (longValue == 0) continue; - break; - } - - // Flag least significant power of two bit - var powerOfTwoFlag = (longValue ^ (longValue - 1)); - // Shift all powers of two into the high byte and extract - var foundByteIndex = (int)((powerOfTwoFlag * _xorPowerOfTwoToHighByte) >> 57); - // Single LEA instruction with jitted const (using function result) - return i * 8 + foundByteIndex; - } - + /// /// Save the data at the current location then move to the next available space. /// @@ -914,57 +244,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure } } - public MemoryPoolIterator CopyTo(byte[] array, int offset, int count, out int actual) - { - var block = _block; - if (block == null) - { - actual = 0; - return this; - } - - var index = _index; - var remaining = count; - while (true) - { - // Determine if we might attempt to copy data from block.Next before - // calculating "following" so we don't risk skipping data that could - // be added after block.End when we decide to copy from block.Next. - // block.End will always be advanced before block.Next is set. - var wasLastBlock = block.Next == null; - var following = block.End - index; - if (remaining <= following) - { - actual = count; - if (array != null) - { - Buffer.BlockCopy(block.Array, index, array, offset, remaining); - } - return new MemoryPoolIterator(block, index + remaining); - } - else if (wasLastBlock) - { - actual = count - remaining + following; - if (array != null) - { - Buffer.BlockCopy(block.Array, index, array, offset, following); - } - return new MemoryPoolIterator(block, index + following); - } - else - { - if (array != null) - { - Buffer.BlockCopy(block.Array, index, array, offset, following); - } - offset += following; - remaining -= following; - block = block.Next; - index = block.Start; - } - } - } - public void CopyFrom(byte[] data) { CopyFrom(data, 0, data.Length); @@ -1180,177 +459,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure CopyFrom(byteBuffer, position, _maxULongByteLength - position); } - - public unsafe string GetAsciiString(ref MemoryPoolIterator end) - { - var block = _block; - if (block == null || end.IsDefault) - { - return null; - } - - var length = GetLength(end); - - if (length == 0) - { - return null; - } - - var inputOffset = _index; - - var asciiString = new string('\0', length); - - fixed (char* outputStart = asciiString) - { - var output = outputStart; - var remaining = length; - - var endBlock = end.Block; - var endIndex = end.Index; - - var outputOffset = 0; - while (true) - { - int following = (block != endBlock ? block.End : endIndex) - inputOffset; - - if (following > 0) - { - if (!AsciiUtilities.TryGetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following)) - { - throw BadHttpRequestException.GetException(RequestRejectionReason.NonAsciiOrNullCharactersInInputString); - } - - outputOffset += following; - remaining -= following; - } - - if (remaining == 0) - { - break; - } - - block = block.Next; - inputOffset = block.Start; - } - } - - return asciiString; - } - - public string GetUtf8String(ref MemoryPoolIterator end) - { - var block = _block; - if (block == null || end.IsDefault) - { - return default(string); - } - - var index = _index; - if (end.Block == block) - { - return Encoding.UTF8.GetString(block.Array, index, end.Index - index); - } - - var decoder = Encoding.UTF8.GetDecoder(); - - var length = GetLength(end); - var charLength = length; - // Worse case is 1 byte = 1 char - var chars = new char[charLength]; - var charIndex = 0; - - var remaining = length; - while (true) - { - int bytesUsed; - int charsUsed; - bool completed; - var following = block.End - index; - if (remaining <= following) - { - decoder.Convert( - block.Array, - index, - remaining, - chars, - charIndex, - charLength - charIndex, - true, - out bytesUsed, - out charsUsed, - out completed); - return new string(chars, 0, charIndex + charsUsed); - } - else if (block.Next == null) - { - decoder.Convert( - block.Array, - index, - following, - chars, - charIndex, - charLength - charIndex, - true, - out bytesUsed, - out charsUsed, - out completed); - return new string(chars, 0, charIndex + charsUsed); - } - else - { - decoder.Convert( - block.Array, - index, - following, - chars, - charIndex, - charLength - charIndex, - false, - out bytesUsed, - out charsUsed, - out completed); - charIndex += charsUsed; - remaining -= following; - block = block.Next; - index = block.Start; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArraySegment GetArraySegment(MemoryPoolIterator end) - { - var block = _block; - if (block == null || end.IsDefault) - { - return default(ArraySegment); - } - - var index = _index; - if (end.Block == block) - { - return new ArraySegment(block.Array, index, end.Index - index); - } - - return GetArraySegmentMultiBlock(ref end); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private ArraySegment GetArraySegmentMultiBlock(ref MemoryPoolIterator end) - { - var length = GetLength(end); - var array = new byte[length]; - CopyTo(array, 0, length, out length); - return new ArraySegment(array, 0, length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector GetVector(byte vectorByte) - { - // Vector .ctor doesn't become an intrinsic due to detection issue - // However this does cause it to become an intrinsic (with additional multiply and reg->reg copy) - // https://github.com/dotnet/coreclr/issues/7459#issuecomment-253965670 - return Vector.AsVectorByte(new Vector(vectorByte * 0x01010101u)); - } } } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs index 220ae3d80b..5252a96e9a 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs @@ -1,6 +1,7 @@ // 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.Linq; using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; @@ -14,27 +15,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests private void FullAsciiRangeSupported() { var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray(); - using (var pool = new MemoryPool()) + var s = new Span(byteRange).GetAsciiStringNonNullCharacters(); + + Assert.Equal(s.Length, byteRange.Length); + + for (var i = 1; i < byteRange.Length; i++) { - var mem = pool.Lease(); - mem.GetIterator().CopyFrom(byteRange); + var sb = (byte)s[i]; + var b = byteRange[i]; - var begin = mem.GetIterator(); - var end = GetIterator(begin, byteRange.Length); - - var s = begin.GetAsciiString(ref end); - - Assert.Equal(s.Length, byteRange.Length); - - for (var i = 1; i < byteRange.Length; i++) - { - var sb = (byte)s[i]; - var b = byteRange[i]; - - Assert.Equal(sb, b); - } - - pool.Return(mem); + Assert.Equal(sb, b); } } @@ -49,123 +39,29 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { var byteRange = Enumerable.Range(1, length).Select(x => (byte)x).ToArray(); byteRange[position] = b; - - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - mem.GetIterator().CopyFrom(byteRange); - - var begin = mem.GetIterator(); - var end = GetIterator(begin, byteRange.Length); - - Assert.Throws(() => begin.GetAsciiString(ref end)); - - pool.Return(mem); - } + + Assert.Throws(() => new Span(byteRange).GetAsciiStringNonNullCharacters()); } } } - [Fact] - private void MultiBlockProducesCorrectResults() - { - var byteRange = Enumerable.Range(0, 512 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray(); - var expectedByteRange = byteRange - .Concat(byteRange) - .Concat(byteRange) - .Concat(byteRange) - .ToArray(); - - using (var pool = new MemoryPool()) - { - var mem0 = pool.Lease(); - var mem1 = pool.Lease(); - var mem2 = pool.Lease(); - var mem3 = pool.Lease(); - mem0.GetIterator().CopyFrom(byteRange); - mem1.GetIterator().CopyFrom(byteRange); - mem2.GetIterator().CopyFrom(byteRange); - mem3.GetIterator().CopyFrom(byteRange); - - mem0.Next = mem1; - mem1.Next = mem2; - mem2.Next = mem3; - - var begin = mem0.GetIterator(); - var end = GetIterator(begin, expectedByteRange.Length); - - var s = begin.GetAsciiString(ref end); - - Assert.Equal(s.Length, expectedByteRange.Length); - - for (var i = 0; i < expectedByteRange.Length; i++) - { - var sb = (byte)((s[i] & 0x7f) | 0x01); - var b = expectedByteRange[i]; - - Assert.Equal(sb, b); - } - - pool.Return(mem0); - pool.Return(mem1); - pool.Return(mem2); - pool.Return(mem3); - } - } - [Fact] private void LargeAllocationProducesCorrectResults() { var byteRange = Enumerable.Range(0, 16384 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray(); var expectedByteRange = byteRange.Concat(byteRange).ToArray(); - using (var pool = new MemoryPool()) + + var s = new Span(expectedByteRange).GetAsciiStringNonNullCharacters(); + + Assert.Equal(expectedByteRange.Length, s.Length); + + for (var i = 0; i < expectedByteRange.Length; i++) { - var mem0 = pool.Lease(); - var mem1 = pool.Lease(); - mem0.GetIterator().CopyFrom(byteRange); - mem1.GetIterator().CopyFrom(byteRange); + var sb = (byte)((s[i] & 0x7f) | 0x01); + var b = expectedByteRange[i]; - var lastBlock = mem0; - while (lastBlock.Next != null) - { - lastBlock = lastBlock.Next; - } - lastBlock.Next = mem1; - - var begin = mem0.GetIterator(); - var end = GetIterator(begin, expectedByteRange.Length); - - var s = begin.GetAsciiString(ref end); - - Assert.Equal(expectedByteRange.Length, s.Length); - - for (var i = 0; i < expectedByteRange.Length; i++) - { - var sb = (byte)((s[i] & 0x7f) | 0x01); - var b = expectedByteRange[i]; - - Assert.Equal(sb, b); - } - - var block = mem0; - while (block != null) - { - var returnBlock = block; - block = block.Next; - pool.Return(returnBlock); - } + Assert.Equal(sb, b); } } - - private MemoryPoolIterator GetIterator(MemoryPoolIterator begin, int displacement) - { - var result = begin; - for (int i = 0; i < displacement; ++i) - { - result.Take(); - } - - return result; - } } } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolBlockTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolBlockTests.cs index 4cde6c079a..2213f81f07 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolBlockTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolBlockTests.cs @@ -8,105 +8,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public class MemoryPoolBlockTests { - [Fact] - public void SeekWorks() - { - using (var pool = new MemoryPool()) - { - var block = pool.Lease(); - foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x)) - { - block.Array[block.End++] = ch; - } - - var iterator = block.GetIterator(); - foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x)) - { - var hit = iterator; - hit.Seek(ch); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(ch, byte.MaxValue); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(byte.MaxValue, ch); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(ch, byte.MaxValue, byte.MaxValue); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(byte.MaxValue, ch, byte.MaxValue); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(ch, byte.MaxValue, byte.MaxValue); - Assert.Equal(ch, iterator.GetLength(hit)); - } - - pool.Return(block); - } - } - - [Fact] - public void SeekWorksAcrossBlocks() - { - using (var pool = new MemoryPool()) - { - var block1 = pool.Lease(); - var block2 = block1.Next = pool.Lease(); - var block3 = block2.Next = pool.Lease(); - - foreach (var ch in Enumerable.Range(0, 34).Select(x => (byte)x)) - { - block1.Array[block1.End++] = ch; - } - foreach (var ch in Enumerable.Range(34, 25).Select(x => (byte)x)) - { - block2.Array[block2.End++] = ch; - } - foreach (var ch in Enumerable.Range(59, 197).Select(x => (byte)x)) - { - block3.Array[block3.End++] = ch; - } - - var iterator = block1.GetIterator(); - foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x)) - { - var hit = iterator; - hit.Seek(ch); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(ch, byte.MaxValue); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(byte.MaxValue, ch); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(ch, byte.MaxValue, byte.MaxValue); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(byte.MaxValue, ch, byte.MaxValue); - Assert.Equal(ch, iterator.GetLength(hit)); - - hit = iterator; - hit.Seek(byte.MaxValue, byte.MaxValue, ch); - Assert.Equal(ch, iterator.GetLength(hit)); - } - - pool.Return(block1); - pool.Return(block2); - pool.Return(block3); - } - } - + [Fact] public void GetLengthBetweenIteratorsWorks() { @@ -192,87 +94,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests pool.Return(block2); } } - - [Fact] - public void CopyToCorrectlyTraversesBlocks() - { - using (var pool = new MemoryPool()) - { - var block1 = pool.Lease(); - var block2 = block1.Next = pool.Lease(); - - for (int i = 0; i < 128; i++) - { - block1.Array[block1.End++] = (byte)i; - } - for (int i = 128; i < 256; i++) - { - block2.Array[block2.End++] = (byte)i; - } - - var beginIterator = block1.GetIterator(); - - var array = new byte[256]; - int actual; - var endIterator = beginIterator.CopyTo(array, 0, 256, out actual); - - Assert.Equal(256, actual); - - for (int i = 0; i < 256; i++) - { - Assert.Equal(i, array[i]); - } - - endIterator.CopyTo(array, 0, 256, out actual); - Assert.Equal(0, actual); - - pool.Return(block1); - pool.Return(block2); - } - } - - [Fact] - public void CopyFromCorrectlyTraversesBlocks() - { - using (var pool = new MemoryPool()) - { - var block1 = pool.Lease(); - var start = block1.GetIterator(); - var end = start; - var bufferSize = block1.Data.Count * 3; - var buffer = new byte[bufferSize]; - - for (int i = 0; i < bufferSize; i++) - { - buffer[i] = (byte)(i % 73); - } - - Assert.Null(block1.Next); - - end.CopyFrom(new ArraySegment(buffer)); - - Assert.NotNull(block1.Next); - - for (int i = 0; i < bufferSize; i++) - { - Assert.Equal(i % 73, start.Take()); - } - - Assert.Equal(-1, start.Take()); - Assert.Equal(start.Block, end.Block); - Assert.Equal(start.Index, end.Index); - - var block = block1; - while (block != null) - { - var returnBlock = block; - block = block.Next; - - pool.Return(returnBlock); - } - } - } - + [Fact] public void IsEndCorrectlyTraversesBlocks() { diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolExtensions.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolExtensions.cs index 6348258ce9..1d62dd2793 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolExtensions.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolExtensions.cs @@ -6,8 +6,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public static MemoryPoolIterator Add(this MemoryPoolIterator iterator, int count) { - int actual; - return iterator.CopyTo(new byte[count], 0, count, out actual); + for (int i = 0; i < count; i++) + { + iterator.Take(); + } + return iterator; } } } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs index 83cf36c4c8..4643b5236b 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs @@ -30,80 +30,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests _pool.Dispose(); } - [Fact] - public void TestFindFirstEqualByte() - { - var bytes = Enumerable.Repeat(0xff, Vector.Count).ToArray(); - for (int i = 0; i < Vector.Count; i++) - { - Vector vector = new Vector(bytes); - Assert.Equal(i, MemoryPoolIterator.LocateFirstFoundByte(vector)); - bytes[i] = 0; - } - - for (int i = 0; i < Vector.Count; i++) - { - bytes[i] = 1; - Vector vector = new Vector(bytes); - Assert.Equal(i, MemoryPoolIterator.LocateFirstFoundByte(vector)); - bytes[i] = 0; - } - } - - [Theory] - [InlineData("a", "a", 'a', 0)] - [InlineData("ab", "a", 'a', 0)] - [InlineData("aab", "a", 'a', 0)] - [InlineData("acab", "a", 'a', 0)] - [InlineData("acab", "c", 'c', 1)] - [InlineData("abcdefghijklmnopqrstuvwxyz", "lo", 'l', 11)] - [InlineData("abcdefghijklmnopqrstuvwxyz", "ol", 'l', 11)] - [InlineData("abcdefghijklmnopqrstuvwxyz", "ll", 'l', 11)] - [InlineData("abcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)] - [InlineData("abcdefghijklmnopqrstuvwxyz", "rml", 'l', 11)] - [InlineData("abcdefghijklmnopqrstuvwxyz", "mlr", 'l', 11)] - [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)] - [InlineData("aaaaaaaaaaalmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)] - [InlineData("aaaaaaaaaaacmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'm', 12)] - [InlineData("aaaaaaaaaaarmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'r', 11)] - [InlineData("/localhost:5000/PATH/%2FPATH2/ HTTP/1.1", " %?", '%', 21)] - [InlineData("/localhost:5000/PATH/%2FPATH2/?key=value HTTP/1.1", " %?", '%', 21)] - [InlineData("/localhost:5000/PATH/PATH2/?key=value HTTP/1.1", " %?", '?', 27)] - [InlineData("/localhost:5000/PATH/PATH2/ HTTP/1.1", " %?", ' ', 27)] - public void MemorySeek(string raw, string search, char expectResult, int expectIndex) - { - var block = _pool.Lease(); - var chars = raw.ToCharArray().Select(c => (byte)c).ToArray(); - Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length); - block.End += chars.Length; - - var begin = block.GetIterator(); - var searchFor = search.ToCharArray(); - - int found = -1; - if (searchFor.Length == 1) - { - found = begin.Seek((byte)searchFor[0]); - } - else if (searchFor.Length == 2) - { - found = begin.Seek((byte)searchFor[0], (byte)searchFor[1]); - } - else if (searchFor.Length == 3) - { - found = begin.Seek((byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]); - } - else - { - Assert.False(true, "Invalid test sample."); - } - - Assert.Equal(expectResult, found); - Assert.Equal(expectIndex, begin.Index - block.Start); - - _pool.Return(block); - } - [Fact] public void Put() { @@ -241,728 +167,20 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } - [Fact] - public void PeekLong() - { - // Arrange - var block = _pool.Lease(); - var bytes = BitConverter.GetBytes(0x0102030405060708UL); - Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytes.Length); - block.End += bytes.Length; - var scan = block.GetIterator(); - var originalIndex = scan.Index; - - // Assert - ulong result; - Assert.True(scan.TryPeekLong(out result)); - Assert.Equal(0x0102030405060708UL, result); - Assert.Equal(originalIndex, scan.Index); - - _pool.Return(block); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(6)] - [InlineData(7)] - public void PeekLongNotEnoughBytes(int totalBytes) - { - // Arrange - var block = _pool.Lease(); - var bytes = BitConverter.GetBytes(0x0102030405060708UL); - var bytesLength = totalBytes; - Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytesLength); - block.End += bytesLength; - var scan = block.GetIterator(); - var originalIndex = scan.Index; - - // Assert - ulong result; - Assert.False(scan.TryPeekLong(out result)); - Assert.Equal(originalIndex, scan.Index); - _pool.Return(block); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(6)] - [InlineData(7)] - public void PeekLongNotEnoughBytesAtBlockBoundary(int firstBlockBytes) - { - // Arrange - var expectedResult = 0x0102030405060708UL; - var nextBlockBytes = 7 - firstBlockBytes; - - var block = _pool.Lease(); - block.End += firstBlockBytes; - - var nextBlock = _pool.Lease(); - nextBlock.End += nextBlockBytes; - - block.Next = nextBlock; - - var bytes = BitConverter.GetBytes(expectedResult); - Buffer.BlockCopy(bytes, 0, block.Array, block.Start, firstBlockBytes); - Buffer.BlockCopy(bytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes); - - var scan = block.GetIterator(); - var originalIndex = scan.Index; - - // Assert - ulong result; - Assert.False(scan.TryPeekLong(out result)); - Assert.Equal(originalIndex, scan.Index); - - _pool.Return(block); - _pool.Return(nextBlock); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(6)] - [InlineData(7)] - [InlineData(8)] - public void PeekLongAtBlockBoundary(int firstBlockBytes) - { - // Arrange - var expectedResult = 0x0102030405060708UL; - var nonZeroData = 0xFF00FFFF0000FFFFUL; - var nextBlockBytes = 8 - firstBlockBytes; - - var block = _pool.Lease(); - block.Start += 8; - block.End = block.Start + firstBlockBytes; - - var nextBlock = _pool.Lease(); - nextBlock.Start += 8; - nextBlock.End = nextBlock.Start + nextBlockBytes; - - block.Next = nextBlock; - - var bytes = BitConverter.GetBytes(expectedResult); - Buffer.BlockCopy(bytes, 0, block.Array, block.Start, firstBlockBytes); - Buffer.BlockCopy(bytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes); - - // Fill in surrounding bytes with non-zero data - var nonZeroBytes = BitConverter.GetBytes(nonZeroData); - Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.Start - 8, 8); - Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.End, 8); - Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.Start - 8, 8); - Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.End, 8); - - var scan = block.GetIterator(); - var originalIndex = scan.Index; - - // Assert - ulong result; - Assert.True(scan.TryPeekLong(out result)); - Assert.Equal(expectedResult, result); - Assert.Equal(originalIndex, scan.Index); - - _pool.Return(block); - _pool.Return(nextBlock); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(6)] - [InlineData(7)] - [InlineData(8)] - public void PeekLongAtBlockBoundarayWithMostSignificatBitsSet(int firstBlockBytes) - { - // Arrange - var expectedResult = 0xFF02030405060708UL; - var nonZeroData = 0xFF00FFFF0000FFFFUL; - var nextBlockBytes = 8 - firstBlockBytes; - - var block = _pool.Lease(); - block.Start += 8; - block.End = block.Start + firstBlockBytes; - - var nextBlock = _pool.Lease(); - nextBlock.Start += 8; - nextBlock.End = nextBlock.Start + nextBlockBytes; - - block.Next = nextBlock; - - var expectedBytes = BitConverter.GetBytes(expectedResult); - Buffer.BlockCopy(expectedBytes, 0, block.Array, block.Start, firstBlockBytes); - Buffer.BlockCopy(expectedBytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes); - - // Fill in surrounding bytes with non-zero data - var nonZeroBytes = BitConverter.GetBytes(nonZeroData); - Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.Start - 8, 8); - Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.End, 8); - Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.Start - 8, 8); - Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.End, 8); - - var scan = block.GetIterator(); - var originalIndex = scan.Index; - - // Assert - ulong result; - Assert.True(scan.TryPeekLong(out result)); - Assert.Equal(expectedResult, result); - Assert.Equal(originalIndex, scan.Index); - - _pool.Return(block); - _pool.Return(nextBlock); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(6)] - [InlineData(7)] - [InlineData(8)] - [InlineData(9)] - public void SkipAtBlockBoundary(int blockBytes) - { - // Arrange - var nextBlockBytes = 10 - blockBytes; - - var block = _pool.Lease(); - block.End += blockBytes; - - var nextBlock = _pool.Lease(); - nextBlock.End += nextBlockBytes; - - block.Next = nextBlock; - - var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - Buffer.BlockCopy(bytes, 0, block.Array, block.Start, blockBytes); - Buffer.BlockCopy(bytes, blockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes); - - var scan = block.GetIterator(); - var originalIndex = scan.Index; - - // Act - scan.Skip(8); - var result = scan.Take(); - - // Assert - Assert.Equal(0x08, result); - Assert.NotEqual(originalIndex, scan.Index); - - _pool.Return(block); - _pool.Return(nextBlock); - } - - [Fact] - public void SkipThrowsWhenSkippingMoreBytesThanAvailableInSingleBlock() - { - // Arrange - var block = _pool.Lease(); - block.End += 5; - - var scan = block.GetIterator(); - - // Act/Assert - Assert.ThrowsAny(() => scan.Skip(8)); - - _pool.Return(block); - } - - [Fact] - public void SkipThrowsWhenSkippingMoreBytesThanAvailableInMultipleBlocks() - { - // Arrange - var firstBlock = _pool.Lease(); - firstBlock.End += 3; - - var middleBlock = _pool.Lease(); - middleBlock.End += 1; - firstBlock.Next = middleBlock; - - var finalBlock = _pool.Lease(); - finalBlock.End += 2; - middleBlock.Next = finalBlock; - - var scan = firstBlock.GetIterator(); - - // Act/Assert - Assert.ThrowsAny(() => scan.Skip(8)); - - _pool.Return(firstBlock); - _pool.Return(middleBlock); - _pool.Return(finalBlock); - } - - - [Theory] - [MemberData(nameof(SeekByteLimitData))] - public void TestSeekByteLimitWithinSameBlock(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue) - { - MemoryPoolBlock block = null; - - try - { - // Arrange - - block = _pool.Lease(); - var chars = input.ToString().ToCharArray().Select(c => (byte) c).ToArray(); - Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length); - block.End += chars.Length; - var scan = block.GetIterator(); - - // Act - int bytesScanned; - var returnValue = scan.Seek((byte)seek, out bytesScanned, limit); - - // Assert - Assert.Equal(expectedBytesScanned, bytesScanned); - Assert.Equal(expectedReturnValue, returnValue); - - Assert.Same(block, scan.Block); - var expectedEndIndex = expectedReturnValue != -1 ? - block.Start + input.IndexOf(seek) : - block.Start + expectedBytesScanned; - Assert.Equal(expectedEndIndex, scan.Index); - } - finally - { - // Cleanup - if (block != null) _pool.Return(block); - } - } - - [Theory] - [MemberData(nameof(SeekByteLimitData))] - public void TestSeekByteLimitAcrossBlocks(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue) - { - MemoryPoolBlock block1 = null; - MemoryPoolBlock block2 = null; - MemoryPoolBlock emptyBlock = null; - - try - { - // Arrange - var input1 = input.Substring(0, input.Length / 2); - block1 = _pool.Lease(); - var chars1 = input1.ToCharArray().Select(c => (byte)c).ToArray(); - Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length); - block1.End += chars1.Length; - - emptyBlock = _pool.Lease(); - block1.Next = emptyBlock; - - var input2 = input.Substring(input.Length / 2); - block2 = _pool.Lease(); - var chars2 = input2.ToCharArray().Select(c => (byte)c).ToArray(); - Buffer.BlockCopy(chars2, 0, block2.Array, block2.Start, chars2.Length); - block2.End += chars2.Length; - emptyBlock.Next = block2; - - var scan = block1.GetIterator(); - - // Act - int bytesScanned; - var returnValue = scan.Seek((byte)seek, out bytesScanned, limit); - - // Assert - Assert.Equal(expectedBytesScanned, bytesScanned); - Assert.Equal(expectedReturnValue, returnValue); - - var seekCharIndex = input.IndexOf(seek); - var expectedEndBlock = limit <= input.Length / 2 ? - block1 : - (seekCharIndex != -1 && seekCharIndex < input.Length / 2 ? block1 : block2); - Assert.Same(expectedEndBlock, scan.Block); - var expectedEndIndex = expectedReturnValue != -1 ? - expectedEndBlock.Start + (expectedEndBlock == block1 ? input1.IndexOf(seek) : input2.IndexOf(seek)) : - expectedEndBlock.Start + (expectedEndBlock == block1 ? expectedBytesScanned : expectedBytesScanned - (input.Length / 2)); - Assert.Equal(expectedEndIndex, scan.Index); - } - finally - { - // Cleanup - if (block1 != null) _pool.Return(block1); - if (emptyBlock != null) _pool.Return(emptyBlock); - if (block2 != null) _pool.Return(block2); - } - } - - [Theory] - [MemberData(nameof(SeekIteratorLimitData))] - public void TestSeekIteratorLimitWithinSameBlock(string input, char seek, char limitAt, int expectedReturnValue) - { - MemoryPoolBlock block = null; - - try - { - // Arrange - var afterSeek = (byte)'B'; - - block = _pool.Lease(); - var chars = input.ToCharArray().Select(c => (byte)c).ToArray(); - Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length); - block.End += chars.Length; - var scan1 = block.GetIterator(); - var scan2_1 = scan1; - var scan2_2 = scan1; - var scan3_1 = scan1; - var scan3_2 = scan1; - var scan3_3 = scan1; - var end = scan1; - - // Act - var endReturnValue = end.Seek((byte)limitAt); - var returnValue1 = scan1.Seek((byte)seek, ref end); - var returnValue2_1 = scan2_1.Seek((byte)seek, afterSeek, ref end); - var returnValue2_2 = scan2_2.Seek(afterSeek, (byte)seek, ref end); - var returnValue3_1 = scan3_1.Seek((byte)seek, afterSeek, afterSeek, ref end); - var returnValue3_2 = scan3_2.Seek(afterSeek, (byte)seek, afterSeek, ref end); - var returnValue3_3 = scan3_3.Seek(afterSeek, afterSeek, (byte)seek, ref end); - - // Assert - Assert.Equal(input.Contains(limitAt) ? limitAt : -1, endReturnValue); - Assert.Equal(expectedReturnValue, returnValue1); - Assert.Equal(expectedReturnValue, returnValue2_1); - Assert.Equal(expectedReturnValue, returnValue2_2); - Assert.Equal(expectedReturnValue, returnValue3_1); - Assert.Equal(expectedReturnValue, returnValue3_2); - Assert.Equal(expectedReturnValue, returnValue3_3); - - Assert.Same(block, scan1.Block); - Assert.Same(block, scan2_1.Block); - Assert.Same(block, scan2_2.Block); - Assert.Same(block, scan3_1.Block); - Assert.Same(block, scan3_2.Block); - Assert.Same(block, scan3_3.Block); - - var expectedEndIndex = expectedReturnValue != -1 ? block.Start + input.IndexOf(seek) : end.Index; - Assert.Equal(expectedEndIndex, scan1.Index); - Assert.Equal(expectedEndIndex, scan2_1.Index); - Assert.Equal(expectedEndIndex, scan2_2.Index); - Assert.Equal(expectedEndIndex, scan3_1.Index); - Assert.Equal(expectedEndIndex, scan3_2.Index); - Assert.Equal(expectedEndIndex, scan3_3.Index); - } - finally - { - // Cleanup - if (block != null) _pool.Return(block); - } - } - - [Theory] - [MemberData(nameof(SeekIteratorLimitData))] - public void TestSeekIteratorLimitAcrossBlocks(string input, char seek, char limitAt, int expectedReturnValue) - { - MemoryPoolBlock block1 = null; - MemoryPoolBlock block2 = null; - MemoryPoolBlock emptyBlock = null; - - try - { - // Arrange - var afterSeek = (byte)'B'; - - var input1 = input.Substring(0, input.Length / 2); - block1 = _pool.Lease(); - var chars1 = input1.ToCharArray().Select(c => (byte)c).ToArray(); - Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length); - block1.End += chars1.Length; - - emptyBlock = _pool.Lease(); - block1.Next = emptyBlock; - - var input2 = input.Substring(input.Length / 2); - block2 = _pool.Lease(); - var chars2 = input2.ToCharArray().Select(c => (byte)c).ToArray(); - Buffer.BlockCopy(chars2, 0, block2.Array, block2.Start, chars2.Length); - block2.End += chars2.Length; - emptyBlock.Next = block2; - - var scan1 = block1.GetIterator(); - var scan2_1 = scan1; - var scan2_2 = scan1; - var scan3_1 = scan1; - var scan3_2 = scan1; - var scan3_3 = scan1; - var end = scan1; - - // Act - var endReturnValue = end.Seek((byte)limitAt); - var returnValue1 = scan1.Seek((byte)seek, ref end); - var returnValue2_1 = scan2_1.Seek((byte)seek, afterSeek, ref end); - var returnValue2_2 = scan2_2.Seek(afterSeek, (byte)seek, ref end); - var returnValue3_1 = scan3_1.Seek((byte)seek, afterSeek, afterSeek, ref end); - var returnValue3_2 = scan3_2.Seek(afterSeek, (byte)seek, afterSeek, ref end); - var returnValue3_3 = scan3_3.Seek(afterSeek, afterSeek, (byte)seek, ref end); - - // Assert - Assert.Equal(input.Contains(limitAt) ? limitAt : -1, endReturnValue); - Assert.Equal(expectedReturnValue, returnValue1); - Assert.Equal(expectedReturnValue, returnValue2_1); - Assert.Equal(expectedReturnValue, returnValue2_2); - Assert.Equal(expectedReturnValue, returnValue3_1); - Assert.Equal(expectedReturnValue, returnValue3_2); - Assert.Equal(expectedReturnValue, returnValue3_3); - - var seekCharIndex = input.IndexOf(seek); - var limitAtIndex = input.IndexOf(limitAt); - var expectedEndBlock = seekCharIndex != -1 && seekCharIndex < input.Length / 2 ? - block1 : - (limitAtIndex != -1 && limitAtIndex < input.Length / 2 ? block1 : block2); - Assert.Same(expectedEndBlock, scan1.Block); - Assert.Same(expectedEndBlock, scan2_1.Block); - Assert.Same(expectedEndBlock, scan2_2.Block); - Assert.Same(expectedEndBlock, scan3_1.Block); - Assert.Same(expectedEndBlock, scan3_2.Block); - Assert.Same(expectedEndBlock, scan3_3.Block); - - var expectedEndIndex = expectedReturnValue != -1 ? - expectedEndBlock.Start + (expectedEndBlock == block1 ? input1.IndexOf(seek) : input2.IndexOf(seek)) : - end.Index; - Assert.Equal(expectedEndIndex, scan1.Index); - Assert.Equal(expectedEndIndex, scan2_1.Index); - Assert.Equal(expectedEndIndex, scan2_2.Index); - Assert.Equal(expectedEndIndex, scan3_1.Index); - Assert.Equal(expectedEndIndex, scan3_2.Index); - Assert.Equal(expectedEndIndex, scan3_3.Index); - } - finally - { - // Cleanup - if (block1 != null) _pool.Return(block1); - if (emptyBlock != null) _pool.Return(emptyBlock); - if (block2 != null) _pool.Return(block2); - } - } [Fact] public void EmptyIteratorBehaviourIsValid() { const byte byteCr = (byte)'\n'; - ulong longValue; var end = default(MemoryPoolIterator); - - Assert.False(default(MemoryPoolIterator).TryPeekLong(out longValue)); - Assert.Null(default(MemoryPoolIterator).GetAsciiString(ref end)); - Assert.Null(default(MemoryPoolIterator).GetUtf8String(ref end)); - // Assert.Equal doesn't work for default(ArraySegments) - Assert.True(default(MemoryPoolIterator).GetArraySegment(end).Equals(default(ArraySegment))); + Assert.True(default(MemoryPoolIterator).IsDefault); Assert.True(default(MemoryPoolIterator).IsEnd); - Assert.Equal(default(MemoryPoolIterator).Take(), -1); - Assert.Equal(default(MemoryPoolIterator).Peek(), -1); - Assert.Equal(default(MemoryPoolIterator).Seek(byteCr), -1); - Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, ref end), -1); - Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, byteCr), -1); - Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, byteCr, byteCr), -1); default(MemoryPoolIterator).CopyFrom(default(ArraySegment)); default(MemoryPoolIterator).CopyFromAscii(""); Assert.ThrowsAny(() => default(MemoryPoolIterator).Put(byteCr)); Assert.ThrowsAny(() => default(MemoryPoolIterator).GetLength(end)); - Assert.ThrowsAny(() => default(MemoryPoolIterator).Skip(1)); - } - - [Fact] - public void TestGetArraySegment() - { - MemoryPoolBlock block0 = null; - MemoryPoolBlock block1 = null; - - var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray(); - try - { - // Arrange - block0 = _pool.Lease(); - block1 = _pool.Lease(); - - block0.GetIterator().CopyFrom(byteRange); - block1.GetIterator().CopyFrom(byteRange); - - block0.Next = block1; - - var begin = block0.GetIterator(); - var end0 = begin; - var end1 = begin; - - end0.Skip(byteRange.Length); - end1.Skip(byteRange.Length * 2); - - // Act - var as0 = begin.GetArraySegment(end0); - var as1 = begin.GetArraySegment(end1); - - // Assert - Assert.Equal(as0.Count, byteRange.Length); - Assert.Equal(as1.Count, byteRange.Length * 2); - - for (var i = 1; i < byteRange.Length; i++) - { - var asb0 = as0.Array[i + as0.Offset]; - var asb1 = as1.Array[i + as1.Offset]; - var b = byteRange[i]; - - Assert.Equal(asb0, b); - Assert.Equal(asb1, b); - } - - for (var i = 1 + byteRange.Length; i < byteRange.Length * 2; i++) - { - var asb1 = as1.Array[i + as1.Offset]; - var b = byteRange[i - byteRange.Length]; - - Assert.Equal(asb1, b); - } - - } - finally - { - if (block0 != null) _pool.Return(block0); - if (block1 != null) _pool.Return(block1); - } - } - - [Fact] - public void TestTake() - { - MemoryPoolBlock block0 = null; - MemoryPoolBlock block1 = null; - MemoryPoolBlock block2 = null; - MemoryPoolBlock emptyBlock0 = null; - MemoryPoolBlock emptyBlock1 = null; - - var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray(); - try - { - // Arrange - block0 = _pool.Lease(); - block1 = _pool.Lease(); - block2 = _pool.Lease(); - emptyBlock0 = _pool.Lease(); - emptyBlock1 = _pool.Lease(); - - block0.GetIterator().CopyFrom(byteRange); - block1.GetIterator().CopyFrom(byteRange); - block2.GetIterator().CopyFrom(byteRange); - - var begin = block0.GetIterator(); - - // Single block - for (var i = 0; i < byteRange.Length; i++) - { - var t = begin.Take(); - var b = byteRange[i]; - - Assert.Equal(t, b); - } - - Assert.Equal(begin.Take(), -1); - - // Dual block - block0.Next = block1; - begin = block0.GetIterator(); - - for (var block = 0; block < 2; block++) - { - for (var i = 0; i < byteRange.Length; i++) - { - var t = begin.Take(); - var b = byteRange[i]; - - Assert.Equal(t, b); - } - } - - Assert.Equal(begin.Take(), -1); - - // Multi block - block1.Next = emptyBlock0; - emptyBlock0.Next = emptyBlock1; - emptyBlock1.Next = block2; - begin = block0.GetIterator(); - - for (var block = 0; block < 3; block++) - { - for (var i = 0; i < byteRange.Length; i++) - { - var t = begin.Take(); - var b = byteRange[i]; - - Assert.Equal(t, b); - } - } - - Assert.Equal(begin.Take(), -1); - } - finally - { - if (block0 != null) _pool.Return(block0); - if (block1 != null) _pool.Return(block1); - if (block2 != null) _pool.Return(block2); - if (emptyBlock0 != null) _pool.Return(emptyBlock0); - if (emptyBlock1 != null) _pool.Return(emptyBlock1); - } - } - - [Fact] - public void TestTakeEmptyBlocks() - { - MemoryPoolBlock emptyBlock0 = null; - MemoryPoolBlock emptyBlock1 = null; - MemoryPoolBlock emptyBlock2 = null; - try - { - // Arrange - emptyBlock0 = _pool.Lease(); - emptyBlock1 = _pool.Lease(); - emptyBlock2 = _pool.Lease(); - - var beginEmpty = emptyBlock0.GetIterator(); - - // Assert - - // No blocks - Assert.Equal(default(MemoryPoolIterator).Take(), -1); - - // Single empty block - Assert.Equal(beginEmpty.Take(), -1); - - // Dual empty block - emptyBlock0.Next = emptyBlock1; - beginEmpty = emptyBlock0.GetIterator(); - Assert.Equal(beginEmpty.Take(), -1); - - // Multi empty block - emptyBlock1.Next = emptyBlock2; - beginEmpty = emptyBlock0.GetIterator(); - Assert.Equal(beginEmpty.Take(), -1); - } - finally - { - if (emptyBlock0 != null) _pool.Return(emptyBlock0); - if (emptyBlock1 != null) _pool.Return(emptyBlock1); - if (emptyBlock2 != null) _pool.Return(emptyBlock2); - } } [Theory] @@ -973,23 +191,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests [InlineData("abcde", "abcde", 6)] public void TestGetAsciiStringEscaped(string input, string expected, int maxChars) { - MemoryPoolBlock block = null; - - try - { // Arrange - var buffer = new Span(Encoding.ASCII.GetBytes(input)); + var buffer = new Span(Encoding.ASCII.GetBytes(input)); - // Act - var result = buffer.GetAsciiStringEscaped(maxChars); + // Act + var result = buffer.GetAsciiStringEscaped(maxChars); - // Assert - Assert.Equal(expected, result); - } - finally - { - if (block != null) _pool.Return(block); - } + // Assert + Assert.Equal(expected, result); } [Fact] diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs deleted file mode 100644 index 0acfc27fe8..0000000000 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/UrlPathDecoder.cs +++ /dev/null @@ -1,226 +0,0 @@ -// 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.Linq; -using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; -using Xunit; - -namespace Microsoft.AspNetCore.Server.KestrelTests -{ - public class UrlPathDecoderTests - { - [Fact] - public void Empty() - { - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - - PositiveAssert(mem, string.Empty, string.Empty); - - pool.Return(mem); - } - } - - [Fact] - public void WhiteSpace() - { - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - - PositiveAssert(mem, " ", " "); - - pool.Return(mem); - } - } - - [Theory] - [InlineData("/foo/bar", "/foo/bar")] - [InlineData("/foo/BAR", "/foo/BAR")] - [InlineData("/foo/", "/foo/")] - [InlineData("/", "/")] - public void NormalCases(string raw, string expect) - { - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - - PositiveAssert(mem, raw, expect); - - pool.Return(mem); - } - } - - [Theory] - [InlineData("%2F", "%2F")] - [InlineData("/foo%2Fbar", "/foo%2Fbar")] - [InlineData("/foo%2F%20bar", "/foo%2F bar")] - public void SkipForwardSlash(string raw, string expect) - { - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - - PositiveAssert(mem, raw, expect); - - pool.Return(mem); - } - } - - [Theory] - [InlineData("%D0%A4", "Ф")] - [InlineData("%d0%a4", "Ф")] - [InlineData("%E0%A4%AD", "भ")] - [InlineData("%e0%A4%Ad", "भ")] - [InlineData("%F0%A4%AD%A2", "𤭢")] - [InlineData("%F0%a4%Ad%a2", "𤭢")] - [InlineData("%48%65%6C%6C%6F%20%57%6F%72%6C%64", "Hello World")] - [InlineData("%48%65%6C%6C%6F%2D%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", "Hello-µ@ßöäüàá")] - // Test the borderline cases of overlong UTF8. - [InlineData("%C2%80", "\u0080")] - [InlineData("%E0%A0%80", "\u0800")] - [InlineData("%F0%90%80%80", "\U00010000")] - [InlineData("%63", "c")] - [InlineData("%32", "2")] - [InlineData("%20", " ")] - public void ValidUTF8(string raw, string expect) - { - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - - PositiveAssert(mem, raw, expect); - - pool.Return(mem); - } - } - - [Theory] - [InlineData("%C3%84ra%20Benetton", "Ära Benetton")] - [InlineData("%E6%88%91%E8%87%AA%E6%A8%AA%E5%88%80%E5%90%91%E5%A4%A9%E7%AC%91%E5%8E%BB%E7%95%99%E8%82%9D%E8%83%86%E4%B8%A4%E6%98%86%E4%BB%91", "我自横刀向天笑去留肝胆两昆仑")] - public void Internationalized(string raw, string expect) - { - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - - PositiveAssert(mem, raw, expect); - - pool.Return(mem); - } - } - - [Theory] - // Overlong ASCII - [InlineData("%C0%A4", "%C0%A4")] - [InlineData("%C1%BF", "%C1%BF")] - [InlineData("%E0%80%AF", "%E0%80%AF")] - [InlineData("%E0%9F%BF", "%E0%9F%BF")] - [InlineData("%F0%80%80%AF", "%F0%80%80%AF")] - [InlineData("%F0%8F%8F%BF", "%F0%8F%8F%BF")] - // Incomplete - [InlineData("%", "%")] - [InlineData("%%", "%%")] - [InlineData("%A", "%A")] - [InlineData("%Y", "%Y")] - // Mixed - [InlineData("%%32", "%2")] - [InlineData("%%20", "% ")] - [InlineData("%C0%A4%32", "%C0%A42")] - [InlineData("%32%C0%A4%32", "2%C0%A42")] - [InlineData("%C0%32%A4", "%C02%A4")] - public void InvalidUTF8(string raw, string expect) - { - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - - PositiveAssert(mem, raw, expect); - - pool.Return(mem); - } - } - - [Theory] - [InlineData("/foo%2Fbar", 10, "/foo%2Fbar", 10)] - [InlineData("/foo%2Fbar", 9, "/foo%2Fba", 9)] - [InlineData("/foo%2Fbar", 8, "/foo%2Fb", 8)] - [InlineData("%D0%A4", 6, "Ф", 1)] - [InlineData("%D0%A4", 5, "%D0%A", 5)] - [InlineData("%D0%A4", 4, "%D0%", 4)] - [InlineData("%D0%A4", 3, "%D0", 3)] - [InlineData("%D0%A4", 2, "%D", 2)] - [InlineData("%D0%A4", 1, "%", 1)] - [InlineData("%D0%A4", 0, "", 0)] - [InlineData("%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", 45, "µ@ßöäüàá", 8)] - [InlineData("%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", 44, "µ@ßöäüà%C3%A", 12)] - public void DecodeWithBoundary(string raw, int rawLength, string expect, int expectLength) - { - using (var pool = new MemoryPool()) - { - var mem = pool.Lease(); - - var begin = BuildSample(mem, raw); - var end = GetIterator(begin, rawLength); - - var end2 = UrlPathDecoder.Unescape(begin, end); - var result = begin.GetUtf8String(ref end2); - - Assert.Equal(expectLength, result.Length); - Assert.Equal(expect, result); - - pool.Return(mem); - } - } - - private MemoryPoolIterator BuildSample(MemoryPoolBlock mem, string data) - { - var store = data.Select(c => (byte)c).ToArray(); - mem.GetIterator().CopyFrom(new ArraySegment(store)); - - return mem.GetIterator(); - } - - private MemoryPoolIterator GetIterator(MemoryPoolIterator begin, int displacement) - { - var result = begin; - for (int i = 0; i < displacement; ++i) - { - result.Take(); - } - - return result; - } - - private void PositiveAssert(MemoryPoolBlock mem, string raw, string expect) - { - var begin = BuildSample(mem, raw); - var end = GetIterator(begin, raw.Length); - - var result = UrlPathDecoder.Unescape(begin, end); - Assert.Equal(expect, begin.GetUtf8String(ref result)); - } - - private void PositiveAssert(MemoryPoolBlock mem, string raw) - { - var begin = BuildSample(mem, raw); - var end = GetIterator(begin, raw.Length); - - var result = UrlPathDecoder.Unescape(begin, end); - Assert.NotEqual(raw.Length, begin.GetUtf8String(ref result).Length); - } - - private void NegativeAssert(MemoryPoolBlock mem, string raw) - { - var begin = BuildSample(mem, raw); - var end = GetIterator(begin, raw.Length); - - var resultEnd = UrlPathDecoder.Unescape(begin, end); - var result = begin.GetUtf8String(ref resultEnd); - Assert.Equal(raw, result); - } - } -}