Imported Upstream version 6.4.0.137

Former-commit-id: 943baa9f16a098c33e129777827f3a9d20da00d6
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2019-07-26 19:53:28 +00:00
parent e9207cf623
commit ef583813eb
2712 changed files with 74169 additions and 40587 deletions

View File

@@ -10,15 +10,6 @@ namespace System
// The idea is to stay with static helper methods and strings
internal static partial class IPv4AddressHelper
{
internal const long Invalid = -1;
// Note: the native parser cannot handle MaxIPv4Value, only MaxIPv4Value - 1
private const long MaxIPv4Value = UInt32.MaxValue;
private const int Octal = 8;
private const int Decimal = 10;
private const int Hex = 16;
private const int NumberOfLabels = 4;
// methods
// Parse and canonicalize
internal static string ParseCanonicalName(string str, int start, int end, ref bool isLoopback)
@@ -42,298 +33,6 @@ namespace System
}
}
// Only called from the IPv6Helper, only parse the canonical format
internal static int ParseHostNumber(string str, int start, int end)
{
unsafe
{
byte* numbers = stackalloc byte[NumberOfLabels];
ParseCanonical(str, numbers, start, end);
return (numbers[0] << 24) + (numbers[1] << 16) + (numbers[2] << 8) + numbers[3];
}
}
//
// IsValid
//
// Performs IsValid on a substring. Updates the index to where we
// believe the IPv4 address ends
//
// Inputs:
// <argument> name
// string containing possible IPv4 address
//
// <argument> start
// offset in <name> to start checking for IPv4 address
//
// <argument> end
// offset in <name> of the last character we can touch in the check
//
// Outputs:
// <argument> end
// index of last character in <name> we checked
//
// <argument> allowIPv6
// enables parsing IPv4 addresses embedded in IPv6 address literals
//
// <argument> notImplicitFile
// do not consider this URI holding an implicit filename
//
// <argument> unknownScheme
// the check is made on an unknown scheme (suppress IPv4 canonicalization)
//
// Assumes:
// The address string is terminated by either
// end of the string, characters ':' '/' '\' '?'
//
//
// Returns:
// bool
//
// Throws:
// Nothing
//
//Remark: MUST NOT be used unless all input indexes are verified and trusted.
internal static unsafe bool IsValid(char* name, int start, ref int end, bool allowIPv6, bool notImplicitFile, bool unknownScheme)
{
// IPv6 can only have canonical IPv4 embedded. Unknown schemes will not attempt parsing of non-canonical IPv4 addresses.
if (allowIPv6 || unknownScheme)
{
return IsValidCanonical(name, start, ref end, allowIPv6, notImplicitFile);
}
else
{
return ParseNonCanonical(name, start, ref end, notImplicitFile) != Invalid;
}
}
//
// IsValidCanonical
//
// Checks if the substring is a valid canonical IPv4 address or an IPv4 address embedded in an IPv6 literal
// This is an attempt to parse ABNF productions from RFC3986, Section 3.2.2:
// IP-literal = "[" ( IPv6address / IPvFuture ) "]"
// IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
// dec-octet = DIGIT ; 0-9
// / %x31-39 DIGIT ; 10-99
// / "1" 2DIGIT ; 100-199
// / "2" %x30-34 DIGIT ; 200-249
// / "25" %x30-35 ; 250-255
//
internal static unsafe bool IsValidCanonical(char* name, int start, ref int end, bool allowIPv6, bool notImplicitFile)
{
int dots = 0;
int number = 0;
bool haveNumber = false;
bool firstCharIsZero = false;
while (start < end)
{
char ch = name[start];
if (allowIPv6)
{
// for ipv4 inside ipv6 the terminator is either ScopeId, prefix or ipv6 terminator
if (ch == ']' || ch == '/' || ch == '%') break;
}
else if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
{
break;
}
if (ch <= '9' && ch >= '0')
{
if (!haveNumber && (ch == '0'))
{
if ((start + 1 < end) && name[start + 1] == '0')
{
// 00 is not allowed as a prefix.
return false;
}
firstCharIsZero = true;
}
haveNumber = true;
number = number * 10 + (name[start] - '0');
if (number > 255)
{
return false;
}
}
else if (ch == '.')
{
if (!haveNumber || (number > 0 && firstCharIsZero))
{
// 0 is not allowed to prefix a number.
return false;
}
++dots;
haveNumber = false;
number = 0;
firstCharIsZero = false;
}
else
{
return false;
}
++start;
}
bool res = (dots == 3) && haveNumber;
if (res)
{
end = start;
}
return res;
}
// Parse any canonical or non-canonical IPv4 formats and return a long between 0 and MaxIPv4Value.
// Return Invalid (-1) for failures.
// If the address has less than three dots, only the rightmost section is assumed to contain the combined value for
// the missing sections: 0xFF00FFFF == 0xFF.0x00.0xFF.0xFF == 0xFF.0xFFFF
internal static unsafe long ParseNonCanonical(char* name, int start, ref int end, bool notImplicitFile)
{
int numberBase = Decimal;
char ch;
Span<long> parts = stackalloc long[4];
long currentValue = 0;
bool atLeastOneChar = false;
// Parse one dotted section at a time
int dotCount = 0; // Limit 3
int current = start;
for (; current < end; current++)
{
ch = name[current];
currentValue = 0;
// Figure out what base this section is in
numberBase = Decimal;
if (ch == '0')
{
numberBase = Octal;
current++;
atLeastOneChar = true;
if (current < end)
{
ch = name[current];
if (ch == 'x' || ch == 'X')
{
numberBase = Hex;
current++;
atLeastOneChar = false;
}
}
}
// Parse this section
for (; current < end; current++)
{
ch = name[current];
int digitValue;
if ((numberBase == Decimal || numberBase == Hex) && '0' <= ch && ch <= '9')
{
digitValue = ch - '0';
}
else if (numberBase == Octal && '0' <= ch && ch <= '7')
{
digitValue = ch - '0';
}
else if (numberBase == Hex && 'a' <= ch && ch <= 'f')
{
digitValue = ch + 10 - 'a';
}
else if (numberBase == Hex && 'A' <= ch && ch <= 'F')
{
digitValue = ch + 10 - 'A';
}
else
{
break; // Invalid/terminator
}
currentValue = (currentValue * numberBase) + digitValue;
if (currentValue > MaxIPv4Value) // Overflow
{
return Invalid;
}
atLeastOneChar = true;
}
if (current < end && name[current] == '.')
{
if (dotCount >= 3 // Max of 3 dots and 4 segments
|| !atLeastOneChar // No empty segments: 1...1
// Only the last segment can be more than 255 (if there are less than 3 dots)
|| currentValue > 0xFF)
{
return Invalid;
}
parts[dotCount] = currentValue;
dotCount++;
atLeastOneChar = false;
continue;
}
// We don't get here unless We find an invalid character or a terminator
break;
}
// Terminators
if (!atLeastOneChar)
{
return Invalid; // Empty trailing segment: 1.1.1.
}
else if (current >= end)
{
// end of string, allowed
}
else if ((ch = name[current]) == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
{
end = current;
}
else
{
// not a valid terminating character
return Invalid;
}
parts[dotCount] = currentValue;
// Parsed, reassemble and check for overflows
switch (dotCount)
{
case 0: // 0xFFFFFFFF
if (parts[0] > MaxIPv4Value)
{
return Invalid;
}
return parts[0];
case 1: // 0xFF.0xFFFFFF
if (parts[1] > 0xffffff)
{
return Invalid;
}
return (parts[0] << 24) | (parts[1] & 0xffffff);
case 2: // 0xFF.0xFF.0xFFFF
if (parts[2] > 0xffff)
{
return Invalid;
}
return (parts[0] << 24) | ((parts[1] & 0xff) << 16) | (parts[2] & 0xffff);
case 3: // 0xFF.0xFF.0xFF.0xFF
if (parts[3] > 0xff)
{
return Invalid;
}
return (parts[0] << 24) | ((parts[1] & 0xff) << 16) | ((parts[2] & 0xff) << 8) | (parts[3] & 0xff);
default:
return Invalid;
}
}
//
// Parse
//
@@ -359,26 +58,5 @@ namespace System
return numbers[0] == 127;
}
// Assumes:
// <Name> has been validated and contains only decimal digits in groups
// of 8-bit numbers and the characters '.'
// Address may terminate with ':' or with the end of the string
//
private static unsafe bool ParseCanonical(string name, byte* numbers, int start, int end)
{
for (int i = 0; i < NumberOfLabels; ++i)
{
byte b = 0;
char ch;
for (; (start < end) && (ch = name[start]) != '.' && ch != ':'; ++start)
{
b = (byte)(b * 10 + (byte)(ch - '0'));
}
numbers[i] = b;
++start;
}
return numbers[0] == 127;
}
}
}

