Unescape string in memory
1. In place unescape; 1. UTF-8 verification; 2. MemoryPoolIterator2.Put 3. Tests
This commit is contained in:
parent
f88631efb3
commit
52f4fa91e3
|
|
@ -420,8 +420,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
if (_responseStarted) return;
|
if (_responseStarted) return;
|
||||||
|
|
||||||
await FireOnStarting();
|
await FireOnStarting();
|
||||||
|
|
||||||
if (_applicationException != null)
|
if (_applicationException != null)
|
||||||
{
|
{
|
||||||
throw new ObjectDisposedException(
|
throw new ObjectDisposedException(
|
||||||
"The response has been aborted due to an unhandled application exception.",
|
"The response has been aborted due to an unhandled application exception.",
|
||||||
|
|
@ -591,12 +591,17 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
scan.Take();
|
scan.Take();
|
||||||
begin = scan;
|
begin = scan;
|
||||||
var chFound = scan.Seek(' ', '?');
|
|
||||||
if (chFound == -1)
|
var needDecode = false;
|
||||||
|
var chFound = scan.Seek(' ', '?', '%');
|
||||||
|
if (chFound == '%')
|
||||||
{
|
{
|
||||||
return false;
|
needDecode = true;
|
||||||
|
chFound = scan.Seek(' ', '?');
|
||||||
}
|
}
|
||||||
var requestUri = begin.GetString(scan);
|
|
||||||
|
var pathBegin = begin;
|
||||||
|
var pathEnd = scan;
|
||||||
|
|
||||||
var queryString = "";
|
var queryString = "";
|
||||||
if (chFound == '?')
|
if (chFound == '?')
|
||||||
|
|
@ -623,9 +628,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needDecode)
|
||||||
|
{
|
||||||
|
pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestUrlPath = pathBegin.GetString(pathEnd);
|
||||||
|
|
||||||
consumed = scan;
|
consumed = scan;
|
||||||
Method = method;
|
Method = method;
|
||||||
RequestUri = requestUri;
|
RequestUri = requestUrlPath;
|
||||||
QueryString = queryString;
|
QueryString = queryString;
|
||||||
HttpVersion = httpVersion;
|
HttpVersion = httpVersion;
|
||||||
Path = RequestUri;
|
Path = RequestUri;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
// 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.AspNet.Server.Kestrel.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
{
|
||||||
|
public class UrlPathDecoder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unescapes the string between given memory iterators in place.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">The iterator points to the beginning of the sequence.</param>
|
||||||
|
/// <param name="end">The iterator points to the byte behind the end of the sequence.</param>
|
||||||
|
/// <returns>The iterator points to the byte behind the end of the processed sequence.</returns>
|
||||||
|
public static MemoryPoolIterator2 Unescape(MemoryPoolIterator2 start, MemoryPoolIterator2 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unescape the percent-encodings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">The iterator point to the first % char</param>
|
||||||
|
/// <param name="writer">The place to write to</param>
|
||||||
|
/// <param name="end">The end of the sequence</param>
|
||||||
|
private static bool DecodeCore(ref MemoryPoolIterator2 reader, ref MemoryPoolIterator2 writer, MemoryPoolIterator2 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 == -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(MemoryPoolIterator2 head, MemoryPoolIterator2 tail, ref MemoryPoolIterator2 writer)
|
||||||
|
{
|
||||||
|
while (!CompareIterators(ref head, ref tail))
|
||||||
|
{
|
||||||
|
writer.Put((byte)head.Take());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read the percent-encoding and try unescape it.
|
||||||
|
///
|
||||||
|
/// The operation first peek at the character the <paramref name="scan"/>
|
||||||
|
/// iterator points at. If it is % the <paramref name="scan"/> 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 <paramref name="scan"/> iterator
|
||||||
|
/// will be removed beyond the location of % and -1 will be returned.
|
||||||
|
///
|
||||||
|
/// If the following two characters can't be successfully unescaped the
|
||||||
|
/// <paramref name="scan"/> iterator will be move behind the % and -1
|
||||||
|
/// will be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scan">The value to read</param>
|
||||||
|
/// <param name="end">The end of the sequence</param>
|
||||||
|
/// <returns>The unescaped byte if success. Otherwise return -1.</returns>
|
||||||
|
private static int UnescapePercentEncoding(ref MemoryPoolIterator2 scan, MemoryPoolIterator2 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read the next char and convert it into hexadecimal value.
|
||||||
|
///
|
||||||
|
/// The <paramref name="scan"/> iterator will be moved to the next
|
||||||
|
/// byte no matter no matter whether the operation successes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scan">The value to read</param>
|
||||||
|
/// <param name="end">The end of the sequence</param>
|
||||||
|
/// <returns>The hexadecimal value if successes, otherwise -1.</returns>
|
||||||
|
private static int ReadHex(ref MemoryPoolIterator2 scan, MemoryPoolIterator2 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 MemoryPoolIterator2 lhs, ref MemoryPoolIterator2 rhs)
|
||||||
|
{
|
||||||
|
// uses ref parameter to save cost of copying
|
||||||
|
return (lhs.Block == rhs.Block) && (lhs.Index == rhs.Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
using System;
|
// 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 System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -9,9 +12,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Array of "minus one" bytes of the length of SIMD operations on the current hardware. Used as an argument in the
|
/// Array of "minus one" bytes of the length of SIMD operations on the current hardware. Used as an argument in the
|
||||||
/// vector dot product that counts matching character occurence.
|
/// vector dot product that counts matching character occurrence.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Vector<byte> _dotCount = new Vector<byte>(Byte.MaxValue);
|
private static Vector<byte> _dotCount = new Vector<byte>(Byte.MaxValue);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Array of negative numbers starting at 0 and continuing for the length of SIMD operations on the current hardware.
|
/// Array of negative numbers starting at 0 and continuing for the length of SIMD operations on the current hardware.
|
||||||
|
|
@ -295,6 +298,167 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int Seek(int char0, int char1, int char2)
|
||||||
|
{
|
||||||
|
if (IsDefault)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var byte0 = (byte)char0;
|
||||||
|
var byte1 = (byte)char1;
|
||||||
|
var byte2 = (byte)char2;
|
||||||
|
var vectorStride = Vector<byte>.Count;
|
||||||
|
var ch0Vector = new Vector<byte>(byte0);
|
||||||
|
var ch1Vector = new Vector<byte>(byte1);
|
||||||
|
var ch2Vector = new Vector<byte>(byte2);
|
||||||
|
|
||||||
|
var block = _block;
|
||||||
|
var index = _index;
|
||||||
|
var array = block.Array;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
while (block.End == index)
|
||||||
|
{
|
||||||
|
if (block.Next == null)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
|
array = block.Array;
|
||||||
|
}
|
||||||
|
while (block.End != index)
|
||||||
|
{
|
||||||
|
var following = block.End - index;
|
||||||
|
if (following >= vectorStride)
|
||||||
|
{
|
||||||
|
var data = new Vector<byte>(array, index);
|
||||||
|
var ch0Equals = Vector.Equals(data, ch0Vector);
|
||||||
|
var ch0Count = Vector.Dot(ch0Equals, _dotCount);
|
||||||
|
var ch1Equals = Vector.Equals(data, ch1Vector);
|
||||||
|
var ch1Count = Vector.Dot(ch1Equals, _dotCount);
|
||||||
|
var ch2Equals = Vector.Equals(data, ch2Vector);
|
||||||
|
var ch2Count = Vector.Dot(ch2Equals, _dotCount);
|
||||||
|
|
||||||
|
if (ch0Count == 0 && ch1Count == 0 && ch2Count == 0)
|
||||||
|
{
|
||||||
|
index += vectorStride;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (ch0Count < 2 && ch1Count < 2 && ch2Count < 2)
|
||||||
|
{
|
||||||
|
var ch0Index = ch0Count == 1 ? Vector.Dot(ch0Equals, _dotIndex) : byte.MaxValue;
|
||||||
|
var ch1Index = ch1Count == 1 ? Vector.Dot(ch1Equals, _dotIndex) : byte.MaxValue;
|
||||||
|
var ch2Index = ch2Count == 1 ? Vector.Dot(ch2Equals, _dotIndex) : byte.MaxValue;
|
||||||
|
|
||||||
|
int toReturn, toMove;
|
||||||
|
if (ch0Index < ch1Index)
|
||||||
|
{
|
||||||
|
if (ch0Index < ch2Index)
|
||||||
|
{
|
||||||
|
toReturn = char0;
|
||||||
|
toMove = ch0Index;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toReturn = char2;
|
||||||
|
toMove = ch2Index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ch1Index < ch2Index)
|
||||||
|
{
|
||||||
|
toReturn = char1;
|
||||||
|
toMove = ch1Index;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toReturn = char2;
|
||||||
|
toMove = ch2Index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_block = block;
|
||||||
|
_index = index + toMove;
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
following = vectorStride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (following > 0)
|
||||||
|
{
|
||||||
|
var byteIndex = block.Array[index];
|
||||||
|
if (byteIndex == byte0)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index;
|
||||||
|
return char0;
|
||||||
|
}
|
||||||
|
else if (byteIndex == byte1)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index;
|
||||||
|
return char1;
|
||||||
|
}
|
||||||
|
else if (byteIndex == byte2)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index;
|
||||||
|
return char2;
|
||||||
|
}
|
||||||
|
following--;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the data at the current location then move to the next available space.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The byte to be saved.</param>
|
||||||
|
/// <returns>true if the operation successes. false if can't find available space.</returns>
|
||||||
|
public bool Put(byte data)
|
||||||
|
{
|
||||||
|
if (_block == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (_index < _block.End)
|
||||||
|
{
|
||||||
|
_block.Array[_index++] = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var block = _block;
|
||||||
|
var index = _index;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (index < block.End)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index + 1;
|
||||||
|
block.Array[index] = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (block.Next == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int GetLength(MemoryPoolIterator2 end)
|
public int GetLength(MemoryPoolIterator2 end)
|
||||||
{
|
{
|
||||||
if (IsDefault || end.IsDefault)
|
if (IsDefault || end.IsDefault)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
|
{
|
||||||
|
public class MemoryPoolIterator2Tests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly MemoryPool2 _pool;
|
||||||
|
|
||||||
|
public MemoryPoolIterator2Tests()
|
||||||
|
{
|
||||||
|
_pool = new MemoryPool2();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_pool.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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(256);
|
||||||
|
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(searchFor[0]);
|
||||||
|
}
|
||||||
|
else if (searchFor.Length == 2)
|
||||||
|
{
|
||||||
|
found = begin.Seek(searchFor[0], searchFor[1]);
|
||||||
|
}
|
||||||
|
else if (searchFor.Length == 3)
|
||||||
|
{
|
||||||
|
found = begin.Seek(searchFor[0], searchFor[1], searchFor[2]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.False(true, "Invalid test sample.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(expectResult, found);
|
||||||
|
Assert.Equal(expectIndex, begin.Index - block.Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Put()
|
||||||
|
{
|
||||||
|
var blocks = new MemoryPoolBlock2[4];
|
||||||
|
for (var i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
blocks[i] = _pool.Lease(16);
|
||||||
|
blocks[i].End += 16;
|
||||||
|
|
||||||
|
for (var j = 0; j < blocks.Length; ++j)
|
||||||
|
{
|
||||||
|
blocks[i].Array[blocks[i].Start + j] = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != 0)
|
||||||
|
{
|
||||||
|
blocks[i - 1].Next = blocks[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// put FF at first block's head
|
||||||
|
var head = blocks[0].GetIterator();
|
||||||
|
Assert.True(head.Put(0xFF));
|
||||||
|
|
||||||
|
// data is put at correct position
|
||||||
|
Assert.Equal(0xFF, blocks[0].Array[blocks[0].Start]);
|
||||||
|
Assert.Equal(0x00, blocks[0].Array[blocks[0].Start + 1]);
|
||||||
|
|
||||||
|
// iterator is moved to next byte after put
|
||||||
|
Assert.Equal(1, head.Index - blocks[0].Start);
|
||||||
|
|
||||||
|
for (var i = 0; i < 14; ++i)
|
||||||
|
{
|
||||||
|
// move itr to the end of the block 0
|
||||||
|
head.Take();
|
||||||
|
}
|
||||||
|
|
||||||
|
// write to the end of block 0
|
||||||
|
Assert.True(head.Put(0xFE));
|
||||||
|
Assert.Equal(0xFE, blocks[0].Array[blocks[0].End - 1]);
|
||||||
|
Assert.Equal(0x00, blocks[1].Array[blocks[1].Start]);
|
||||||
|
|
||||||
|
// put data across the block link
|
||||||
|
Assert.True(head.Put(0xFD));
|
||||||
|
Assert.Equal(0xFD, blocks[1].Array[blocks[1].Start]);
|
||||||
|
Assert.Equal(0x00, blocks[1].Array[blocks[1].Start + 1]);
|
||||||
|
|
||||||
|
// paint every block
|
||||||
|
head = blocks[0].GetIterator();
|
||||||
|
for (var i = 0; i < 64; ++i)
|
||||||
|
{
|
||||||
|
Assert.True(head.Put((byte)i), $"Fail to put data at {i}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't put anything by the end
|
||||||
|
Assert.False(head.Put(0xFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
// 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.AspNet.Server.Kestrel.Http;
|
||||||
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
|
{
|
||||||
|
public class UrlPathDecoderTests
|
||||||
|
{
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Empty()
|
||||||
|
{
|
||||||
|
PositiveAssert(string.Empty, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WhiteSpace()
|
||||||
|
{
|
||||||
|
PositiveAssert(" ", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/foo/bar", "/foo/bar")]
|
||||||
|
[InlineData("/foo/BAR", "/foo/BAR")]
|
||||||
|
[InlineData("/foo/", "/foo/")]
|
||||||
|
[InlineData("/", "/")]
|
||||||
|
public void NormalCases(string raw, string expect)
|
||||||
|
{
|
||||||
|
PositiveAssert(raw, expect);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("%2F", "%2F")]
|
||||||
|
[InlineData("/foo%2Fbar", "/foo%2Fbar")]
|
||||||
|
[InlineData("/foo%2F%20bar", "/foo%2F bar")]
|
||||||
|
public void SkipForwardSlash(string raw, string expect)
|
||||||
|
{
|
||||||
|
PositiveAssert(raw, expect);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
PositiveAssert(raw, expect);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
PositiveAssert(raw, expect);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
PositiveAssert(raw, expect);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
var begin = BuildSample(raw);
|
||||||
|
var end = GetIterator(begin, rawLength);
|
||||||
|
|
||||||
|
var end2 = UrlPathDecoder.Unescape(begin, end);
|
||||||
|
var result = begin.GetString(end2);
|
||||||
|
|
||||||
|
Assert.Equal(expectLength, result.Length);
|
||||||
|
Assert.Equal(expect, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemoryPoolIterator2 BuildSample(string data)
|
||||||
|
{
|
||||||
|
var store = data.Select(c => (byte)c).ToArray();
|
||||||
|
var mem = MemoryPoolBlock2.Create(new ArraySegment<byte>(store), IntPtr.Zero, null, null);
|
||||||
|
mem.End = store.Length;
|
||||||
|
|
||||||
|
return mem.GetIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemoryPoolIterator2 GetIterator(MemoryPoolIterator2 begin, int displacement)
|
||||||
|
{
|
||||||
|
var result = begin;
|
||||||
|
for (int i = 0; i < displacement; ++i)
|
||||||
|
{
|
||||||
|
result.Take();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PositiveAssert(string raw, string expect)
|
||||||
|
{
|
||||||
|
var begin = BuildSample(raw);
|
||||||
|
var end = GetIterator(begin, raw.Length);
|
||||||
|
|
||||||
|
var result = UrlPathDecoder.Unescape(begin, end);
|
||||||
|
Assert.Equal(expect, begin.GetString(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PositiveAssert(string raw)
|
||||||
|
{
|
||||||
|
var begin = BuildSample(raw);
|
||||||
|
var end = GetIterator(begin, raw.Length);
|
||||||
|
|
||||||
|
var result = UrlPathDecoder.Unescape(begin, end);
|
||||||
|
Assert.NotEqual(raw.Length, begin.GetString(result).Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NegativeAssert(string raw)
|
||||||
|
{
|
||||||
|
var begin = BuildSample(raw);
|
||||||
|
var end = GetIterator(begin, raw.Length);
|
||||||
|
|
||||||
|
var resultEnd = UrlPathDecoder.Unescape(begin, end);
|
||||||
|
var result = begin.GetString(resultEnd);
|
||||||
|
Assert.Equal(raw, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue