< 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>
 10613125        {
 10613126            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 27
 10613128            return typeof(TChar) == typeof(char)
 10613129                ? (char)(object)value
 10613130                : (byte)(object)value;
 10613131        }
 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>
 7436        {
 7437            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 38
 7439            Span<byte> numbers = stackalloc byte[NumberOfLabels];
 40
 74041            for (int i = 0; i < numbers.Length; ++i)
 29642            {
 29643                int b = 0;
 44                int ch;
 45
 114046                for (; (start < end) && (ch = ToUShort(str[start])) != '.' && ch != ':'; ++start)
 42247                {
 42248                    b = (b * 10) + ch - '0';
 42249                }
 50
 29651                numbers[i] = (byte)b;
 29652                ++start;
 29653            }
 54
 7455            return BinaryPrimitives.ReadInt32BigEndian(numbers);
 7456        }
 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>
 701102        {
 103            // IPv6 can only have canonical IPv4 embedded. Unknown schemes will not attempt parsing of non-canonical IPv
 701104            if (allowIPv6 || unknownScheme)
 701105            {
 701106                return IsValidCanonical(name, out end, allowIPv6, notImplicitFile);
 107            }
 108            else
 0109            {
 0110                return ParseNonCanonical(name, out end, notImplicitFile) != Invalid;
 111            }
 701112        }
 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>
 701129        {
 701130            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 131
 701132            end = 0; // Default value in case of failure
 701133            int dots = 0;
 701134            long number = 0;
 701135            bool haveNumber = false;
 701136            int start = 0;
 137
 47790138            while (start < name.Length)
 47678139            {
 47678140                int ch = ToUShort(name[start]);
 141
 47678142                if (allowIPv6)
 47678143                {
 144                    // For an IPv4 address nested inside an IPv6 address, the terminator is either the IPv6 address term
 47678145                    if (ch == ']' || ch == '/' || ch == '%')
 121146                    {
 121147                        break;
 148                    }
 47557149                }
 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
 47557159                uint parsedCharacter = (uint)(ch - '0');
 160
 47557161                if (parsedCharacter < IPv4AddressHelper.Decimal)
 28564162                {
 28564163                    if (!haveNumber && parsedCharacter == 0 &&
 28564164                        (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
 28558170                    haveNumber = true;
 28558171                    number = number * IPv4AddressHelper.Decimal + parsedCharacter;
 28558172                    if (number > byte.MaxValue)
 90173                    {
 90174                        return false;
 175                    }
 28468176                }
 18993177                else if (ch == '.')
 18731178                {
 179                    // If the current character is not an integer, it may be the IPv4 component separator ('.')
 180
 18731181                    if (!haveNumber)
 110182                    {
 110183                        return false;
 184                    }
 185
 18621186                    ++dots;
 18621187                    haveNumber = false;
 18621188                    number = 0;
 18621189                }
 190                else
 262191                {
 262192                    return false;
 193                }
 47089194                ++start;
 47089195            }
 233196            bool res = (dots == 3) && haveNumber;
 233197            end = res ? start : 0;
 233198            return res;
 701199        }
 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>
 2775207        {
 2775208            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 209
 2775210            end = 0; // Default value in case of failure
 211            int numberBase;
 2775212            int ch = 0;
 2775213            Span<long> parts = [0, 0, 0]; // One part per octet. Final octet doesn't have a terminator, so is stored in 
 2775214            long currentValue = 0;
 2775215            bool atLeastOneChar = false;
 216
 217            // Parse one dotted section at a time
 2775218            int dotCount = 0; // Limit 3
 2775219            int current = 0;
 220
 4509221            for (; current < name.Length; current++)
 3620222            {
 3620223                ch = ToUShort(name[current]);
 3620224                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
 3620229                numberBase = IPv4AddressHelper.Decimal;
 230
 3620231                if (ch == '0')
 842232                {
 842233                    current++;
 842234                    atLeastOneChar = true;
 842235                    if (current < name.Length)
 775236                    {
 775237                        ch = ToUShort(name[current]);
 238
 775239                        if (ch == 'x' || ch == 'X')
 94240                        {
 94241                            numberBase = IPv4AddressHelper.Hex;
 242
 94243                            current++;
 94244                            atLeastOneChar = false;
 94245                        }
 246                        else
 681247                        {
 681248                            numberBase = IPv4AddressHelper.Octal;
 681249                        }
 775250                    }
 842251                }
 252
 253                // Parse this section
 26992254                for (; current < name.Length; current++)
 14751255                {
 14751256                    ch = ToUShort(name[current]);
 14751257                    int digitValue = HexConverter.FromChar(ch);
 258
 14751259                    if (digitValue >= numberBase)
 3059260                    {
 3059261                        break; // Invalid/terminator
 262                    }
 11692263                    currentValue = (currentValue * numberBase) + digitValue;
 264
 11692265                    if (currentValue > MaxIPv4Value) // Overflow
 6266                    {
 6267                        return Invalid;
 268                    }
 269
 11686270                    atLeastOneChar = true;
 11686271                }
 272
 3614273                if (current < name.Length && ch == '.')
 893274                {
 893275                    if (dotCount >= 3 // Max of 3 dots and 4 segments
 893276                        || !atLeastOneChar // No empty segments: 1...1
 893277                                           // Only the last segment can be more than 255 (if there are less than 3 dots)
 893278                        || currentValue > 0xFF)
 26279                    {
 26280                        return Invalid;
 281                    }
 867282                    parts[dotCount] = currentValue;
 867283                    dotCount++;
 867284                    atLeastOneChar = false;
 867285                    continue;
 286                }
 287                // We don't get here unless we find an invalid character or a terminator
 0288                break;
 289            }
 290
 291            // Terminators
 2743292            if (!atLeastOneChar)
 2078293            {
 2078294                return Invalid;  // Empty trailing segment: 1.1.1.
 295            }
 665296            else if (current >= name.Length)
 553297            {
 298                // end of string, allowed
 553299                end = name.Length;
 553300            }
 112301            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
 42308            {
 309                // not a valid terminating character
 42310                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            }
 2775345        }
 346    }
 347}