View File

@@ -3,8 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Diagnostics;
namespace System
{
@@ -12,108 +11,111 @@ namespace System
// The idea is to stay with static helper methods and strings
internal static partial class IPv6AddressHelper
{
// fields
#if !MONO
private const int NumberOfLabels = 8;
#endif
// Lower case hex, no leading zeros
private const string CanonicalNumberFormat = "{0:x}";
private const string EmbeddedIPv4Format = ":{0:d}.{1:d}.{2:d}.{3:d}";
private const char Separator = ':';
// methods
internal static string ParseCanonicalName(string str, int start, ref bool isLoopback, ref string scopeId)
internal static unsafe string ParseCanonicalName(string str, int start, ref bool isLoopback, ref string scopeId)
{
unsafe
{
ushort* numbers = stackalloc ushort[NumberOfLabels];
// optimized zeroing of 8 shorts = 2 longs
((long*)numbers)[0] = 0L;
((long*)numbers)[1] = 0L;
isLoopback = Parse(str, numbers, start, ref scopeId);
return '[' + CreateCanonicalName(numbers) + ']';
}
}
ushort* numbersPtr = stackalloc ushort[NumberOfLabels];
// optimized zeroing of 8 shorts = 2 longs
((long*)numbersPtr)[0] = 0L;
((long*)numbersPtr)[1] = 0L;
Span<ushort> numbers = new Span<ushort>(numbersPtr, NumberOfLabels);
Parse(str, numbersPtr, start, ref scopeId);
isLoopback = IsLoopback(numbers);
internal static unsafe string CreateCanonicalName(ushort* numbers)
{
// RFC 5952 Sections 4 & 5 - Compressed, lower case, with possible embedded IPv4 addresses.
// Start to finish, inclusive. <-1, -1> for no compression
KeyValuePair<int, int> range = FindCompressionRange(numbers);
(int rangeStart, int rangeEnd) = FindCompressionRange(numbers);
bool ipv4Embedded = ShouldHaveIpv4Embedded(numbers);
StringBuilder builder = new StringBuilder();
Span<char> stackSpace = stackalloc char[48]; // large enough for any IPv6 string, including brackets
stackSpace[0] = '[';
int pos = 1;
int charsWritten;
bool success;
for (int i = 0; i < NumberOfLabels; i++)
{
if (ipv4Embedded && i == (NumberOfLabels - 2))
{
stackSpace[pos++] = ':';
// Write the remaining digits as an IPv4 address
builder.AppendFormat(CultureInfo.InvariantCulture, EmbeddedIPv4Format,
numbers[i] >> 8, numbers[i] & 0xFF, numbers[i + 1] >> 8, numbers[i + 1] & 0xFF);
success = (numbers[i] >> 8).TryFormat(stackSpace.Slice(pos), out charsWritten);
Debug.Assert(success);
pos += charsWritten;
stackSpace[pos++] = '.';
success = (numbers[i] & 0xFF).TryFormat(stackSpace.Slice(pos), out charsWritten);
Debug.Assert(success);
pos += charsWritten;
stackSpace[pos++] = '.';
success = (numbers[i + 1] >> 8).TryFormat(stackSpace.Slice(pos), out charsWritten);
Debug.Assert(success);
pos += charsWritten;
stackSpace[pos++] = '.';
success = (numbers[i + 1] & 0xFF).TryFormat(stackSpace.Slice(pos), out charsWritten);
Debug.Assert(success);
pos += charsWritten;
break;
}
// Compression; 1::1, ::1, 1::
if (range.Key == i)
{ // Start compression, add :
builder.Append(Separator);
if (rangeStart == i)
{
// Start compression, add :
stackSpace[pos++] = ':';
}
if (range.Key <= i && range.Value == (NumberOfLabels - 1))
{ // Remainder compressed; 1::
builder.Append(Separator);
if (rangeStart <= i && rangeEnd == NumberOfLabels)
{
// Remainder compressed; 1::
stackSpace[pos++] = ':';
break;
}
if (range.Key <= i && i <= range.Value)
if (rangeStart <= i && i < rangeEnd)
{
continue; // Compressed
}
if (i != 0)
{
builder.Append(Separator);
stackSpace[pos++] = ':';
}
builder.AppendFormat(CultureInfo.InvariantCulture, CanonicalNumberFormat, numbers[i]);
success = numbers[i].TryFormat(stackSpace.Slice(pos), out charsWritten, format: "x");
Debug.Assert(success);
pos += charsWritten;
}
return builder.ToString();
stackSpace[pos++] = ']';
return new string(stackSpace.Slice(0, pos));
}
// RFC 5952 Section 4.2.3
// Longest consecutive sequence of zero segments, minimum 2.
// On equal, first sequence wins.
// <-1, -1> for no compression.
private static unsafe KeyValuePair<int, int> FindCompressionRange(ushort* numbers)
private static unsafe bool IsLoopback(ReadOnlySpan<ushort> numbers)
{
int longestSequenceLength = 0;
int longestSequenceStart = -1;
//
// is the address loopback? Loopback is defined as one of:
//
// 0:0:0:0:0:0:0:1
// 0:0:0:0:0:0:127.0.0.1 == 0:0:0:0:0:0:7F00:0001
// 0:0:0:0:0:FFFF:127.0.0.1 == 0:0:0:0:0:FFFF:7F00:0001
//
int currentSequenceLength = 0;
for (int i = 0; i < NumberOfLabels; i++)
{
if (numbers[i] == 0)
{ // In a sequence
currentSequenceLength++;
if (currentSequenceLength > longestSequenceLength)
{
longestSequenceLength = currentSequenceLength;
longestSequenceStart = i - currentSequenceLength + 1;
}
}
else
{
currentSequenceLength = 0;
}
}
if (longestSequenceLength >= 2)
{
return new KeyValuePair<int, int>(longestSequenceStart,
longestSequenceStart + longestSequenceLength - 1);
}
return new KeyValuePair<int, int>(-1, -1); // No compression
return ((numbers[0] == 0)
&& (numbers[1] == 0)
&& (numbers[2] == 0)
&& (numbers[3] == 0)
&& (numbers[4] == 0))
&& (((numbers[5] == 0)
&& (numbers[6] == 0)
&& (numbers[7] == 1))
|| (((numbers[6] == 0x7F00)
&& (numbers[7] == 0x0001))
&& ((numbers[5] == 0)
|| (numbers[5] == 0xFFFF))));
}
// Returns true if the IPv6 address should be formated with an embedded IPv4 address:
@@ -351,210 +353,5 @@ namespace System
{
return InternalIsValid(name, start, ref end, false);
}
//
// Parse
//
// Convert this IPv6 address into a sequence of 8 16-bit numbers
//
// Inputs:
// <member> Name
// The validated IPv6 address
//
// Outputs:
// <member> numbers
// Array filled in with the numbers in the IPv6 groups
//
// <member> PrefixLength
// Set to the number after the prefix separator (/) if found
//
// Assumes:
// <Name> has been validated and contains only hex digits in groups of
// 16-bit numbers, the characters ':' and '/', and a possible IPv4
// address
//
// Returns:
// true if this is a loopback, false otherwise. There is no failure indication as the sting must be a valid one.
//
// Throws:
// Nothing
//
internal static unsafe bool Parse(string address, ushort* numbers, int start, ref string scopeId)
{
int number = 0;
int index = 0;
int compressorIndex = -1;
bool numberIsValid = true;
//This used to be a class instance member but have not been used so far
int PrefixLength = 0;
if (address[start] == '[')
{
++start;
}
for (int i = start; i < address.Length && address[i] != ']';)
{
switch (address[i])
{
case '%':
if (numberIsValid)
{
numbers[index++] = (ushort)number;
numberIsValid = false;
}
start = i;
for (++i; address[i] != ']' && address[i] != '/'; ++i)
{
;
}
scopeId = address.Substring(start, i - start);
// ignore prefix if any
for (; address[i] != ']'; ++i)
{
;
}
break;
case ':':
numbers[index++] = (ushort)number;
number = 0;
++i;
if (address[i] == ':')
{
compressorIndex = index;
++i;
}
else if ((compressorIndex < 0) && (index < 6))
{
//
// no point checking for IPv4 address if we don't
// have a compressor or we haven't seen 6 16-bit
// numbers yet
//
break;
}
//
// check to see if the upcoming number is really an IPv4
// address. If it is, convert it to 2 ushort numbers
//
for (int j = i; (address[j] != ']') &&
(address[j] != ':') &&
(address[j] != '%') &&
(address[j] != '/') &&
(j < i + 4); ++j)
{
if (address[j] == '.')
{
//
// we have an IPv4 address. Find the end of it:
// we know that since we have a valid IPv6
// address, the only things that will terminate
// the IPv4 address are the prefix delimiter '/'
// or the end-of-string (which we conveniently
// delimited with ']')
//
while ((address[j] != ']') && (address[j] != '/') && (address[j] != '%'))
{
++j;
}
number = IPv4AddressHelper.ParseHostNumber(address, i, j);
unchecked
{
numbers[index++] = (ushort)(number >> 16);
numbers[index++] = (ushort)number;
}
i = j;
//
// set this to avoid adding another number to
// the array if there's a prefix
//
number = 0;
numberIsValid = false;
break;
}
}
break;
case '/':
if (numberIsValid)
{
numbers[index++] = (ushort)number;
numberIsValid = false;
}
//
// since we have a valid IPv6 address string, the prefix
// length is the last token in the string
//
for (++i; address[i] != ']'; ++i)
{
PrefixLength = PrefixLength * 10 + (address[i] - '0');
}
break;
default:
number = number * 16 + Uri.FromHex(address[i++]);
break;
}
}
//
// add number to the array if its not the prefix length or part of
// an IPv4 address that's already been handled
//
if (numberIsValid)
{
numbers[index++] = (ushort)number;
}
//
// if we had a compressor sequence ("::") then we need to expand the
// numbers array
//
if (compressorIndex > 0)
{
int toIndex = NumberOfLabels - 1;
int fromIndex = index - 1;
for (int i = index - compressorIndex; i > 0; --i)
{
numbers[toIndex--] = numbers[fromIndex];
numbers[fromIndex--] = 0;
}
}
//
// is the address loopback? Loopback is defined as one of:
//
// 0:0:0:0:0:0:0:1
// 0:0:0:0:0:0:127.0.0.1 == 0:0:0:0:0:0:7F00:0001
// 0:0:0:0:0:FFFF:127.0.0.1 == 0:0:0:0:0:FFFF:7F00:0001
//
return ((numbers[0] == 0)
&& (numbers[1] == 0)
&& (numbers[2] == 0)
&& (numbers[3] == 0)
&& (numbers[4] == 0))
&& (((numbers[5] == 0)
&& (numbers[6] == 0)
&& (numbers[7] == 1))
|| (((numbers[6] == 0x7F00)
&& (numbers[7] == 0x0001))
&& ((numbers[5] == 0)
|| (numbers[5] == 0xFFFF))));
}
}
}