< Summary

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

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-0%100%
CreateTokenChars()-0%0%
IsTokenChar(...)-0%0%
GetTokenLength(...)-0%0%
GetWhitespaceLength(...)-0%0%
GetNumberLength(...)-0%0%
GetQuotedStringLength(...)-0%100%
GetQuotedPairLength(...)-0%0%
TryStringToDate(...)-0%100%
GetExpressionLength(...)-0%0%

File(s)

C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Blobs.Batch\src\Shared\HttpRuleParser.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.Diagnostics.Contracts;
 8using System.Globalization;
 9using System.Text;
 10
 11#pragma warning disable IDE0018 // Inline declaration
 12#pragma warning disable IDE0054 // Use compound assignment
 13#pragma warning disable IDE0059 // Unnecessary assignment
 14#pragma warning disable IDE1006 // Missing s_ prefix
 15
 16namespace Azure.Core.Http.Multipart
 17{
 18    internal static class HttpRuleParser
 19    {
 020        private static readonly bool[] TokenChars = CreateTokenChars();
 21        private const int MaxNestedCount = 5;
 022        private static readonly string[] DateFormats = new string[] {
 023            // "r", // RFC 1123, required output format but too strict for input
 024            "ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
 025            "ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
 026            "d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
 027            "d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
 028            "ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
 029            "ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
 030            "d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
 031            "d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone
 032
 033            "dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850, short year
 034            "dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
 035            "ddd, d'-'MMM'-'yyyy H:m:s 'GMT'", // RFC 850, long year
 036            "ddd MMM d H:m:s yyyy", // ANSI C's asctime() format
 037
 038            "ddd, d MMM yyyy H:m:s zzz", // RFC 5322
 039            "ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
 040            "d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
 041            "d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
 042        };
 43
 44        internal const char CR = '\r';
 45        internal const char LF = '\n';
 46        internal const char SP = ' ';
 47        internal const char Tab = '\t';
 48        internal const int MaxInt64Digits = 19;
 49        internal const int MaxInt32Digits = 10;
 50
 51        // iso-8859-1, Western European (ISO)
 052        internal static readonly Encoding DefaultHttpEncoding = Encoding.GetEncoding("iso-8859-1");
 53
 54        private static bool[] CreateTokenChars()
 55        {
 56            // token = 1*<any CHAR except CTLs or separators>
 57            // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
 58
 059            var tokenChars = new bool[128]; // everything is false
 60
 061            for (int i = 33; i < 127; i++) // skip Space (32) & DEL (127)
 62            {
 063                tokenChars[i] = true;
 64            }
 65
 66            // remove separators: these are not valid token characters
 067            tokenChars[(byte)'('] = false;
 068            tokenChars[(byte)')'] = false;
 069            tokenChars[(byte)'<'] = false;
 070            tokenChars[(byte)'>'] = false;
 071            tokenChars[(byte)'@'] = false;
 072            tokenChars[(byte)','] = false;
 073            tokenChars[(byte)';'] = false;
 074            tokenChars[(byte)':'] = false;
 075            tokenChars[(byte)'\\'] = false;
 076            tokenChars[(byte)'"'] = false;
 077            tokenChars[(byte)'/'] = false;
 078            tokenChars[(byte)'['] = false;
 079            tokenChars[(byte)']'] = false;
 080            tokenChars[(byte)'?'] = false;
 081            tokenChars[(byte)'='] = false;
 082            tokenChars[(byte)'{'] = false;
 083            tokenChars[(byte)'}'] = false;
 84
 085            return tokenChars;
 86        }
 87
 88        internal static bool IsTokenChar(char character)
 89        {
 90            // Must be between 'space' (32) and 'DEL' (127)
 091            if (character > 127)
 92            {
 093                return false;
 94            }
 95
 096            return TokenChars[character];
 97        }
 98
 99        [Pure]
 100        internal static int GetTokenLength(StringSegment input, int startIndex)
 101        {
 102            Contract.Requires(input != null);
 103            Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
 104
 0105            if (startIndex >= input.Length)
 106            {
 0107                return 0;
 108            }
 109
 0110            var current = startIndex;
 111
 0112            while (current < input.Length)
 113            {
 0114                if (!IsTokenChar(input[current]))
 115                {
 0116                    return current - startIndex;
 117                }
 0118                current++;
 119            }
 0120            return input.Length - startIndex;
 121        }
 122
 123        internal static int GetWhitespaceLength(StringSegment input, int startIndex)
 124        {
 125            Contract.Requires(input != null);
 126            Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
 127
 0128            if (startIndex >= input.Length)
 129            {
 0130                return 0;
 131            }
 132
 0133            var current = startIndex;
 134
 135            char c;
 0136            while (current < input.Length)
 137            {
 0138                c = input[current];
 139
 0140                if ((c == SP) || (c == Tab))
 141                {
 0142                    current++;
 0143                    continue;
 144                }
 145
 0146                if (c == CR)
 147                {
 148                    // If we have a #13 char, it must be followed by #10 and then at least one SP or HT.
 0149                    if ((current + 2 < input.Length) && (input[current + 1] == LF))
 150                    {
 0151                        char spaceOrTab = input[current + 2];
 0152                        if ((spaceOrTab == SP) || (spaceOrTab == Tab))
 153                        {
 0154                            current += 3;
 0155                            continue;
 156                        }
 157                    }
 158                }
 159
 0160                return current - startIndex;
 161            }
 162
 163            // All characters between startIndex and the end of the string are LWS characters.
 0164            return input.Length - startIndex;
 165        }
 166
 167        internal static int GetNumberLength(StringSegment input, int startIndex, bool allowDecimal)
 168        {
 169            Contract.Requires(input != null);
 170            Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
 171            Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
 172
 0173            var current = startIndex;
 174            char c;
 175
 176            // If decimal values are not allowed, we pretend to have read the '.' character already. I.e. if a dot is
 177            // found in the string, parsing will be aborted.
 0178            var haveDot = !allowDecimal;
 179
 180            // The RFC doesn't allow decimal values starting with dot. I.e. value ".123" is invalid. It must be in the
 181            // form "0.123". Also, there are no negative values defined in the RFC. So we'll just parse non-negative
 182            // values.
 183            // The RFC only allows decimal dots not ',' characters as decimal separators. Therefore value "1,23" is
 184            // considered invalid and must be represented as "1.23".
 0185            if (input[current] == '.')
 186            {
 0187                return 0;
 188            }
 189
 0190            while (current < input.Length)
 191            {
 0192                c = input[current];
 0193                if ((c >= '0') && (c <= '9'))
 194                {
 0195                    current++;
 196                }
 0197                else if (!haveDot && (c == '.'))
 198                {
 199                    // Note that value "1." is valid.
 0200                    haveDot = true;
 0201                    current++;
 202                }
 203                else
 204                {
 205                    break;
 206                }
 207            }
 208
 0209            return current - startIndex;
 210        }
 211
 212        internal static HttpParseResult GetQuotedStringLength(StringSegment input, int startIndex, out int length)
 213        {
 0214            var nestedCount = 0;
 0215            return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length);
 216        }
 217
 218        // quoted-pair = "\" CHAR
 219        // CHAR = <any US-ASCII character (octets 0 - 127)>
 220        internal static HttpParseResult GetQuotedPairLength(StringSegment input, int startIndex, out int length)
 221        {
 222            Contract.Requires(input != null);
 223            Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
 224            Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) &&
 225                (Contract.ValueAtReturn(out length) <= (input.Length - startIndex)));
 226
 0227            length = 0;
 228
 0229            if (input[startIndex] != '\\')
 230            {
 0231                return HttpParseResult.NotParsed;
 232            }
 233
 234            // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char)
 235            // If so, check whether the character is in the range 0-127. If not, it's an invalid value.
 0236            if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127))
 237            {
 0238                return HttpParseResult.InvalidFormat;
 239            }
 240
 241            // We don't care what the char next to '\' is.
 0242            length = 2;
 0243            return HttpParseResult.Parsed;
 244        }
 245
 246        // Try the various date formats in the order listed above.
 247        // We should accept a wide verity of common formats, but only output RFC 1123 style dates.
 248        internal static bool TryStringToDate(StringSegment input, out DateTimeOffset result) =>
 0249            DateTimeOffset.TryParseExact(input.ToString(), DateFormats, DateTimeFormatInfo.InvariantInfo,
 0250                DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal, out result);
 251
 252        // TEXT = <any OCTET except CTLs, but including LWS>
 253        // LWS = [CRLF] 1*( SP | HT )
 254        // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
 255        //
 256        // Since we don't really care about the content of a quoted string or comment, we're more tolerant and
 257        // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment).
 258        //
 259        // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like
 260        // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested
 261        // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any)
 262        // is unusual.
 263        private static HttpParseResult GetExpressionLength(
 264            StringSegment input,
 265            int startIndex,
 266            char openChar,
 267            char closeChar,
 268            bool supportsNesting,
 269            ref int nestedCount,
 270            out int length)
 271        {
 272            Contract.Requires(input != null);
 273            Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
 274            Contract.Ensures((Contract.Result<HttpParseResult>() != HttpParseResult.Parsed) ||
 275                (Contract.ValueAtReturn<int>(out length) > 0));
 276
 0277            length = 0;
 278
 0279            if (input[startIndex] != openChar)
 280            {
 0281                return HttpParseResult.NotParsed;
 282            }
 283
 0284            var current = startIndex + 1; // Start parsing with the character next to the first open-char
 0285            while (current < input.Length)
 286            {
 287                // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e.
 288                // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char.
 0289                var quotedPairLength = 0;
 0290                if ((current + 2 < input.Length) &&
 0291                    (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed))
 292                {
 293                    // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair,
 294                    // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only
 295                    // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars).
 0296                    current = current + quotedPairLength;
 0297                    continue;
 298                }
 299
 300                // If we support nested expressions and we find an open-char, then parse the nested expressions.
 0301                if (supportsNesting && (input[current] == openChar))
 302                {
 0303                    nestedCount++;
 304                    try
 305                    {
 306                        // Check if we exceeded the number of nested calls.
 0307                        if (nestedCount > MaxNestedCount)
 308                        {
 0309                            return HttpParseResult.InvalidFormat;
 310                        }
 311
 0312                        var nestedLength = 0;
 0313                        HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar,
 0314                            supportsNesting, ref nestedCount, out nestedLength);
 315
 316                        switch (nestedResult)
 317                        {
 318                            case HttpParseResult.Parsed:
 0319                                current += nestedLength; // add the length of the nested expression and continue.
 0320                                break;
 321
 322                            case HttpParseResult.NotParsed:
 323                                Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression " +
 324                                    "parsing, because we found the open-char. So either it's a valid nested " +
 325                                    "expression or it has invalid format.");
 326                                break;
 327
 328                            case HttpParseResult.InvalidFormat:
 329                                // If the nested expression is invalid, we can't continue, so we fail with invalid forma
 0330                                return HttpParseResult.InvalidFormat;
 331
 332                            default:
 333                                Contract.Assert(false, "Unknown enum result: " + nestedResult);
 334                                break;
 335                        }
 0336                    }
 337                    finally
 338                    {
 0339                        nestedCount--;
 0340                    }
 341                }
 342
 0343                if (input[current] == closeChar)
 344                {
 0345                    length = current - startIndex + 1;
 0346                    return HttpParseResult.Parsed;
 347                }
 0348                current++;
 349            }
 350
 351            // We didn't see the final quote, therefore we have an invalid expression string.
 0352            return HttpParseResult.InvalidFormat;
 0353        }
 354    }
 355}