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;
|
||||
|
||||
await FireOnStarting();
|
||||
|
||||
if (_applicationException != null)
|
||||
|
||||
if (_applicationException != null)
|
||||
{
|
||||
throw new ObjectDisposedException(
|
||||
"The response has been aborted due to an unhandled application exception.",
|
||||
|
|
@ -591,12 +591,17 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
|
||||
scan.Take();
|
||||
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 = "";
|
||||
if (chFound == '?')
|
||||
|
|
@ -623,9 +628,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
return false;
|
||||
}
|
||||
|
||||
if (needDecode)
|
||||
{
|
||||
pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd);
|
||||
}
|
||||
|
||||
var requestUrlPath = pathBegin.GetString(pathEnd);
|
||||
|
||||
consumed = scan;
|
||||
Method = method;
|
||||
RequestUri = requestUri;
|
||||
RequestUri = requestUrlPath;
|
||||
QueryString = queryString;
|
||||
HttpVersion = httpVersion;
|
||||
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.Numerics;
|
||||
using System.Text;
|
||||
|
|
@ -9,9 +12,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
|||
{
|
||||
/// <summary>
|
||||
/// 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>
|
||||
private static Vector<byte> _dotCount = new Vector<byte>(Byte.MaxValue);
|
||||
private static Vector<byte> _dotCount = new Vector<byte>(Byte.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
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