| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- using System.Diagnostics;
- using System.IO;
- using System.Net.Sockets;
- using System.Runtime.InteropServices;
- using System.Text;
- namespace System.Net
- {
- internal class IPAddressParser
- {
- private const int MaxIPv4StringLength = 15; // 4 numbers separated by 3 periods, with up to 3 digits per number
- internal static unsafe IPAddress Parse(ReadOnlySpan<char> ipSpan, bool tryParse)
- {
- if (ipSpan.Contains(':'))
- {
- // The address is parsed as IPv6 if and only if it contains a colon. This is valid because
- // we don't support/parse a port specification at the end of an IPv4 address.
- ushort* numbers = stackalloc ushort[IPAddressParserStatics.IPv6AddressShorts];
- new Span<ushort>(numbers, IPAddressParserStatics.IPv6AddressShorts).Clear();
- if (Ipv6StringToAddress(ipSpan, numbers, IPAddressParserStatics.IPv6AddressShorts, out uint scope))
- {
- return new IPAddress(numbers, IPAddressParserStatics.IPv6AddressShorts, scope);
- }
- }
- else if (Ipv4StringToAddress(ipSpan, out long address))
- {
- return new IPAddress(address);
- }
- if (tryParse)
- {
- return null;
- }
- throw new FormatException(SR.dns_bad_ip_address, new SocketException(SocketError.InvalidArgument));
- }
- internal static unsafe string IPv4AddressToString(uint address)
- {
- char* addressString = stackalloc char[MaxIPv4StringLength];
- int charsWritten = IPv4AddressToStringHelper(address, addressString);
- return new string(addressString, 0, charsWritten);
- }
- internal static unsafe void IPv4AddressToString(uint address, StringBuilder destination)
- {
- char* addressString = stackalloc char[MaxIPv4StringLength];
- int charsWritten = IPv4AddressToStringHelper(address, addressString);
- destination.Append(addressString, charsWritten);
- }
- internal static unsafe bool IPv4AddressToString(uint address, Span<char> formatted, out int charsWritten)
- {
- if (formatted.Length < MaxIPv4StringLength)
- {
- charsWritten = 0;
- return false;
- }
- fixed (char* formattedPtr = &MemoryMarshal.GetReference(formatted))
- {
- charsWritten = IPv4AddressToStringHelper(address, formattedPtr);
- }
- return true;
- }
- private static unsafe int IPv4AddressToStringHelper(uint address, char* addressString)
- {
- int offset = 0;
- FormatIPv4AddressNumber((int)(address & 0xFF), addressString, ref offset);
- addressString[offset++] = '.';
- FormatIPv4AddressNumber((int)((address >> 8) & 0xFF), addressString, ref offset);
- addressString[offset++] = '.';
- FormatIPv4AddressNumber((int)((address >> 16) & 0xFF), addressString, ref offset);
- addressString[offset++] = '.';
- FormatIPv4AddressNumber((int)((address >> 24) & 0xFF), addressString, ref offset);
- return offset;
- }
- internal static string IPv6AddressToString(ushort[] address, uint scopeId)
- {
- Debug.Assert(address != null);
- Debug.Assert(address.Length == IPAddressParserStatics.IPv6AddressShorts);
- StringBuilder buffer = IPv6AddressToStringHelper(address, scopeId);
- return StringBuilderCache.GetStringAndRelease(buffer);
- }
- internal static bool IPv6AddressToString(ushort[] address, uint scopeId, Span<char> destination, out int charsWritten)
- {
- Debug.Assert(address != null);
- Debug.Assert(address.Length == IPAddressParserStatics.IPv6AddressShorts);
- StringBuilder buffer = IPv6AddressToStringHelper(address, scopeId);
- if (destination.Length < buffer.Length)
- {
- StringBuilderCache.Release(buffer);
- charsWritten = 0;
- return false;
- }
- buffer.CopyTo(0, destination, buffer.Length);
- charsWritten = buffer.Length;
- StringBuilderCache.Release(buffer);
- return true;
- }
- internal static StringBuilder IPv6AddressToStringHelper(ushort[] address, uint scopeId)
- {
- const int INET6_ADDRSTRLEN = 65;
- StringBuilder buffer = StringBuilderCache.Acquire(INET6_ADDRSTRLEN);
- if (IPv6AddressHelper.ShouldHaveIpv4Embedded(address))
- {
- // We need to treat the last 2 ushorts as a 4-byte IPv4 address,
- // so output the first 6 ushorts normally, followed by the IPv4 address.
- AppendSections(address, 0, 6, buffer);
- if (buffer[buffer.Length - 1] != ':')
- {
- buffer.Append(':');
- }
- IPv4AddressToString(ExtractIPv4Address(address), buffer);
- }
- else
- {
- // No IPv4 address. Output all 8 sections as part of the IPv6 address
- // with normal formatting rules.
- AppendSections(address, 0, 8, buffer);
- }
- // If there's a scope ID, append it.
- if (scopeId != 0)
- {
- buffer.Append('%').Append(scopeId);
- }
- return buffer;
- }
- private static unsafe void FormatIPv4AddressNumber(int number, char* addressString, ref int offset)
- {
- // Math.DivRem has no overload for byte, assert here for safety
- Debug.Assert(number < 256);
- offset += number > 99 ? 3 : number > 9 ? 2 : 1;
- int i = offset;
- do
- {
- number = Math.DivRem(number, 10, out int rem);
- addressString[--i] = (char)('0' + rem);
- } while (number != 0);
- }
- public static unsafe bool Ipv4StringToAddress(ReadOnlySpan<char> ipSpan, out long address)
- {
- int end = ipSpan.Length;
- long tmpAddr;
- fixed (char* ipStringPtr = &MemoryMarshal.GetReference(ipSpan))
- {
- tmpAddr = IPv4AddressHelper.ParseNonCanonical(ipStringPtr, 0, ref end, notImplicitFile: true);
- }
- if (tmpAddr != IPv4AddressHelper.Invalid && end == ipSpan.Length)
- {
- // IPv4AddressHelper.ParseNonCanonical returns the bytes in the inverse order.
- // Reverse them and return success.
- address =
- ((0xFF000000 & tmpAddr) >> 24) |
- ((0x00FF0000 & tmpAddr) >> 8) |
- ((0x0000FF00 & tmpAddr) << 8) |
- ((0x000000FF & tmpAddr) << 24);
- return true;
- }
- else
- {
- // Failed to parse the address.
- address = 0;
- return false;
- }
- }
- public static unsafe bool Ipv6StringToAddress(ReadOnlySpan<char> ipSpan, ushort* numbers, int numbersLength, out uint scope)
- {
- Debug.Assert(numbers != null);
- Debug.Assert(numbersLength >= IPAddressParserStatics.IPv6AddressShorts);
- int end = ipSpan.Length;
- bool isValid = false;
- fixed (char* ipStringPtr = &MemoryMarshal.GetReference(ipSpan))
- {
- isValid = IPv6AddressHelper.IsValidStrict(ipStringPtr, 0, ref end);
- }
- if (isValid || (end != ipSpan.Length))
- {
- string scopeId = null;
- IPv6AddressHelper.Parse(ipSpan, numbers, 0, ref scopeId);
- long result = 0;
- if (!string.IsNullOrEmpty(scopeId))
- {
- if (scopeId.Length < 2)
- {
- scope = 0;
- return false;
- }
- for (int i = 1; i < scopeId.Length; i++)
- {
- char c = scopeId[i];
- if (c < '0' || c > '9')
- {
- scope = 0;
- #if MONO // zoneId can be a string, see https://github.com/dotnet/corefx/issues/27529
- return true;
- #else
- return false;
- #endif
- }
- result = (result * 10) + (c - '0');
- if (result > uint.MaxValue)
- {
- scope = 0;
- return false;
- }
- }
- }
- scope = (uint)result;
- return true;
- }
- scope = 0;
- return false;
- }
- /// <summary>
- /// Appends each of the numbers in address in indexed range [fromInclusive, toExclusive),
- /// while also replacing the longest sequence of 0s found in that range with "::", as long
- /// as the sequence is more than one 0.
- /// </summary>
- private static void AppendSections(ushort[] address, int fromInclusive, int toExclusive, StringBuilder buffer)
- {
- // Find the longest sequence of zeros to be combined into a "::"
- ReadOnlySpan<ushort> addressSpan = new ReadOnlySpan<ushort>(address, fromInclusive, toExclusive - fromInclusive);
- (int zeroStart, int zeroEnd) = IPv6AddressHelper.FindCompressionRange(addressSpan);
- bool needsColon = false;
- // Output all of the numbers before the zero sequence
- for (int i = fromInclusive; i < zeroStart; i++)
- {
- if (needsColon)
- {
- buffer.Append(':');
- }
- needsColon = true;
- AppendHex(address[i], buffer);
- }
- // Output the zero sequence if there is one
- if (zeroStart >= 0)
- {
- buffer.Append("::");
- needsColon = false;
- fromInclusive = zeroEnd;
- }
- // Output everything after the zero sequence
- for (int i = fromInclusive; i < toExclusive; i++)
- {
- if (needsColon)
- {
- buffer.Append(':');
- }
- needsColon = true;
- AppendHex(address[i], buffer);
- }
- }
- /// <summary>Appends a number as hexadecimal (without the leading "0x") to the StringBuilder.</summary>
- private static unsafe void AppendHex(ushort value, StringBuilder buffer)
- {
- const int MaxLength = sizeof(ushort) * 2; // two hex chars per byte
- char* chars = stackalloc char[MaxLength];
- int pos = MaxLength;
- do
- {
- int rem = value % 16;
- value /= 16;
- chars[--pos] = rem < 10 ? (char)('0' + rem) : (char)('a' + (rem - 10));
- Debug.Assert(pos >= 0);
- }
- while (value != 0);
- buffer.Append(chars + pos, MaxLength - pos);
- }
- /// <summary>Extracts the IPv4 address from the end of the IPv6 address byte array.</summary>
- private static uint ExtractIPv4Address(ushort[] address) => (uint)(Reverse(address[7]) << 16) | Reverse(address[6]);
- /// <summary>Reverses the two bytes in the ushort.</summary>
- private static ushort Reverse(ushort number) => (ushort)(((number >> 8) & 0xFF) | ((number << 8) & 0xFF00));
- }
- }
|