< Summary

Line coverage
65%
Covered lines: 128
Uncovered lines: 66
Coverable lines: 194
Total lines: 392
Line coverage: 65.9%
Branch coverage
54%
Covered branches: 64
Total branches: 117
Branch coverage: 54.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: ToUShort(...)50%44100%
File 1: ParseHostNumber(...)0%10100%
File 1: IsValid(...)100%22100%
File 1: IsValidCanonical(...)57.14%424277.55%
File 1: ParseNonCanonical(...)65.45%555582.79%
File 2: ParseCanonicalName(...)0%440%

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>
 723625        {
 723626            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 27
 723628            return typeof(TChar) == typeof(char)
 723629                ? (char)(object)value
 723630                : (byte)(object)value;
 723631        }
 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>
 036        {
 037            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 38
 039            Span<byte> numbers = stackalloc byte[NumberOfLabels];
 40
 041            for (int i = 0; i < numbers.Length; ++i)
 042            {
 043                int b = 0;
 44                int ch;
 45
 046                for (; (start < end) && (ch = ToUShort(str[start])) != '.' && ch != ':'; ++start)
 047                {
 048                    b = (b * 10) + ch - '0';
 049                }
 50
 051                numbers[i] = (byte)b;
 052                ++start;
 053            }
 54
 055            return BinaryPrimitives.ReadInt32BigEndian(numbers);
 056        }
 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>
 288102        {
 103            // IPv6 can only have canonical IPv4 embedded. Unknown schemes will not attempt parsing of non-canonical IPv
 288104            if (allowIPv6 || unknownScheme)
 78105            {
 78106                return IsValidCanonical(name, out end, allowIPv6, notImplicitFile);
 107            }
 108            else
 210109            {
 210110                return ParseNonCanonical(name, out end, notImplicitFile) != Invalid;
 111            }
 288112        }
 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>
 78129        {
 78130            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 131
 78132            end = 0; // Default value in case of failure
 78133            int dots = 0;
 78134            long number = 0;
 78135            bool haveNumber = false;
 78136            int start = 0;
 137
 766138            while (start < name.Length)
 766139            {
 766140                int ch = ToUShort(name[start]);
 141
 766142                if (allowIPv6)
 766143                {
 144                    // For an IPv4 address nested inside an IPv6 address, the terminator is either the IPv6 address term
 766145                    if (ch == ']' || ch == '/' || ch == '%')
 78146                    {
 78147                        break;
 148                    }
 688149                }
 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
 688159                uint parsedCharacter = (uint)(ch - '0');
 160
 688161                if (parsedCharacter < IPv4AddressHelper.Decimal)
 454162                {
 454163                    if (!haveNumber && parsedCharacter == 0 &&
 454164                        (uint)(start + 1) < (uint)name.Length && char.IsAsciiDigit((char)ToUShort(name[start + 1])))
 0165                    {
 166                        // Octal is not allowed in canonical format.
 0167                        return false;
 168                    }
 169
 454170                    haveNumber = true;
 454171                    number = number * IPv4AddressHelper.Decimal + parsedCharacter;
 454172                    if (number > byte.MaxValue)
 0173                    {
 0174                        return false;
 175                    }
 454176                }
 234177                else if (ch == '.')
 234178                {
 179                    // If the current character is not an integer, it may be the IPv4 component separator ('.')
 180
 234181                    if (!haveNumber)
 0182                    {
 0183                        return false;
 184                    }
 185
 234186                    ++dots;
 234187                    haveNumber = false;
 234188                    number = 0;
 234189                }
 190                else
 0191                {
 0192                    return false;
 193                }
 688194                ++start;
 688195            }
 78196            bool res = (dots == 3) && haveNumber;
 78197            end = res ? start : 0;
 78198            return res;
 78199        }
 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>
 210207        {
 210208            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 209
 210210            end = 0; // Default value in case of failure
 211            int numberBase;
 210212            int ch = 0;
 210213            Span<long> parts = [0, 0, 0]; // One part per octet. Final octet doesn't have a terminator, so is stored in 
 210214            long currentValue = 0;
 210215            bool atLeastOneChar = false;
 216
 217            // Parse one dotted section at a time
 210218            int dotCount = 0; // Limit 3
 210219            int current = 0;
 220
 574221            for (; current < name.Length; current++)
 392222            {
 392223                ch = ToUShort(name[current]);
 392224                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
 392229                numberBase = IPv4AddressHelper.Decimal;
 230
 392231                if (ch == '0')
 262232                {
 262233                    current++;
 262234                    atLeastOneChar = true;
 262235                    if (current < name.Length)
 253236                    {
 253237                        ch = ToUShort(name[current]);
 238
 253239                        if (ch == 'x' || ch == 'X')
 44240                        {
 44241                            numberBase = IPv4AddressHelper.Hex;
 242
 44243                            current++;
 44244                            atLeastOneChar = false;
 44245                        }
 246                        else
 209247                        {
 209248                            numberBase = IPv4AddressHelper.Octal;
 209249                        }
 253250                    }
 262251                }
 252
 253                // Parse this section
 11260254                for (; current < name.Length; current++)
 5721255                {
 5721256                    ch = ToUShort(name[current]);
 5721257                    int digitValue = HexConverter.FromChar(ch);
 258
 5721259                    if (digitValue >= numberBase)
 287260                    {
 287261                        break; // Invalid/terminator
 262                    }
 5434263                    currentValue = (currentValue * numberBase) + digitValue;
 264
 5434265                    if (currentValue > MaxIPv4Value) // Overflow
 0266                    {
 0267                        return Invalid;
 268                    }
 269
 5434270                    atLeastOneChar = true;
 5434271                }
 272
 392273                if (current < name.Length && ch == '.')
 182274                {
 182275                    if (dotCount >= 3 // Max of 3 dots and 4 segments
 182276                        || !atLeastOneChar // No empty segments: 1...1
 182277                                           // Only the last segment can be more than 255 (if there are less than 3 dots)
 182278                        || currentValue > 0xFF)
 0279                    {
 0280                        return Invalid;
 281                    }
 182282                    parts[dotCount] = currentValue;
 182283                    dotCount++;
 182284                    atLeastOneChar = false;
 182285                    continue;
 286                }
 287                // We don't get here unless we find an invalid character or a terminator
 0288                break;
 289            }
 290
 291            // Terminators
 210292            if (!atLeastOneChar)
 0293            {
 0294                return Invalid;  // Empty trailing segment: 1.1.1.
 295            }
 210296            else if (current >= name.Length)
 105297            {
 298                // end of string, allowed
 105299                end = name.Length;
 105300            }
 105301            else if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
 105302            {
 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
 105305                end = current;
 105306            }
 307            else
 0308            {
 309                // not a valid terminating character
 0310                return Invalid;
 311            }
 312
 313            // Parsed, reassemble and check for overflows in the last part. Previous parts have already been checked in 
 210314            switch (dotCount)
 315            {
 316                case 0: // 0xFFFFFFFF
 118317                    return currentValue;
 318                case 1: // 0xFF.0xFFFFFF
 36319                    Debug.Assert(parts[0] <= 0xFF);
 36320                    if (currentValue > 0xffffff)
 0321                    {
 0322                        return Invalid;
 323                    }
 36324                    return (parts[0] << 24) | currentValue;
 325                case 2: // 0xFF.0xFF.0xFFFF
 22326                    Debug.Assert(parts[0] <= 0xFF);
 22327                    Debug.Assert(parts[1] <= 0xFF);
 22328                    if (currentValue > 0xffff)
 0329                    {
 0330                        return Invalid;
 331                    }
 22332                    return (parts[0] << 24) | (parts[1] << 16) | currentValue;
 333                case 3: // 0xFF.0xFF.0xFF.0xFF
 34334                    Debug.Assert(parts[0] <= 0xFF);
 34335                    Debug.Assert(parts[1] <= 0xFF);
 34336                    Debug.Assert(parts[2] <= 0xFF);
 34337                    if (currentValue > 0xff)
 0338                    {
 0339                        return Invalid;
 340                    }
 34341                    return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | currentValue;
 342                default:
 0343                    return Invalid;
 344            }
 210345        }
 346    }
 347}

