< Summary

Information
Class: System.Net.Http.Headers.HeaderUtilities
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\Headers\HeaderUtilities.cs
Line coverage
19%
Covered lines: 43
Uncovered lines: 178
Coverable lines: 221
Total lines: 363
Line coverage: 19.4%
Branch coverage
21%
Covered branches: 21
Total branches: 98
Branch coverage: 21.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
SetQuality(...)0%660%
Encode5987(...)0%10100%
AddHexEscaped(...)100%110%
GetQuality(...)0%660%
CheckValidToken(...)50%2266.66%
CheckValidComment(...)50%4471.42%
CheckValidQuotedString(...)50%4471.42%
AreEqualCollections(...)100%110%
AreEqualCollections(...)0%28280%
GetNextNonEmptyOrWhitespaceIndex(...)100%1010100%
GetDateTimeOffsetValue(...)0%660%
GetTimeSpanValue(...)0%220%
TryParseInt32(...)100%110%
TryParseInt32(...)50%6657.14%
TryParseInt64(...)50%6657.14%
DumpHeaders(...)0%440%
Clone(...)0%440%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\Headers\HeaderUtilities.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;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Globalization;
 8using System.Runtime.CompilerServices;
 9using System.Text;
 10
 11namespace System.Net.Http.Headers
 12{
 13    internal static class HeaderUtilities
 14    {
 15        private const string qualityName = "q";
 16
 17        internal const string ConnectionClose = "close";
 018        internal static readonly TransferCodingHeaderValue TransferEncodingChunked =
 019            new TransferCodingHeaderValue("chunked");
 020        internal static readonly NameValueWithParametersHeaderValue ExpectContinue =
 021            new NameValueWithParametersHeaderValue("100-continue");
 22
 23        internal const string BytesUnit = "bytes";
 24
 25        // attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
 26        //      ; token except ( "*" / "'" / "%" )
 027        private static readonly SearchValues<byte> s_rfc5987AttrBytes =
 028            SearchValues.Create("!#$&+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~"u8);
 29
 30        internal static void SetQuality(UnvalidatedObjectCollection<NameValueHeaderValue> parameters, double? value)
 031        {
 032            Debug.Assert(parameters != null);
 33
 034            NameValueHeaderValue? qualityParameter = NameValueHeaderValue.Find(parameters, qualityName);
 035            if (value.HasValue)
 036            {
 37                // Note that even if we check the value here, we can't prevent a user from adding an invalid quality
 38                // value using Parameters.Add(). Even if we would prevent the user from adding an invalid value
 39                // using Parameters.Add() they could always add invalid values using HttpHeaders.AddWithoutValidation().
 40                // So this check is really for convenience to show users that they're trying to add an invalid
 41                // value.
 042                double d = value.GetValueOrDefault();
 043                ArgumentOutOfRangeException.ThrowIfNegative(d);
 044                ArgumentOutOfRangeException.ThrowIfGreaterThan(d, 1);
 45
 046                string qualityString = d.ToString("0.0##", NumberFormatInfo.InvariantInfo);
 047                if (qualityParameter != null)
 048                {
 049                    qualityParameter.Value = qualityString;
 050                }
 51                else
 052                {
 053                    parameters.Add(new NameValueHeaderValue(qualityName, qualityString));
 054                }
 055            }
 56            else
 057            {
 58                // Remove quality parameter
 059                if (qualityParameter != null)
 060                {
 061                    parameters.Remove(qualityParameter);
 062                }
 063            }
 064        }
 65
 66        // Encode a string using RFC 5987 encoding.
 67        // encoding'lang'PercentEncodedSpecials
 68        internal static string Encode5987(string input)
 069        {
 070            var builder = new ValueStringBuilder(stackalloc char[256]);
 071            byte[] utf8bytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(input.Length));
 072            int utf8length = Encoding.UTF8.GetBytes(input, 0, input.Length, utf8bytes, 0);
 73
 074            builder.Append("utf-8\'\'");
 75
 076            ReadOnlySpan<byte> utf8 = utf8bytes.AsSpan(0, utf8length);
 77            do
 078            {
 079                int length = utf8.IndexOfAnyExcept(s_rfc5987AttrBytes);
 080                if (length < 0)
 081                {
 082                    length = utf8.Length;
 083                }
 84
 085                Encoding.ASCII.GetChars(utf8.Slice(0, length), builder.AppendSpan(length));
 86
 087                utf8 = utf8.Slice(length);
 88
 089                if (utf8.IsEmpty)
 090                {
 091                    break;
 92                }
 93
 094                length = utf8.IndexOfAny(s_rfc5987AttrBytes);
 095                if (length < 0)
 096                {
 097                    length = utf8.Length;
 098                }
 99
 0100                foreach (byte b in utf8.Slice(0, length))
 0101                {
 0102                    AddHexEscaped(b, ref builder);
 0103                }
 104
 0105                utf8 = utf8.Slice(length);
 0106            }
 0107            while (!utf8.IsEmpty);
 108
 0109            ArrayPool<byte>.Shared.Return(utf8bytes);
 110
 0111            return builder.ToString();
 0112        }
 113
 114        /// <summary>Transforms an ASCII character into its hexadecimal representation, adding the characters to a Strin
 115        private static void AddHexEscaped(byte c, ref ValueStringBuilder destination)
 0116        {
 0117            destination.Append('%');
 0118            destination.Append(HexConverter.ToCharUpper(c >> 4));
 0119            destination.Append(HexConverter.ToCharUpper(c));
 0120        }
 121
 122        internal static double? GetQuality(UnvalidatedObjectCollection<NameValueHeaderValue> parameters)
 0123        {
 0124            Debug.Assert(parameters != null);
 125
 0126            NameValueHeaderValue? qualityParameter = NameValueHeaderValue.Find(parameters, qualityName);
 0127            if (qualityParameter != null)
 0128            {
 129                // Note that the RFC requires decimal '.' regardless of the culture. I.e. using ',' as decimal
 130                // separator is considered invalid (even if the current culture would allow it).
 131                double qualityValue;
 0132                if (double.TryParse(qualityParameter.Value, NumberStyles.AllowDecimalPoint,
 0133                    NumberFormatInfo.InvariantInfo, out qualityValue))
 0134                {
 0135                    return qualityValue;
 136                }
 137                // If the stored value is an invalid quality value, just return null and log a warning.
 0138                if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, SR.Format(SR.net_http_log_headers_invalid
 0139            }
 0140            return null;
 0141        }
 142
 143        internal static void CheckValidToken(string value, [CallerArgumentExpression(nameof(value))] string? parameterNa
 197971144        {
 197971145            ArgumentException.ThrowIfNullOrEmpty(value, parameterName);
 146
 197971147            if (!HttpRuleParser.IsToken(value))
 0148            {
 0149                throw new FormatException(SR.Format(CultureInfo.InvariantCulture, SR.net_http_headers_invalid_value, val
 150            }
 197971151        }
 152
 153        internal static void CheckValidComment(string value, [CallerArgumentExpression(nameof(value))] string? parameter
 1226154        {
 1226155            ArgumentException.ThrowIfNullOrEmpty(value, parameterName);
 156
 1226157            if ((HttpRuleParser.GetCommentLength(value, 0, out int length) != HttpParseResult.Parsed) ||
 1226158                (length != value.Length)) // no trailing spaces allowed
 0159            {
 0160                throw new FormatException(SR.Format(CultureInfo.InvariantCulture, SR.net_http_headers_invalid_value, val
 161            }
 1226162        }
 163
 164        internal static void CheckValidQuotedString(string value, [CallerArgumentExpression(nameof(value))] string? para
 27716165        {
 27716166            ArgumentException.ThrowIfNullOrEmpty(value, parameterName);
 167
 27716168            if ((HttpRuleParser.GetQuotedStringLength(value, 0, out int length) != HttpParseResult.Parsed) ||
 27716169                (length != value.Length)) // no trailing spaces allowed
 0170            {
 0171                throw new FormatException(SR.Format(CultureInfo.InvariantCulture, SR.net_http_headers_invalid_value, val
 172            }
 27716173        }
 174
 175        internal static bool AreEqualCollections<T>(ObjectCollection<T>? x, ObjectCollection<T>? y) where T : class
 0176        {
 0177            return AreEqualCollections(x, y, null);
 0178        }
 179
 180        internal static bool AreEqualCollections<T>(ObjectCollection<T>? x, ObjectCollection<T>? y, IEqualityComparer<T>
 0181        {
 0182            if (x == null)
 0183            {
 0184                return (y == null) || (y.Count == 0);
 185            }
 186
 0187            if (y == null)
 0188            {
 0189                return (x.Count == 0);
 190            }
 191
 0192            if (x.Count != y.Count)
 0193            {
 0194                return false;
 195            }
 196
 0197            if (x.Count == 0)
 0198            {
 0199                return true;
 200            }
 201
 202            // We have two unordered lists. So comparison is an O(n*m) operation which is expensive. Usually
 203            // headers have 1-2 parameters (if any), so this comparison shouldn't be too expensive.
 0204            bool[] alreadyFound = new bool[x.Count];
 0205            int i = 0;
 0206            foreach (var xItem in x)
 0207            {
 0208                Debug.Assert(xItem != null);
 209
 0210                i = 0;
 0211                bool found = false;
 0212                foreach (var yItem in y)
 0213                {
 0214                    if (!alreadyFound[i])
 0215                    {
 0216                        if (((comparer == null) && xItem.Equals(yItem)) ||
 0217                            ((comparer != null) && comparer.Equals(xItem, yItem)))
 0218                        {
 0219                            alreadyFound[i] = true;
 0220                            found = true;
 0221                            break;
 222                        }
 0223                    }
 0224                    i++;
 0225                }
 226
 0227                if (!found)
 0228                {
 0229                    return false;
 230                }
 0231            }
 232
 233            // Since we never re-use a "found" value in 'y', we expect 'alreadyFound' to have all fields set to 'true'.
 234            // Otherwise the two collections can't be equal and we should not get here.
 0235            Debug.Assert(Array.TrueForAll(alreadyFound, value => value),
 0236                "Expected all values in 'alreadyFound' to be true since collections are considered equal.");
 237
 0238            return true;
 0239        }
 240
 241        internal static int GetNextNonEmptyOrWhitespaceIndex(string input, int startIndex, bool skipEmptyValues,
 242            out bool separatorFound)
 2006158243        {
 2006158244            Debug.Assert(input != null);
 2006158245            Debug.Assert(startIndex <= input.Length); // it's OK if index == value.Length.
 246
 2006158247            separatorFound = false;
 2006158248            int current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
 249
 2006158250            if ((current == input.Length) || (input[current] != ','))
 1015667251            {
 1015667252                return current;
 253            }
 254
 255            // If we have a separator, skip the separator and all following whitespace. If we support
 256            // empty values, continue until the current character is neither a separator nor a whitespace.
 990491257            separatorFound = true;
 990491258            current++; // skip delimiter.
 990491259            current += HttpRuleParser.GetWhitespaceLength(input, current);
 260
 990491261            if (skipEmptyValues)
 990409262            {
 1048594263                while ((current < input.Length) && (input[current] == ','))
 58185264                {
 58185265                    current++; // skip delimiter.
 58185266                    current += HttpRuleParser.GetWhitespaceLength(input, current);
 58185267                }
 990409268            }
 269
 990491270            return current;
 2006158271        }
 272
 273        internal static DateTimeOffset? GetDateTimeOffsetValue(HeaderDescriptor descriptor, HttpHeaders store, DateTimeO
 0274        {
 0275            Debug.Assert(store != null);
 276
 0277            object? storedValue = store.GetSingleParsedValue(descriptor);
 0278            if (storedValue != null)
 0279            {
 0280                return (DateTimeOffset)storedValue;
 281            }
 0282            else if (defaultValue != null && store.Contains(descriptor))
 0283            {
 0284                return defaultValue;
 285            }
 286
 0287            return null;
 0288        }
 289
 290        internal static TimeSpan? GetTimeSpanValue(HeaderDescriptor descriptor, HttpHeaders store)
 0291        {
 0292            Debug.Assert(store != null);
 293
 0294            object? storedValue = store.GetSingleParsedValue(descriptor);
 0295            if (storedValue != null)
 0296            {
 0297                return (TimeSpan)storedValue;
 298            }
 0299            return null;
 0300        }
 301
 302        internal static bool TryParseInt32(string value, out int result) =>
 0303            int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
 304
 305        internal static bool TryParseInt32(string value, int offset, int length, out int result)
 27190306        {
 27190307            if (offset < 0 || length < 0 || offset > value.Length - length)
 0308            {
 0309                result = 0;
 0310                return false;
 311            }
 312
 27190313            return int.TryParse(value.AsSpan(offset, length), NumberStyles.None, CultureInfo.InvariantCulture, out resul
 27190314        }
 315
 316        internal static bool TryParseInt64(string value, int offset, int length, out long result)
 55250317        {
 55250318            if (offset < 0 || length < 0 || offset > value.Length - length)
 0319            {
 0320                result = 0;
 0321                return false;
 322            }
 323
 55250324            return long.TryParse(value.AsSpan(offset, length), NumberStyles.None, CultureInfo.InvariantCulture, out resu
 55250325        }
 326
 327        internal static void DumpHeaders(ref ValueStringBuilder sb, params ReadOnlySpan<HttpHeaders?> headers)
 0328        {
 329            // Dumps all headers in the following format:
 330            // {
 331            //    HeaderName1: Value1, Value2
 332            //    HeaderName2: Value1
 333            //    ...
 334            // }
 0335            sb.Append('{');
 0336            sb.Append(Environment.NewLine);
 337
 0338            for (int i = 0; i < headers.Length; i++)
 0339            {
 0340                if (headers[i] is HttpHeaders hh)
 0341                {
 0342                    hh.Dump(ref sb, indentLines: true);
 0343                }
 0344            }
 345
 0346            sb.Append('}');
 0347        }
 348
 349        internal static UnvalidatedObjectCollection<NameValueHeaderValue>? Clone(this UnvalidatedObjectCollection<NameVa
 0350        {
 0351            if (source == null)
 0352                return null;
 353
 0354            var copy = new UnvalidatedObjectCollection<NameValueHeaderValue>();
 0355            foreach (NameValueHeaderValue item in source)
 0356            {
 0357                copy.Add(new NameValueHeaderValue(item));
 0358            }
 359
 0360            return copy;
 0361        }
 362    }
 363}

Methods/Properties

.cctor()
SetQuality(System.Net.Http.Headers.UnvalidatedObjectCollection`1<System.Net.Http.Headers.NameValueHeaderValue>,System.Nullable`1<System.Double>)
Encode5987(System.String)
AddHexEscaped(System.Byte,System.Text.ValueStringBuilder&)
GetQuality(System.Net.Http.Headers.UnvalidatedObjectCollection`1<System.Net.Http.Headers.NameValueHeaderValue>)
CheckValidToken(System.String,System.String)
CheckValidComment(System.String,System.String)
CheckValidQuotedString(System.String,System.String)
AreEqualCollections(System.Net.Http.Headers.ObjectCollection`1<T>,System.Net.Http.Headers.ObjectCollection`1<T>)
AreEqualCollections(System.Net.Http.Headers.ObjectCollection`1<T>,System.Net.Http.Headers.ObjectCollection`1<T>,System.Collections.Generic.IEqualityComparer`1<T>)
GetNextNonEmptyOrWhitespaceIndex(System.String,System.Int32,System.Boolean,System.Boolean&)
GetDateTimeOffsetValue(System.Net.Http.Headers.HeaderDescriptor,System.Net.Http.Headers.HttpHeaders,System.Nullable`1<System.DateTimeOffset>)
GetTimeSpanValue(System.Net.Http.Headers.HeaderDescriptor,System.Net.Http.Headers.HttpHeaders)
TryParseInt32(System.String,System.Int32&)
TryParseInt32(System.String,System.Int32,System.Int32,System.Int32&)
TryParseInt64(System.String,System.Int32,System.Int32,System.Int64&)
DumpHeaders(System.Text.ValueStringBuilder&,System.ReadOnlySpan`1<System.Net.Http.Headers.HttpHeaders>)
Clone(System.Net.Http.Headers.UnvalidatedObjectCollection`1<System.Net.Http.Headers.NameValueHeaderValue>)