< Summary

Information
Class: System.Net.IPv4AddressHelper
Assembly: System.Net.Primitives
File(s): D:\runner\runtime\src\libraries\Common\src\System\Net\IPv4AddressHelper.Common.cs
Line coverage
95%
Covered lines: 163
Uncovered lines: 7
Coverable lines: 170
Total lines: 347
Line coverage: 95.8%
Branch coverage
86%
Covered branches: 98
Total branches: 113
Branch coverage: 86.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
ToUShort(...)100%44100%
ParseHostNumber(...)100%1010100%
IsValid(...)50%2271.42%
IsValidCanonical(...)71.42%424293.87%
ParseNonCanonical(...)96.36%555597.84%

File(s)

D:\runner\runtime\src\libraries\Common\src\System\Net\IPv4AddressHelper.Common.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Diagnostics;
 6using System.Numerics;
 7using System.Runtime.CompilerServices;
 8
 9namespace System.Net
 10{
 11    internal static partial class IPv4AddressHelper
 12    {
 13        internal const long Invalid = -1;
 14        private const long MaxIPv4Value = uint.MaxValue; // the native parser cannot handle MaxIPv4Value, only MaxIPv4Va
 15
 16        private const int Octal = 8;
 17        private const int Decimal = 10;
 18        private const int Hex = 16;
 19
 20        private const int NumberOfLabels = 4;
 21
 22        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 23        internal static ushort ToUShort<TChar>(TChar value)
 24            where TChar : unmanaged, IBinaryInteger<TChar>
 11133225        {
 11133226            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 27
 11133228            return typeof(TChar) == typeof(char)
 11133229                ? (char)(object)value
 11133230                : (byte)(object)value;
 11133231        }
 32
 33        // Only called from the IPv6Helper, only parse the canonical format
 34        internal static unsafe int ParseHostNumber<TChar>(ReadOnlySpan<TChar> str, int start, int end)
 35            where TChar : unmanaged, IBinaryInteger<TChar>
 7836        {
 7837            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 38
 7839            Span<byte> numbers = stackalloc byte[NumberOfLabels];
 40
 78041            for (int i = 0; i < numbers.Length; ++i)
 31242            {
 31243                int b = 0;
 44                int ch;
 45
 122046                for (; (start < end) && (ch = ToUShort(str[start])) != '.' && ch != ':'; ++start)
 45447                {
 45448                    b = (b * 10) + ch - '0';
 45449                }
 50
 31251                numbers[i] = (byte)b;
 31252                ++start;
 31253            }
 54
 7855            return BinaryPrimitives.ReadInt32BigEndian(numbers);
 7856        }
 57
 58        //
 59        // IsValid
 60        //
 61        //  Performs IsValid on a substring. Updates the index to where we
 62        //  believe the IPv4 address ends
 63        //
 64        // Inputs:
 65        //  <argument>  name
 66        //      string containing possible IPv4 address
 67        //
 68        //  <argument>  start
 69        //      offset in <name> to start checking for IPv4 address
 70        //
 71        //  <argument>  end
 72        //      offset in <name> of the last character we can touch in the check
 73        //
 74        // Outputs:
 75        //  <argument>  end
 76        //      index of last character in <name> we checked
 77        //
 78        //  <argument>  allowIPv6
 79        //      enables parsing IPv4 addresses embedded in IPv6 address literals
 80        //
 81        //  <argument>  notImplicitFile
 82        //      do not consider this URI holding an implicit filename
 83        //
 84        //  <argument>  unknownScheme
 85        //      the check is made on an unknown scheme (suppress IPv4 canonicalization)
 86        //
 87        // Assumes:
 88        // The address string is terminated by either
 89        // end of the string, characters ':' '/' '\' '?'
 90        //
 91        //
 92        // Returns:
 93        //  bool
 94        //
 95        // Throws:
 96        //  Nothing
 97        //
 98
 99        //Remark: MUST NOT be used unless all input indexes are verified and trusted.
 100        internal static bool IsValid<TChar>(ReadOnlySpan<TChar> name, out int end, bool allowIPv6, bool notImplicitFile,
 101            where TChar : unmanaged, IBinaryInteger<TChar>
 741102        {
 103            // IPv6 can only have canonical IPv4 embedded. Unknown schemes will not attempt parsing of non-canonical IPv
 741104            if (allowIPv6 || unknownScheme)
 741105            {
 741106                return IsValidCanonical(name, out end, allowIPv6, notImplicitFile);
 107            }
 108            else
 0109            {
 0110                return ParseNonCanonical(name, out end, notImplicitFile) != Invalid;
 111            }
 741112        }
 113
 114        //
 115        // IsValidCanonical
 116        //
 117        //  Checks if the substring is a valid canonical IPv4 address or an IPv4 address embedded in an IPv6 literal
 118        //  This is an attempt to parse ABNF productions from RFC3986, Section 3.2.2:
 119        //     IP-literal = "[" ( IPv6address / IPvFuture  ) "]"
 120        //     IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
 121        //     dec-octet   = DIGIT                 ; 0-9
 122        //                 / %x31-39 DIGIT         ; 10-99
 123        //                 / "1" 2DIGIT            ; 100-199
 124        //                 / "2" %x30-34 DIGIT     ; 200-249
 125        //                 / "25" %x30-35          ; 250-255
 126        //
 127        internal static bool IsValidCanonical<TChar>(ReadOnlySpan<TChar> name, out int end, bool allowIPv6, bool notImpl
 128            where TChar : unmanaged, IBinaryInteger<TChar>
 741129        {
 741130            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 131
 741132            end = 0; // Default value in case of failure
 741133            int dots = 0;
 741134            long number = 0;
 741135            bool haveNumber = false;
 741136            int start = 0;
 137
 50838138            while (start < name.Length)
 50714139            {
 50714140                int ch = ToUShort(name[start]);
 141
 50714142                if (allowIPv6)
 50714143                {
 144                    // For an IPv4 address nested inside an IPv6 address, the terminator is either the IPv6 address term
 50714145                    if (ch == ']' || ch == '/' || ch == '%')
 121146                    {
 121147                        break;
 148                    }
 50593149                }
 0150                else if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
 0151                {
 152                    // For a normal IPv4 address, the terminator is the prefix ('/' or its counterpart, '\'). If notImpl
 153                    // is one of the characters which signify the start of the rest of the URI - the port number (':'), 
 154
 0155                    break;
 156                }
 157
 158                // An explicit cast to an unsigned integer forces character values preceding '0' to underflow, eliminati
 50593159                uint parsedCharacter = (uint)(ch - '0');
 160
 50593161                if (parsedCharacter < IPv4AddressHelper.Decimal)
 30436162                {
 30436163                    if (!haveNumber && parsedCharacter == 0 &&
 30436164                        (uint)(start + 1) < (uint)name.Length && char.IsAsciiDigit((char)ToUShort(name[start + 1])))
 6165                    {
 166                        // Octal is not allowed in canonical format.
 6167                        return false;
 168                    }
 169
 30430170                    haveNumber = true;
 30430171                    number = number * IPv4AddressHelper.Decimal + parsedCharacter;
 30430172                    if (number > byte.MaxValue)
 96173                    {
 96174                        return false;
 175                    }
 30334176                }
 20157177                else if (ch == '.')
 19877178                {
 179                    // If the current character is not an integer, it may be the IPv4 component separator ('.')
 180
 19877181                    if (!haveNumber)
 114182                    {
 114183                        return false;
 184                    }
 185
 19763186                    ++dots;
 19763187                    haveNumber = false;
 19763188                    number = 0;
 19763189                }
 190                else
 280191                {
 280192                    return false;
 193                }
 50097194                ++start;
 50097195            }
 245196            bool res = (dots == 3) && haveNumber;
 245197            end = res ? start : 0;
 245198            return res;
 741199        }
 200
 201        // Parse any canonical or noncanonical IPv4 formats and return a long between 0 and MaxIPv4Value.
 202        // Return Invalid (-1) for failures.
 203        // If the address has less than three dots, only the rightmost section is assumed to contain the combined value 
 204        // the missing sections: 0xFF00FFFF == 0xFF.0x00.0xFF.0xFF == 0xFF.0xFFFF
 205        internal static long ParseNonCanonical<TChar>(ReadOnlySpan<TChar> name, out int end, bool notImplicitFile)
 206            where TChar : unmanaged, IBinaryInteger<TChar>
 2861207        {
 2861208            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 209
 2861210            end = 0; // Default value in case of failure
 211            int numberBase;
 2861212            int ch = 0;
 2861213            Span<long> parts = [0, 0, 0]; // One part per octet. Final octet doesn't have a terminator, so is stored in 
 2861214            long currentValue = 0;
 2861215            bool atLeastOneChar = false;
 216
 217            // Parse one dotted section at a time
 2861218            int dotCount = 0; // Limit 3
 2861219            int current = 0;
 220
 4599221            for (; current < name.Length; current++)
 3708222            {
 3708223                ch = ToUShort(name[current]);
 3708224                currentValue = 0;
 225
 226                // Figure out what base this section is in, default to base 10.
 227                // A number starting with zero should be interpreted in base 8 / octal
 228                // If the number starts with 0x, it should be interpreted in base 16 / hex
 3708229                numberBase = IPv4AddressHelper.Decimal;
 230
 3708231                if (ch == '0')
 846232                {
 846233                    current++;
 846234                    atLeastOneChar = true;
 846235                    if (current < name.Length)
 779236                    {
 779237                        ch = ToUShort(name[current]);
 238
 779239                        if (ch == 'x' || ch == 'X')
 94240                        {
 94241                            numberBase = IPv4AddressHelper.Hex;
 242
 94243                            current++;
 94244                            atLeastOneChar = false;
 94245                        }
 246                        else
 685247                        {
 685248                            numberBase = IPv4AddressHelper.Octal;
 685249                        }
 779250                    }
 846251                }
 252
 253                // Parse this section
 27144254                for (; current < name.Length; current++)
 14871255                {
 14871256                    ch = ToUShort(name[current]);
 14871257                    int digitValue = HexConverter.FromChar(ch);
 258
 14871259                    if (digitValue >= numberBase)
 3147260                    {
 3147261                        break; // Invalid/terminator
 262                    }
 11724263                    currentValue = (currentValue * numberBase) + digitValue;
 264
 11724265                    if (currentValue > MaxIPv4Value) // Overflow
 6266                    {
 6267                        return Invalid;
 268                    }
 269
 11718270                    atLeastOneChar = true;
 11718271                }
 272
 3702273                if (current < name.Length && ch == '.')
 895274                {
 895275                    if (dotCount >= 3 // Max of 3 dots and 4 segments
 895276                        || !atLeastOneChar // No empty segments: 1...1
 895277                                           // Only the last segment can be more than 255 (if there are less than 3 dots)
 895278                        || currentValue > 0xFF)
 26279                    {
 26280                        return Invalid;
 281                    }
 869282                    parts[dotCount] = currentValue;
 869283                    dotCount++;
 869284                    atLeastOneChar = false;
 869285                    continue;
 286                }
 287                // We don't get here unless we find an invalid character or a terminator
 0288                break;
 289            }
 290
 291            // Terminators
 2829292            if (!atLeastOneChar)
 2162293            {
 2162294                return Invalid;  // Empty trailing segment: 1.1.1.
 295            }
 667296            else if (current >= name.Length)
 553297            {
 298                // end of string, allowed
 553299                end = name.Length;
 553300            }
 114301            else if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
 70302            {
 303                // For a normal IPv4 address, the terminator is the prefix ('/' or its counterpart, '\'). If notImplicit
 304                // is one of the characters which signify the start of the rest of the URI - the port number (':'), quer
 70305                end = current;
 70306            }
 307            else
 44308            {
 309                // not a valid terminating character
 44310                return Invalid;
 311            }
 312
 313            // Parsed, reassemble and check for overflows in the last part. Previous parts have already been checked in 
 623314            switch (dotCount)
 315            {
 316                case 0: // 0xFFFFFFFF
 276317                    return currentValue;
 318                case 1: // 0xFF.0xFFFFFF
 92319                    Debug.Assert(parts[0] <= 0xFF);
 92320                    if (currentValue > 0xffffff)
 16321                    {
 16322                        return Invalid;
 323                    }
 76324                    return (parts[0] << 24) | currentValue;
 325                case 2: // 0xFF.0xFF.0xFFFF
 58326                    Debug.Assert(parts[0] <= 0xFF);
 58327                    Debug.Assert(parts[1] <= 0xFF);
 58328                    if (currentValue > 0xffff)
 8329                    {
 8330                        return Invalid;
 331                    }
 50332                    return (parts[0] << 24) | (parts[1] << 16) | currentValue;
 333                case 3: // 0xFF.0xFF.0xFF.0xFF
 197334                    Debug.Assert(parts[0] <= 0xFF);
 197335                    Debug.Assert(parts[1] <= 0xFF);
 197336                    Debug.Assert(parts[2] <= 0xFF);
 197337                    if (currentValue > 0xff)
 12338                    {
 12339                        return Invalid;
 340                    }
 185341                    return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | currentValue;
 342                default:
 0343                    return Invalid;
 344            }
 2861345        }
 346    }
 347}