< Summary

Line coverage
95%
Covered lines: 163
Uncovered lines: 7
Coverable lines: 170
Total lines: 348
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)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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>
 15460525        {
 15460526            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 27
 15460528            return typeof(TChar) == typeof(char)
 15460529                ? (char)(object)value
 15460530                : (byte)(object)value;
 15460531        }
 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>
 20236        {
 20237            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 38
 20239            Span<byte> numbers = stackalloc byte[NumberOfLabels];
 40
 202041            for (int i = 0; i < numbers.Length; ++i)
 80842            {
 80843                int b = 0;
 44                int ch;
 45
 284046                for (; (start < end) && (ch = ToUShort(str[start])) != '.' && ch != ':'; ++start)
 101647                {
 101648                    b = (b * 10) + ch - '0';
 101649                }
 50
 80851                numbers[i] = (byte)b;
 80852                ++start;
 80853            }
 54
 20255            return BinaryPrimitives.ReadInt32BigEndian(numbers);
 20256        }
 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>
 1291102        {
 103            // IPv6 can only have canonical IPv4 embedded. Unknown schemes will not attempt parsing of non-canonical IPv
 1291104            if (allowIPv6 || unknownScheme)
 1291105            {
 1291106                return IsValidCanonical(name, out end, allowIPv6, notImplicitFile);
 107            }
 108            else
 0109            {
 0110                return ParseNonCanonical(name, out end, notImplicitFile) != Invalid;
 111            }
 1291112        }
 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>
 1291129        {
 1291130            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 131
 1291132            end = 0; // Default value in case of failure
 1291133            int dots = 0;
 1291134            long number = 0;
 1291135            bool haveNumber = false;
 1291136            int start = 0;
 137
 76019138            while (start < name.Length)
 75685139            {
 75685140                int ch = ToUShort(name[start]);
 141
 75685142                if (allowIPv6)
 75685143                {
 144                    // For an IPv4 address nested inside an IPv6 address, the terminator is either the IPv6 address term
 75685145                    if (ch == ']' || ch == '/' || ch == '%')
 193146                    {
 193147                        break;
 148                    }
 75492149                }
 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
 75492159                uint parsedCharacter = (uint)(ch - '0');
 160
 75492161                if (parsedCharacter < IPv4AddressHelper.Decimal)
 50025162                {
 50025163                    if (!haveNumber && parsedCharacter == 0 &&
 50025164                        (uint)(start + 1) < (uint)name.Length && char.IsAsciiDigit((char)ToUShort(name[start + 1])))
 142165                    {
 166                        // Octal is not allowed in canonical format.
 142167                        return false;
 168                    }
 169
 49883170                    haveNumber = true;
 49883171                    number = number * IPv4AddressHelper.Decimal + parsedCharacter;
 49883172                    if (number > byte.MaxValue)
 214173                    {
 214174                        return false;
 175                    }
 49669176                }
 25467177                else if (ch == '.')
 25141178                {
 179                    // If the current character is not an integer, it may be the IPv4 component separator ('.')
 180
 25141181                    if (!haveNumber)
 82182                    {
 82183                        return false;
 184                    }
 185
 25059186                    ++dots;
 25059187                    haveNumber = false;
 25059188                    number = 0;
 25059189                }
 190                else
 326191                {
 326192                    return false;
 193                }
 74728194                ++start;
 74728195            }
 527196            bool res = (dots == 3) && haveNumber;
 527197            end = res ? start : 0;
 527198            return res;
 1291199        }
 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>
 3483207        {
 3483208            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 209
 3483210            end = 0; // Default value in case of failure
 211            int numberBase;
 3483212            int ch = 0;
 3483213            Span<long> parts = [0, 0, 0]; // One part per octet. Final octet doesn't have a terminator, so is stored in 
 3483214            long currentValue = 0;
 3483215            bool atLeastOneChar = false;
 216
 217            // Parse one dotted section at a time
 3483218            int dotCount = 0; // Limit 3
 3483219            int current = 0;
 220
 5329221            for (; current < name.Length; current++)
 4392222            {
 4392223                ch = ToUShort(name[current]);
 4392224                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
 4392229                numberBase = IPv4AddressHelper.Decimal;
 230
 4392231                if (ch == '0')
 953232                {
 953233                    current++;
 953234                    atLeastOneChar = true;
 953235                    if (current < name.Length)
 854236                    {
 854237                        ch = ToUShort(name[current]);
 238
 854239                        if (ch == 'x' || ch == 'X')
 50240                        {
 50241                            numberBase = IPv4AddressHelper.Hex;
 242
 50243                            current++;
 50244                            atLeastOneChar = false;
 50245                        }
 246                        else
 804247                        {
 804248                            numberBase = IPv4AddressHelper.Octal;
 804249                        }
 854250                    }
 953251                }
 252
 253                // Parse this section
 33596254                for (; current < name.Length; current++)
 18351255                {
 18351256                    ch = ToUShort(name[current]);
 18351257                    int digitValue = HexConverter.FromChar(ch);
 258
 18351259                    if (digitValue >= numberBase)
 3743260                    {
 3743261                        break; // Invalid/terminator
 262                    }
 14608263                    currentValue = (currentValue * numberBase) + digitValue;
 264
 14608265                    if (currentValue > MaxIPv4Value) // Overflow
 6266                    {
 6267                        return Invalid;
 268                    }
 269
 14602270                    atLeastOneChar = true;
 14602271                }
 272
 4386273                if (current < name.Length && ch == '.')
 943274                {
 943275                    if (dotCount >= 3 // Max of 3 dots and 4 segments
 943276                        || !atLeastOneChar // No empty segments: 1...1
 943277                                           // Only the last segment can be more than 255 (if there are less than 3 dots)
 943278                        || currentValue > 0xFF)
 20279                    {
 20280                        return Invalid;
 281                    }
 923282                    parts[dotCount] = currentValue;
 923283                    dotCount++;
 923284                    atLeastOneChar = false;
 923285                    continue;
 286                }
 287                // We don't get here unless we find an invalid character or a terminator
 0288                break;
 289            }
 290
 291            // Terminators
 3457292            if (!atLeastOneChar)
 2686293            {
 2686294                return Invalid;  // Empty trailing segment: 1.1.1.
 295            }
 771296            else if (current >= name.Length)
 641297            {
 298                // end of string, allowed
 641299                end = name.Length;
 641300            }
 130301            else if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
 84302            {
 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
 84305                end = current;
 84306            }
 307            else
 46308            {
 309                // not a valid terminating character
 46310                return Invalid;
 311            }
 312
 313            // Parsed, reassemble and check for overflows in the last part. Previous parts have already been checked in 
 725314            switch (dotCount)
 315            {
 316                case 0: // 0xFFFFFFFF
 376317                    return currentValue;
 318                case 1: // 0xFF.0xFFFFFF
 62319                    Debug.Assert(parts[0] <= 0xFF);
 62320                    if (currentValue > 0xffffff)
 6321                    {
 6322                        return Invalid;
 323                    }
 56324                    return (parts[0] << 24) | currentValue;
 325                case 2: // 0xFF.0xFF.0xFFFF
 46326                    Debug.Assert(parts[0] <= 0xFF);
 46327                    Debug.Assert(parts[1] <= 0xFF);
 46328                    if (currentValue > 0xffff)
 10329                    {
 10330                        return Invalid;
 331                    }
 36332                    return (parts[0] << 24) | (parts[1] << 16) | currentValue;
 333                case 3: // 0xFF.0xFF.0xFF.0xFF
 241334                    Debug.Assert(parts[0] <= 0xFF);
 241335                    Debug.Assert(parts[1] <= 0xFF);
 241336                    Debug.Assert(parts[2] <= 0xFF);
 241337                    if (currentValue > 0xff)
 14338                    {
 14339                        return Invalid;
 340                    }
 227341                    return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | currentValue;
 342                default:
 0343                    return Invalid;
 344            }
 3483345        }
 346    }
 347}
 348