private static char[] Compress(char[] dest, ushort start, ref int destLength, UriParser syntax)
{
ushort slashCount = 0;
ushort lastSlash = 0;
ushort dotCount = 0;
ushort removeSegments = 0;
unchecked
{
//ushort i == -1 and start == -1 overflow is ok here
ushort i = (ushort)((ushort)destLength - (ushort)1);
start = (ushort)(start - 1);
for (; i != start; --i)
{
char ch = dest[i];
if (ch == '\\' && syntax.InFact(UriSyntaxFlags.ConvertPathSlashes))
{
dest[i] = ch = '/';
}
//
// compress multiple '/' for file URI
//
if (ch == '/')
{
++slashCount;
}
else
{
if (slashCount > 1)
{
// else preserve repeated slashes
lastSlash = (ushort)(i + 1);
}
slashCount = 0;
}
if (ch == '.')
{
++dotCount;
continue;
}
else if (dotCount != 0)
{
bool skipSegment = syntax.NotAny(UriSyntaxFlags.CanonicalizeAsFilePath)
&& (dotCount > 2 || ch != '/' || i == start);
//
// Cases:
// /./ = remove this segment
// /../ = remove this segment, mark next for removal
// /....x = DO NOT TOUCH, leave as is
// x.../ = DO NOT TOUCH, leave as is, except for V2 legacy mode
//
if (!skipSegment && ch == '/')
{
if ((lastSlash == i + dotCount + 1 // "/..../"
|| (lastSlash == 0 && i + dotCount + 1 == destLength)) // "/..."
&& (dotCount <= 2))
{
//
// /./ or /.<eos> or /../ or /..<eos>
//
// just reusing a variable slot we perform //dest.Remove(i+1, dotCount + (lastSlash==0?0:1));
lastSlash = (ushort)(i + 1 + dotCount + (lastSlash == 0 ? 0 : 1));
Buffer.BlockCopy(dest, lastSlash << 1, dest, (i + 1) << 1, (destLength - lastSlash) << 1);
destLength -= (lastSlash - i - 1);
lastSlash = i;
if (dotCount == 2)
{
//
// We have 2 dots in between like /../ or /..<eos>,
// Mark next segment for removal and remove this /../ or /..
//
++removeSegments;
}
dotCount = 0;
continue;
}
}
// .NET 4.5 no longer removes trailing dots in a path segment x.../ or x...<eos>
dotCount = 0;
//
// Here all other cases go such as
// x.[..]y or /.[..]x or (/x.[...][/] && removeSegments !=0)
}
//
// Now we may want to remove a segment because of previous /../
//
if (ch == '/')
{
if (removeSegments != 0)
{
--removeSegments;
// just reusing a variable slot we perform //dest.Remove(i+1, lastSlash - i);
lastSlash = (ushort)(lastSlash + 1);
Buffer.BlockCopy(dest, lastSlash << 1, dest, (i + 1) << 1, (destLength - lastSlash) << 1);
destLength -= (lastSlash - i - 1);
}
lastSlash = i;
}
}
start = (ushort)((ushort)start + (ushort)1);
} //end of unchecked
if ((ushort)destLength > start && syntax.InFact(UriSyntaxFlags.CanonicalizeAsFilePath))
{
if (slashCount <= 1)
{
if (removeSegments != 0 && dest[start] != '/')
{
//remove first not rooted segment
lastSlash = (ushort)(lastSlash + 1);
Buffer.BlockCopy(dest, lastSlash << 1, dest, start << 1, (destLength - lastSlash) << 1);
destLength -= lastSlash;
}
else if (dotCount != 0)
{
// If final string starts with a segment looking like .[...]/ or .[...]<eos>
// then we remove this first segment
if (lastSlash == dotCount + 1 || (lastSlash == 0 && dotCount + 1 == destLength))
{
dotCount = (ushort)(dotCount + (lastSlash == 0 ? 0 : 1));
Buffer.BlockCopy(dest, dotCount << 1, dest, start << 1, (destLength - dotCount) << 1);
destLength -= dotCount;
}
}
}
}
return dest;
}