D:\runner\runtime\src\libraries\System.Private.Uri\src\System\IPv4AddressHelper.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.Diagnostics;
 5
 6namespace System.Net
 7{
 8    // The class designed as to keep minimal the working set of Uri class.
 9    // The idea is to stay with static helper methods and strings
 10    internal static partial class IPv4AddressHelper
 11    {
 12        // methods
 13        // Parse and canonicalize
 14        internal static unsafe string ParseCanonicalName(string str, int start, int end, ref bool isLoopback)
 015        {
 016            long result = ParseNonCanonical(str.AsSpan(start, end - start), out _, true);
 17
 018            Debug.Assert(result != Invalid, $"Failed to parse after already validated: {str}");
 19
 020            Span<byte> numbers =
 021                unchecked(
 022                [
 023                    (byte)(result >> 24),
 024                    (byte)(result >> 16),
 025                    (byte)(result >> 8),
 026                    (byte)(result)
 027                ]);
 28
 029            isLoopback = numbers[0] == 127;
 30
 031            Span<char> stackSpace = stackalloc char[NumberOfLabels * 3 + 3];
 032            int totalChars = 0, charsWritten;
 033            for (int i = 0; i < NumberOfLabels - 1; i++)
 034            {
 035                numbers[i].TryFormat(stackSpace.Slice(totalChars), out charsWritten);
 036                int periodPos = totalChars + charsWritten;
 037                stackSpace[periodPos] = '.';
 038                totalChars = periodPos + 1;
 039            }
 40
 041            numbers[3].TryFormat(stackSpace.Slice(totalChars), out charsWritten);
 042            return new string(stackSpace.Slice(0, totalChars + charsWritten));
 043        }
 44    }
 45}