< Summary

Class:Azure.Core.Http.Multipart.HeaderUtilities
Assembly:Azure.Storage.Blobs.Batch
File(s):C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Blobs.Batch\src\Shared\HeaderUtilities.cs
Covered lines:0
Uncovered lines:214
Coverable lines:214
Total lines:765
Line coverage:0% (0 of 214)
Covered branches:0
Total branches:178
Branch coverage:0% (0 of 178)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-0%100%
SetQuality(...)-0%0%
GetQuality(...)-0%0%
CheckValidToken(...)-0%0%
AreEqualCollections(...)-0%100%
AreEqualCollections(...)-0%0%
GetNextNonEmptyOrWhitespaceIndex(...)-0%0%
AdvanceCacheDirectiveIndex(...)-0%0%
TryParseSeconds(...)-0%0%
ContainsCacheDirective(...)-0%0%
TryParseNonNegativeInt64FromHeaderValue(...)-0%0%
TryParseNonNegativeInt32(...)-0%0%
TryParseNonNegativeInt64(...)-0%0%
TryParseQualityDouble(...)-0%0%
FormatNonNegativeInt64(...)-0%0%
TryParseDate(...)-0%100%
RemoveQuotes(...)-0%0%
IsQuoted(...)-0%0%
CountBackslashesForDecodingQuotedString(...)-0%0%
CountAndCheckCharactersNeedingBackslashesWhenEncoding(...)-0%0%
ThrowIfReadOnly(...)-0%0%

File(s)

C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Blobs.Batch\src\Shared\HeaderUtilities.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4// Copied from https://github.com/aspnet/AspNetCore/tree/master/src/Http/Headers/src
 5
 6using System;
 7using System.Collections.Generic;
 8using System.Diagnostics;
 9using System.Diagnostics.Contracts;
 10using System.Globalization;
 11
 12#pragma warning disable CA1305  // ToString Locale
 13#pragma warning disable CA1802  // const fields
 14#pragma warning disable IDE0008 // Use explicit type
 15#pragma warning disable IDE0018 // Inline declaration
 16#pragma warning disable IDE0054 // Use compound assignment
 17#pragma warning disable IDE0051 // Unused private member
 18#pragma warning disable IDE0059 // Unnecessary assignment
 19#pragma warning disable IDE1006 // Missing s_ prefix
 20
 21namespace Azure.Core.Http.Multipart
 22{
 23    internal static class HeaderUtilities
 24    {
 025        private static readonly int _int64MaxStringLength = 19;
 026        private static readonly int _qualityValueMaxCharCount = 10; // Little bit more permissive than RFC7231 5.3.1
 27        private const string QualityName = "q";
 28        internal const string BytesUnit = "bytes";
 29
 30        internal static void SetQuality(IList<NameValueHeaderValue> parameters, double? value)
 31        {
 32            Contract.Requires(parameters != null);
 33
 034            var qualityParameter = NameValueHeaderValue.Find(parameters, QualityName);
 035            if (value.HasValue)
 36            {
 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() he 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                if ((value < 0) || (value > 1))
 43                {
 044                    throw new ArgumentOutOfRangeException(nameof(value));
 45                }
 46
 047                var qualityString = ((double)value).ToString("0.0##", NumberFormatInfo.InvariantInfo);
 048                if (qualityParameter != null)
 49                {
 050                    qualityParameter.Value = qualityString;
 51                }
 52                else
 53                {
 054                    parameters.Add(new NameValueHeaderValue(QualityName, qualityString));
 55                }
 56            }
 57            else
 58            {
 59                // Remove quality parameter
 060                if (qualityParameter != null)
 61                {
 062                    parameters.Remove(qualityParameter);
 63                }
 64            }
 065        }
 66
 67        internal static double? GetQuality(IList<NameValueHeaderValue> parameters)
 68        {
 69            Contract.Requires(parameters != null);
 70
 071            var qualityParameter = NameValueHeaderValue.Find(parameters, QualityName);
 072            if (qualityParameter != null)
 73            {
 74                // Note that the RFC requires decimal '.' regardless of the culture. I.e. using ',' as decimal
 75                // separator is considered invalid (even if the current culture would allow it).
 076                if (TryParseQualityDouble(qualityParameter.Value, 0, out var qualityValue, out var length))
 77
 78                {
 079                    return qualityValue;
 80                }
 81            }
 082            return null;
 83        }
 84
 85        internal static void CheckValidToken(StringSegment value, string parameterName)
 86        {
 087            if (StringSegment.IsNullOrEmpty(value))
 88            {
 089                throw new ArgumentException("An empty string is not allowed.", parameterName);
 90            }
 91
 092            if (HttpRuleParser.GetTokenLength(value, 0) != value.Length)
 93            {
 094                throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid token '{0}.", value));
 95            }
 096        }
 97
 98        internal static bool AreEqualCollections<T>(ICollection<T> x, ICollection<T> y)
 99        {
 0100            return AreEqualCollections(x, y, null);
 101        }
 102
 103        internal static bool AreEqualCollections<T>(ICollection<T> x, ICollection<T> y, IEqualityComparer<T> comparer)
 104        {
 0105            if (x == null)
 106            {
 0107                return (y == null) || (y.Count == 0);
 108            }
 109
 0110            if (y == null)
 111            {
 0112                return (x.Count == 0);
 113            }
 114
 0115            if (x.Count != y.Count)
 116            {
 0117                return false;
 118            }
 119
 0120            if (x.Count == 0)
 121            {
 0122                return true;
 123            }
 124
 125            // We have two unordered lists. So comparison is an O(n*m) operation which is expensive. Usually
 126            // headers have 1-2 parameters (if any), so this comparison shouldn't be too expensive.
 0127            var alreadyFound = new bool[x.Count];
 0128            var i = 0;
 0129            foreach (var xItem in x)
 130            {
 131                Contract.Assert(xItem != null);
 132
 0133                i = 0;
 0134                var found = false;
 0135                foreach (var yItem in y)
 136                {
 0137                    if (!alreadyFound[i])
 138                    {
 0139                        if (((comparer == null) && xItem.Equals(yItem)) ||
 0140                            ((comparer != null) && comparer.Equals(xItem, yItem)))
 141                        {
 0142                            alreadyFound[i] = true;
 0143                            found = true;
 0144                            break;
 145                        }
 146                    }
 0147                    i++;
 148                }
 149
 0150                if (!found)
 151                {
 0152                    return false;
 153                }
 154            }
 155
 156            // Since we never re-use a "found" value in 'y', we expected 'alreadyFound' to have all fields set to 'true'
 157            // Otherwise the two collections can't be equal and we should not get here.
 158            Contract.Assert(Contract.ForAll(alreadyFound, value => { return value; }),
 159                "Expected all values in 'alreadyFound' to be true since collections are considered equal.");
 160
 0161            return true;
 0162        }
 163
 164        internal static int GetNextNonEmptyOrWhitespaceIndex(
 165            StringSegment input,
 166            int startIndex,
 167            bool skipEmptyValues,
 168            out bool separatorFound)
 169        {
 170            Contract.Requires(input != null);
 171            Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length.
 172
 0173            separatorFound = false;
 0174            var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
 175
 0176            if ((current == input.Length) || (input[current] != ','))
 177            {
 0178                return current;
 179            }
 180
 181            // If we have a separator, skip the separator and all following whitespaces. If we support
 182            // empty values, continue until the current character is neither a separator nor a whitespace.
 0183            separatorFound = true;
 0184            current++; // skip delimiter.
 0185            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
 186
 0187            if (skipEmptyValues)
 188            {
 0189                while ((current < input.Length) && (input[current] == ','))
 190                {
 0191                    current++; // skip delimiter.
 0192                    current = current + HttpRuleParser.GetWhitespaceLength(input, current);
 193                }
 194            }
 195
 0196            return current;
 197        }
 198
 199        private static int AdvanceCacheDirectiveIndex(int current, string headerValue)
 200        {
 201            // Skip until the next potential name
 0202            current += HttpRuleParser.GetWhitespaceLength(headerValue, current);
 203
 204            // Skip the value if present
 0205            if (current < headerValue.Length && headerValue[current] == '=')
 206            {
 0207                current++; // skip '='
 0208                current += NameValueHeaderValue.GetValueLength(headerValue, current);
 209            }
 210
 211            // Find the next delimiter
 0212            current = headerValue.IndexOf(',', current);
 213
 0214            if (current == -1)
 215            {
 216                // If no delimiter found, skip to the end
 0217                return headerValue.Length;
 218            }
 219
 0220            current++; // skip ','
 0221            current += HttpRuleParser.GetWhitespaceLength(headerValue, current);
 222
 0223            return current;
 224        }
 225
 226        /// <summary>
 227        /// Try to find a target header value among the set of given header values and parse it as a
 228        /// <see cref="TimeSpan"/>.
 229        /// </summary>
 230        /// <param name="headerValues">
 231        /// The <see cref="StringValues"/> containing the set of header values to search.
 232        /// </param>
 233        /// <param name="targetValue">
 234        /// The target header value to look for.
 235        /// </param>
 236        /// <param name="value">
 237        /// When this method returns, contains the parsed <see cref="TimeSpan"/>, if the parsing succeeded, or
 238        /// null if the parsing failed. The conversion fails if the <paramref name="targetValue"/> was not
 239        /// found or could not be parsed as a <see cref="TimeSpan"/>. This parameter is passed uninitialized;
 240        /// any value originally supplied in result will be overwritten.
 241        /// </param>
 242        /// <returns>
 243        /// <code>true</code> if <paramref name="targetValue"/> is found and successfully parsed; otherwise,
 244        /// <code>false</code>.
 245        /// </returns>
 246        // e.g. { "headerValue=10, targetHeaderValue=30" }
 247        public static bool TryParseSeconds(StringValues headerValues, string targetValue, out TimeSpan? value)
 248        {
 0249            if (StringValues.IsNullOrEmpty(headerValues) || string.IsNullOrEmpty(targetValue))
 250            {
 0251                value = null;
 0252                return false;
 253            }
 254
 0255            for (var i = 0; i < headerValues.Count; i++)
 256            {
 257                // Trim leading white space
 0258                var current = HttpRuleParser.GetWhitespaceLength(headerValues[i], 0);
 259
 0260                while (current < headerValues[i].Length)
 261                {
 262                    long seconds;
 0263                    var initial = current;
 0264                    var tokenLength = HttpRuleParser.GetTokenLength(headerValues[i], current);
 0265                    if (tokenLength == targetValue.Length
 0266                        && string.Compare(headerValues[i], current, targetValue, 0, tokenLength, StringComparison.Ordina
 0267                        && TryParseNonNegativeInt64FromHeaderValue(current + tokenLength, headerValues[i], out seconds))
 268                    {
 269                        // Token matches target value and seconds were parsed
 0270                        value = TimeSpan.FromSeconds(seconds);
 0271                        return true;
 272                    }
 273
 0274                    current = AdvanceCacheDirectiveIndex(current + tokenLength, headerValues[i]);
 275
 276                    // Ensure index was advanced
 0277                    if (current <= initial)
 278                    {
 279                        Debug.Assert(false, $"Index '{nameof(current)}' not advanced, this is a bug.");
 0280                        value = null;
 0281                        return false;
 282                    }
 283                }
 284            }
 0285            value = null;
 0286            return false;
 287        }
 288
 289        /// <summary>
 290        /// Check if a target directive exists among the set of given cache control directives.
 291        /// </summary>
 292        /// <param name="cacheControlDirectives">
 293        /// The <see cref="StringValues"/> containing the set of cache control directives.
 294        /// </param>
 295        /// <param name="targetDirectives">
 296        /// The target cache control directives to look for.
 297        /// </param>
 298        /// <returns>
 299        /// <code>true</code> if <paramref name="targetDirectives"/> is contained in <paramref name="cacheControlDirecti
 300        /// otherwise, <code>false</code>.
 301        /// </returns>
 302        public static bool ContainsCacheDirective(StringValues cacheControlDirectives, string targetDirectives)
 303        {
 0304            if (StringValues.IsNullOrEmpty(cacheControlDirectives) || string.IsNullOrEmpty(targetDirectives))
 305            {
 0306                return false;
 307            }
 308
 0309            for (var i = 0; i < cacheControlDirectives.Count; i++)
 310            {
 311                // Trim leading white space
 0312                var current = HttpRuleParser.GetWhitespaceLength(cacheControlDirectives[i], 0);
 313
 0314                while (current < cacheControlDirectives[i].Length)
 315                {
 0316                    var initial = current;
 317
 0318                    var tokenLength = HttpRuleParser.GetTokenLength(cacheControlDirectives[i], current);
 0319                    if (tokenLength == targetDirectives.Length
 0320                        && string.Compare(cacheControlDirectives[i], current, targetDirectives, 0, tokenLength, StringCo
 321                    {
 322                        // Token matches target value
 0323                        return true;
 324                    }
 325
 0326                    current = AdvanceCacheDirectiveIndex(current + tokenLength, cacheControlDirectives[i]);
 327
 328                    // Ensure index was advanced
 0329                    if (current <= initial)
 330                    {
 331                        Debug.Assert(false, $"Index '{nameof(current)}' not advanced, this is a bug.");
 0332                        return false;
 333                    }
 334                }
 335            }
 336
 0337            return false;
 338        }
 339
 340        private static unsafe bool TryParseNonNegativeInt64FromHeaderValue(int startIndex, string headerValue, out long 
 341        {
 342            // Trim leading whitespace
 0343            startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
 344
 345            // Match and skip '=', it also can't be the last character in the headerValue
 0346            if (startIndex >= headerValue.Length - 1 || headerValue[startIndex] != '=')
 347            {
 0348                result = 0;
 0349                return false;
 350            }
 0351            startIndex++;
 352
 353            // Trim trailing whitespace
 0354            startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
 355
 356            // Try parse the number
 0357            if (TryParseNonNegativeInt64(new StringSegment(headerValue, startIndex, HttpRuleParser.GetNumberLength(heade
 358            {
 0359                return true;
 360            }
 361
 0362            result = 0;
 0363            return false;
 364        }
 365
 366        /// <summary>
 367        /// Try to convert a string representation of a positive number to its 64-bit signed integer equivalent.
 368        /// A return value indicates whether the conversion succeeded or failed.
 369        /// </summary>
 370        /// <param name="value">
 371        /// A string containing a number to convert.
 372        /// </param>
 373        /// <param name="result">
 374        /// When this method returns, contains the 64-bit signed integer value equivalent of the number contained
 375        /// in the string, if the conversion succeeded, or zero if the conversion failed. The conversion fails if
 376        /// the string is null or String.Empty, is not of the correct format, is negative, or represents a number
 377        /// greater than Int64.MaxValue. This parameter is passed uninitialized; any value originally supplied in
 378        /// result will be overwritten.
 379        /// </param>
 380        /// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
 381        public static unsafe bool TryParseNonNegativeInt32(StringSegment value, out int result)
 382        {
 0383            if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
 384            {
 0385                result = 0;
 0386                return false;
 387            }
 388
 0389            result = 0;
 0390            fixed (char* ptr = value.Buffer)
 391            {
 0392                var ch = (ushort*)ptr + value.Offset;
 0393                var end = ch + value.Length;
 394
 0395                ushort digit = 0;
 0396                while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9)
 397                {
 398                    // Check for overflow
 0399                    if ((result = result * 10 + digit) < 0)
 400                    {
 0401                        result = 0;
 0402                        return false;
 403                    }
 404
 0405                    ch++;
 406                }
 407
 0408                if (ch != end)
 409                {
 0410                    result = 0;
 0411                    return false;
 412                }
 0413                return true;
 414            }
 415        }
 416
 417        /// <summary>
 418        /// Try to convert a <see cref="StringSegment"/> representation of a positive number to its 64-bit signed
 419        /// integer equivalent. A return value indicates whether the conversion succeeded or failed.
 420        /// </summary>
 421        /// <param name="value">
 422        /// A <see cref="StringSegment"/> containing a number to convert.
 423        /// </param>
 424        /// <param name="result">
 425        /// When this method returns, contains the 64-bit signed integer value equivalent of the number contained
 426        /// in the string, if the conversion succeeded, or zero if the conversion failed. The conversion fails if
 427        /// the <see cref="StringSegment"/> is null or String.Empty, is not of the correct format, is negative, or
 428        /// represents a number greater than Int64.MaxValue. This parameter is passed uninitialized; any value
 429        /// originally supplied in result will be overwritten.
 430        /// </param>
 431        /// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
 432        public static unsafe bool TryParseNonNegativeInt64(StringSegment value, out long result)
 433        {
 0434            if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
 435            {
 0436                result = 0;
 0437                return false;
 438            }
 439
 0440            result = 0;
 0441            fixed (char* ptr = value.Buffer)
 442            {
 0443                var ch = (ushort*)ptr + value.Offset;
 0444                var end = ch + value.Length;
 445
 0446                ushort digit = 0;
 0447                while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9)
 448                {
 449                    // Check for overflow
 0450                    if ((result = result * 10 + digit) < 0)
 451                    {
 0452                        result = 0;
 0453                        return false;
 454                    }
 455
 0456                    ch++;
 457                }
 458
 0459                if (ch != end)
 460                {
 0461                    result = 0;
 0462                    return false;
 463                }
 0464                return true;
 465            }
 466        }
 467
 468        // Strict and fast RFC7231 5.3.1 Quality value parser (and without memory allocation)
 469        // See https://tools.ietf.org/html/rfc7231#section-5.3.1
 470        // Check is made to verify if the value is between 0 and 1 (and it returns False if the check fails).
 471        internal static bool TryParseQualityDouble(StringSegment input, int startIndex, out double quality, out int leng
 472        {
 0473            quality = 0;
 0474            length = 0;
 475
 0476            var inputLength = input.Length;
 0477            var current = startIndex;
 0478            var limit = startIndex + _qualityValueMaxCharCount;
 479
 0480            var intPart = 0;
 0481            var decPart = 0;
 0482            var decPow = 1;
 483
 0484            if (current >= inputLength)
 485            {
 0486                return false;
 487            }
 488
 0489            var ch = input[current];
 490
 0491            if (ch >= '0' && ch <= '1') // Only values between 0 and 1 are accepted, according to RFC
 492            {
 0493                intPart = ch - '0';
 0494                current++;
 495            }
 496            else
 497            {
 498                // The RFC doesn't allow decimal values starting with dot. I.e. value ".123" is invalid. It must be in t
 499                // form "0.123".
 0500                return false;
 501            }
 502
 0503            if (current < inputLength)
 504            {
 0505                ch = input[current];
 506
 0507                if (ch >= '0' && ch <= '9')
 508                {
 509                    // The RFC accepts only one digit before the dot
 0510                    return false;
 511                }
 512
 0513                if (ch == '.')
 514                {
 0515                    current++;
 516
 0517                    while (current < inputLength)
 518                    {
 0519                        ch = input[current];
 0520                        if (ch >= '0' && ch <= '9')
 521                        {
 0522                            if (current >= limit)
 523                            {
 0524                                return false;
 525                            }
 526
 0527                            decPart = decPart * 10 + ch - '0';
 0528                            decPow *= 10;
 0529                            current++;
 530                        }
 531                        else
 532                        {
 533                            break;
 534                        }
 535                    }
 536                }
 537            }
 538
 0539            if (decPart != 0)
 540            {
 0541                quality = intPart + decPart / (double)decPow;
 542            }
 543            else
 544            {
 0545                quality = intPart;
 546            }
 547
 0548            if (quality > 1)
 549            {
 550                // reset quality
 0551                quality = 0;
 0552                return false;
 553            }
 554
 0555            length = current - startIndex;
 0556            return true;
 557        }
 558
 559        /// <summary>
 560        /// Converts the non-negative 64-bit numeric value to its equivalent string representation.
 561        /// </summary>
 562        /// <param name="value">
 563        /// The number to convert.
 564        /// </param>
 565        /// <returns>
 566        /// The string representation of the value of this instance, consisting of a sequence of digits ranging from 0 t
 567        /// </returns>
 568        public static unsafe string FormatNonNegativeInt64(long value)
 569        {
 0570            if (value < 0)
 571            {
 0572                throw new ArgumentOutOfRangeException(nameof(value), value, "The value to be formatted must be non-negat
 573            }
 574
 0575            var position = _int64MaxStringLength;
 0576            char* charBuffer = stackalloc char[_int64MaxStringLength];
 577
 578            do
 579            {
 580                // Consider using Math.DivRem() if available
 0581                var quotient = value / 10;
 0582                charBuffer[--position] = (char)(0x30 + (value - quotient * 10)); // 0x30 = '0'
 0583                value = quotient;
 584            }
 0585            while (value != 0);
 586
 0587            return new string(charBuffer, position, _int64MaxStringLength - position);
 588        }
 589
 590        public static bool TryParseDate(StringSegment input, out DateTimeOffset result)
 591        {
 0592            return HttpRuleParser.TryStringToDate(input, out result);
 593        }
 594
 595        /*
 596        public static string FormatDate(DateTimeOffset dateTime)
 597        {
 598            return FormatDate(dateTime, false);
 599        }
 600
 601        public static string FormatDate(DateTimeOffset dateTime, bool quoted)
 602        {
 603            if (quoted)
 604            {
 605                return string.Create(31, dateTime, (span, dt) =>
 606                {
 607                    span[0] = span[30] = '"';
 608                    dt.TryFormat(span.Slice(1), out _, "r");
 609                });
 610            }
 611
 612            return dateTime.ToString("r");
 613        }
 614        */
 615
 616        public static StringSegment RemoveQuotes(StringSegment input)
 617        {
 0618            if (IsQuoted(input))
 619            {
 0620                input = input.Subsegment(1, input.Length - 2);
 621            }
 0622            return input;
 623        }
 624
 625        public static bool IsQuoted(StringSegment input)
 626        {
 0627            return !StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1
 628        }
 629
 630        /*
 631        /// <summary>
 632        /// Given a quoted-string as defined by <see href="https://tools.ietf.org/html/rfc7230#section-3.2.6">the RFC sp
 633        /// removes quotes and unescapes backslashes and quotes. This assumes that the input is a valid quoted-string.
 634        /// </summary>
 635        /// <param name="input">The quoted-string to be unescaped.</param>
 636        /// <returns>An unescaped version of the quoted-string.</returns>
 637        public static StringSegment UnescapeAsQuotedString(StringSegment input)
 638        {
 639            input = RemoveQuotes(input);
 640
 641            // First pass to calculate the size of the string
 642            var backSlashCount = CountBackslashesForDecodingQuotedString(input);
 643
 644            if (backSlashCount == 0)
 645            {
 646                return input;
 647            }
 648
 649            return string.Create(input.Length - backSlashCount, input, (span, segment) =>
 650            {
 651                var spanIndex = 0;
 652                var spanLength = span.Length;
 653                for (var i = 0; i < segment.Length && (uint)spanIndex < (uint)spanLength; i++)
 654                {
 655                    int nextIndex = i + 1;
 656                    if ((uint)nextIndex < (uint)segment.Length && segment[i] == '\\')
 657                    {
 658                        // If there is an backslash character as the last character in the string,
 659                        // we will assume that it should be included literally in the unescaped string
 660                        // Ex: "hello\\" => "hello\\"
 661                        // Also, if a sender adds a quoted pair like '\\''n',
 662                        // we will assume it is over escaping and just add a n to the string.
 663                        // Ex: "he\\llo" => "hello"
 664                        span[spanIndex] = segment[nextIndex];
 665                        i++;
 666                    }
 667                    else
 668                    {
 669                        span[spanIndex] = segment[i];
 670                    }
 671
 672                    spanIndex++;
 673                }
 674            });
 675        }
 676        */
 677
 678        private static int CountBackslashesForDecodingQuotedString(StringSegment input)
 679        {
 0680            var numberBackSlashes = 0;
 0681            for (var i = 0; i < input.Length; i++)
 682            {
 0683                if (i < input.Length - 1 && input[i] == '\\')
 684                {
 685                    // If there is an backslash character as the last character in the string,
 686                    // we will assume that it should be included literally in the unescaped string
 687                    // Ex: "hello\\" => "hello\\"
 688                    // Also, if a sender adds a quoted pair like '\\''n',
 689                    // we will assume it is over escaping and just add a n to the string.
 690                    // Ex: "he\\llo" => "hello"
 0691                    if (input[i + 1] == '\\')
 692                    {
 693                        // Only count escaped backslashes once
 0694                        i++;
 695                    }
 0696                    numberBackSlashes++;
 697                }
 698            }
 0699            return numberBackSlashes;
 700        }
 701
 702        /*
 703        /// <summary>
 704        /// Escapes a <see cref="StringSegment"/> as a quoted-string, which is defined by
 705        /// <see href="https://tools.ietf.org/html/rfc7230#section-3.2.6">the RFC specification</see>.
 706        /// </summary>
 707        /// <remarks>
 708        /// This will add a backslash before each backslash and quote and add quotes
 709        /// around the input. Assumes that the input does not have quotes around it,
 710        /// as this method will add them. Throws if the input contains any invalid escape characters,
 711        /// as defined by rfc7230.
 712        /// </remarks>
 713        /// <param name="input">The input to be escaped.</param>
 714        /// <returns>An escaped version of the quoted-string.</returns>
 715        public static StringSegment EscapeAsQuotedString(StringSegment input)
 716        {
 717            // By calling this, we know that the string requires quotes around it to be a valid token.
 718            var backSlashCount = CountAndCheckCharactersNeedingBackslashesWhenEncoding(input);
 719
 720            // 2 for quotes
 721            return string.Create(input.Length + backSlashCount + 2, input, (span, segment) => {
 722                // Helps to elide the bounds check for span[0]
 723                span[span.Length - 1] = span[0] = '\"';
 724
 725                var spanIndex = 1;
 726                for (var i = 0; i < segment.Length; i++)
 727                {
 728                    if (segment[i] == '\\' || segment[i] == '\"')
 729                    {
 730                        span[spanIndex++] = '\\';
 731                    }
 732                    else if ((segment[i] <= 0x1F || segment[i] == 0x7F) && segment[i] != 0x09)
 733                    {
 734                        // Control characters are not allowed in a quoted-string, which include all characters
 735                        // below 0x1F (except for 0x09 (TAB)) and 0x7F.
 736                        throw new FormatException($"Invalid control character '{segment[i]}' in input.");
 737                    }
 738                    span[spanIndex++] = segment[i];
 739                }
 740            });
 741        }
 742        */
 743
 744        private static int CountAndCheckCharactersNeedingBackslashesWhenEncoding(StringSegment input)
 745        {
 0746            var numberOfCharactersNeedingEscaping = 0;
 0747            for (var i = 0; i < input.Length; i++)
 748            {
 0749                if (input[i] == '\\' || input[i] == '\"')
 750                {
 0751                    numberOfCharactersNeedingEscaping++;
 752                }
 753            }
 0754            return numberOfCharactersNeedingEscaping;
 755        }
 756
 757        internal static void ThrowIfReadOnly(bool isReadOnly)
 758        {
 0759            if (isReadOnly)
 760            {
 0761                throw new InvalidOperationException("The object cannot be modified because it is read-only.");
 762            }
 0763        }
 764    }
 765}