internal static int GetEntityTagLength(string input, int startIndex, out EntityTagHeaderValue parsedValue)
        {
            Contract.Requires(startIndex >= 0);

            parsedValue = null;

            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return(0);
            }

            // Caller must remove leading whitespaces. If not, we'll return 0.
            bool isWeak  = false;
            int  current = startIndex;

            char firstChar = input[startIndex];

            if (firstChar == '*')
            {
                // We have '*' value, indicating "any" ETag.
                parsedValue = Any;
                current++;
            }
            else
            {
                // The RFC defines 'W/' as prefix, but we'll be flexible and also accept lower-case 'w'.
                if ((firstChar == 'W') || (firstChar == 'w'))
                {
                    current++;
                    // We need at least 3 more chars: the '/' character followed by two quotes.
                    if ((current + 2 >= input.Length) || (input[current] != '/'))
                    {
                        return(0);
                    }
                    isWeak = true;
                    current++; // we have a weak-entity tag.
                    current = current + HttpRuleParser.GetWhitespaceLength(input, current);
                }

                int tagStartIndex = current;
                int tagLength     = 0;
                if (HttpRuleParser.GetQuotedStringLength(input, current, out tagLength) != HttpParseResult.Parsed)
                {
                    return(0);
                }

                parsedValue = new EntityTagHeaderValue();
                if (tagLength == input.Length)
                {
                    // Most of the time we'll have strong ETags without leading/trailing whitespaces.
                    Contract.Assert(startIndex == 0);
                    Contract.Assert(!isWeak);
                    parsedValue.tag    = input;
                    parsedValue.isWeak = false;
                }
                else
                {
                    parsedValue.tag    = input.Substring(tagStartIndex, tagLength);
                    parsedValue.isWeak = isWeak;
                }

                current = current + tagLength;
            }
            current = current + HttpRuleParser.GetWhitespaceLength(input, current);

            return(current - startIndex);
        }
        internal static int GetContentRangeLength(string input, int startIndex, out object parsedValue)
        {
            Debug.Assert(startIndex >= 0);

            parsedValue = null;

            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return(0);
            }

            // Parse the unit string: <unit> in '<unit> <from>-<to>/<length>'
            int unitLength = HttpRuleParser.GetTokenLength(input, startIndex);

            if (unitLength == 0)
            {
                return(0);
            }

            string unit            = input.Substring(startIndex, unitLength);
            int    current         = startIndex + unitLength;
            int    separatorLength = HttpRuleParser.GetWhitespaceLength(input, current);

            if (separatorLength == 0)
            {
                return(0);
            }

            current = current + separatorLength;

            if (current == input.Length)
            {
                return(0);
            }

            // Read range values <from> and <to> in '<unit> <from>-<to>/<length>'
            int fromStartIndex = current;
            int fromLength     = 0;
            int toStartIndex   = 0;
            int toLength       = 0;

            if (!TryGetRangeLength(input, ref current, out fromLength, out toStartIndex, out toLength))
            {
                return(0);
            }

            // After the range is read we expect the length separator '/'
            if ((current == input.Length) || (input[current] != '/'))
            {
                return(0);
            }

            current++; // Skip '/' separator
            current = current + HttpRuleParser.GetWhitespaceLength(input, current);

            if (current == input.Length)
            {
                return(0);
            }

            // We may not have a length (e.g. 'bytes 1-2/*'). But if we do, parse the length now.
            int lengthStartIndex = current;
            int lengthLength     = 0;

            if (!TryGetLengthLength(input, ref current, out lengthLength))
            {
                return(0);
            }

            if (!TryCreateContentRange(input, unit, fromStartIndex, fromLength, toStartIndex, toLength,
                                       lengthStartIndex, lengthLength, out parsedValue))
            {
                return(0);
            }

            return(current - startIndex);
        }
Exemplo n.º 3
0
        internal static int GetViaLength(string input, int startIndex, out object parsedValue)
        {
            Contract.Requires(startIndex >= 0);

            parsedValue = null;

            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return 0;
            }

            // Read <protocolName> and <protocolVersion> in '[<protocolName>/]<protocolVersion> <receivedBy> [<comment>]'
            string protocolName = null;
            string protocolVersion = null;
            int current = GetProtocolEndIndex(input, startIndex, out protocolName, out protocolVersion);

            // If we reached the end of the string after reading protocolName/Version we return (we expect at least
            // <receivedBy> to follow). If reading protocolName/Version read 0 bytes, we return. 
            if ((current == startIndex) || (current == input.Length))
            {
                return 0;
            }
            Debug.Assert(protocolVersion != null);

            // Read <receivedBy> in '[<protocolName>/]<protocolVersion> <receivedBy> [<comment>]'
            string receivedBy = null;
            int receivedByLength = HttpRuleParser.GetHostLength(input, current, true, out receivedBy);

            if (receivedByLength == 0)
            {
                return 0;
            }

            current = current + receivedByLength;
            current = current + HttpRuleParser.GetWhitespaceLength(input, current);

            string comment = null;
            if ((current < input.Length) && (input[current] == '('))
            {
                // We have a <comment> in '[<protocolName>/]<protocolVersion> <receivedBy> [<comment>]'
                int commentLength = 0;
                if (HttpRuleParser.GetCommentLength(input, current, out commentLength) != HttpParseResult.Parsed)
                {
                    return 0; // We found a '(' character but it wasn't a valid comment. Abort.
                }

                comment = input.Substring(current, commentLength);

                current = current + commentLength;
                current = current + HttpRuleParser.GetWhitespaceLength(input, current);
            }

            ViaHeaderValue result = new ViaHeaderValue();
            result._protocolVersion = protocolVersion;
            result._protocolName = protocolName;
            result._receivedBy = receivedBy;
            result._comment = comment;

            parsedValue = result;
            return current - startIndex;
        }
Exemplo n.º 4
0
        private static int GetProtocolEndIndex(string input, int startIndex, out string?protocolName,
                                               out string?protocolVersion)
        {
            // We have a string of the form '[<protocolName>/]<protocolVersion> <receivedBy> [<comment>]'. The first
            // token may either be the protocol name or protocol version. We'll only find out after reading the token
            // and by looking at the following character: If it is a '/' we just parsed the protocol name, otherwise
            // the protocol version.
            protocolName    = null;
            protocolVersion = null;

            int current = startIndex;
            int protocolVersionOrNameLength = HttpRuleParser.GetTokenLength(input, current);

            if (protocolVersionOrNameLength == 0)
            {
                return(0);
            }

            current = startIndex + protocolVersionOrNameLength;
            int whitespaceLength = HttpRuleParser.GetWhitespaceLength(input, current);

            current = current + whitespaceLength;

            if (current == input.Length)
            {
                return(0);
            }

            if (input[current] == '/')
            {
                // We parsed the protocol name
                protocolName = input.Substring(startIndex, protocolVersionOrNameLength);

                current++; // skip the '/' delimiter
                current = current + HttpRuleParser.GetWhitespaceLength(input, current);

                protocolVersionOrNameLength = HttpRuleParser.GetTokenLength(input, current);

                if (protocolVersionOrNameLength == 0)
                {
                    return(0); // We have a string "<token>/" followed by non-token chars. This is invalid.
                }

                protocolVersion = input.Substring(current, protocolVersionOrNameLength);

                current          = current + protocolVersionOrNameLength;
                whitespaceLength = HttpRuleParser.GetWhitespaceLength(input, current);
                current          = current + whitespaceLength;
            }
            else
            {
                protocolVersion = input.Substring(startIndex, protocolVersionOrNameLength);
            }

            if (whitespaceLength == 0)
            {
                return(0); // We were able to parse [<protocolName>/]<protocolVersion> but it wasn't followed by a WS
            }

            return(current);
        }
Exemplo n.º 5
0
        internal static int GetRetryConditionLength(string?input, int startIndex, out object?parsedValue)
        {
            Debug.Assert(startIndex >= 0);

            parsedValue = null;

            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return(0);
            }

            int current = startIndex;

            // Caller must remove leading whitespace.
            DateTimeOffset date         = DateTimeOffset.MinValue;
            int            deltaSeconds = -1; // use -1 to indicate that the value was not set. 'delta' values are always >=0

            // We either have a timespan or a date/time value. Determine which one we have by looking at the first char.
            // If it is a number, we have a timespan, otherwise we assume we have a date.
            char firstChar = input[current];

            if ((firstChar >= '0') && (firstChar <= '9'))
            {
                int deltaStartIndex = current;
                int deltaLength     = HttpRuleParser.GetNumberLength(input, current, false);

                // The value must be in the range 0..2^31
                if ((deltaLength == 0) || (deltaLength > HttpRuleParser.MaxInt32Digits))
                {
                    return(0);
                }

                current = current + deltaLength;
                current = current + HttpRuleParser.GetWhitespaceLength(input, current);

                // RetryConditionHeaderValue only allows 1 value. There must be no delimiter/other chars after 'delta'
                if (current != input.Length)
                {
                    return(0);
                }

                if (!HeaderUtilities.TryParseInt32(input, deltaStartIndex, deltaLength, out deltaSeconds))
                {
                    return(0); // int.TryParse() may return 'false' if the value has 10 digits and is > Int32.MaxValue.
                }
            }
            else
            {
                if (!HttpDateParser.TryParse(input.AsSpan(current), out date))
                {
                    return(0);
                }

                // If we got a valid date, then the parser consumed the whole string (incl. trailing whitespace).
                current = input.Length;
            }

            RetryConditionHeaderValue result = new RetryConditionHeaderValue();

            if (deltaSeconds == -1) // we didn't change delta, so we must have found a date.
            {
                result._date = date;
            }
            else
            {
                result._delta = new TimeSpan(0, 0, deltaSeconds);
            }

            parsedValue = result;
            return(current - startIndex);
        }
Exemplo n.º 6
0
        internal static int GetRangeItemLength(string input, int startIndex, out RangeItemHeaderValue parsedValue)
        {
            Contract.Requires(startIndex >= 0);

            // This parser parses number ranges: e.g. '1-2', '1-', '-2'.

            parsedValue = null;

            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return(0);
            }

            // Caller must remove leading whitespaces. If not, we'll return 0.
            int current = startIndex;

            // Try parse the first value of a value pair.
            int fromStartIndex = current;
            int fromLength     = HttpRuleParser.GetNumberLength(input, current, false);

            if (fromLength > HttpRuleParser.MaxInt64Digits)
            {
                return(0);
            }

            current = current + fromLength;
            current = current + HttpRuleParser.GetWhitespaceLength(input, current);

            // Afer the first value, the '-' character must follow.
            if ((current == input.Length) || (input[current] != '-'))
            {
                // We need a '-' character otherwise this can't be a valid range.
                return(0);
            }

            current++; // skip the '-' character
            current = current + HttpRuleParser.GetWhitespaceLength(input, current);

            int toStartIndex = current;
            int toLength     = 0;

            // If we didn't reach the end of the string, try parse the second value of the range.
            if (current < input.Length)
            {
                toLength = HttpRuleParser.GetNumberLength(input, current, false);

                if (toLength > HttpRuleParser.MaxInt64Digits)
                {
                    return(0);
                }

                current = current + toLength;
                current = current + HttpRuleParser.GetWhitespaceLength(input, current);
            }

            if ((fromLength == 0) && (toLength == 0))
            {
                return(0); // At least one value must be provided in order to be a valid range.
            }

            // Try convert first value to int64
            long from = 0;

            if ((fromLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(fromStartIndex, fromLength), out from))
            {
                return(0);
            }

            // Try convert second value to int64
            long to = 0;

            if ((toLength > 0) && !HeaderUtilities.TryParseInt64(input.Substring(toStartIndex, toLength), out to))
            {
                return(0);
            }

            // 'from' must not be greater than 'to'
            if ((fromLength > 0) && (toLength > 0) && (from > to))
            {
                return(0);
            }

            parsedValue = new RangeItemHeaderValue((fromLength == 0 ? (long?)null : (long?)from),
                                                   (toLength == 0 ? (long?)null : (long?)to));
            return(current - startIndex);
        }
        internal static int GetAuthenticationLength(string?input, int startIndex, out object?parsedValue)
        {
            Debug.Assert(startIndex >= 0);

            parsedValue = null;

            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return(0);
            }

            // Parse the scheme string: <scheme> in '<scheme> <parameter>'
            int schemeLength = HttpRuleParser.GetTokenLength(input, startIndex);

            if (schemeLength == 0)
            {
                return(0);
            }

            var    result       = new AuthenticationHeaderValue();
            string?targetScheme = null;

            switch (schemeLength)
            {
            // Avoid allocating a scheme string for the most common cases.
            case 5: targetScheme = "Basic"; break;

            case 6: targetScheme = "Digest"; break;

            case 4: targetScheme = "NTLM"; break;

            case 9: targetScheme = "Negotiate"; break;
            }
            result._scheme = targetScheme != null && string.CompareOrdinal(input, startIndex, targetScheme, 0, schemeLength) == 0 ?
                             targetScheme :
                             input.Substring(startIndex, schemeLength);

            int current          = startIndex + schemeLength;
            int whitespaceLength = HttpRuleParser.GetWhitespaceLength(input, current);

            current = current + whitespaceLength;

            if ((current == input.Length) || (input[current] == ','))
            {
                // If we only have a scheme followed by whitespace, we're done.
                parsedValue = result;
                return(current - startIndex);
            }

            // We need at least one space between the scheme and parameters. If there is no whitespace, then we must
            // have reached the end of the string (i.e. scheme-only string).
            if (whitespaceLength == 0)
            {
                return(0);
            }

            // If we get here, we have a <scheme> followed by a whitespace. Now we expect the following:
            // '<scheme> <blob>[,<name>=<value>]*[, <otherscheme>...]*': <blob> potentially contains one
            // or more '=' characters, optionally followed by additional name/value pairs, optionally followed by
            // other schemes. <blob> may be a quoted string.
            // We look at the value after ',': if it is <token>=<value> then we have a parameter for <scheme>.
            // If we have either a <token>-only or <token><whitespace><blob> then we have another scheme.
            int parameterStartIndex = current;
            int parameterEndIndex   = current;

            if (!TrySkipFirstBlob(input, ref current, ref parameterEndIndex))
            {
                return(0);
            }

            if (current < input.Length)
            {
                if (!TryGetParametersEndIndex(input, ref current, ref parameterEndIndex))
                {
                    return(0);
                }
            }

            result._parameter = input.Substring(parameterStartIndex, parameterEndIndex - parameterStartIndex + 1);
            parsedValue       = result;
            return(current - startIndex);
        }
Exemplo n.º 8
0
    // name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax|None}; httponly
    private static int GetSetCookieLength(StringSegment input, int startIndex, out SetCookieHeaderValue?parsedValue)
    {
        Contract.Requires(startIndex >= 0);
        var offset = startIndex;

        parsedValue = null;

        if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
        {
            return(0);
        }

        var result = new SetCookieHeaderValue();

        // The caller should have already consumed any leading whitespace, commas, etc..

        // Name=value;

        // Name
        var itemLength = HttpRuleParser.GetTokenLength(input, offset);

        if (itemLength == 0)
        {
            return(0);
        }
        result._name = input.Subsegment(offset, itemLength);
        offset      += itemLength;

        // = (no spaces)
        if (!ReadEqualsSign(input, ref offset))
        {
            return(0);
        }

        // value or "quoted value"
        // The value may be empty
        result._value = CookieHeaderParserShared.GetCookieValue(input, ref offset);

        // *(';' SP cookie-av)
        while (offset < input.Length)
        {
            if (input[offset] == ',')
            {
                // Divider between headers
                break;
            }
            if (input[offset] != ';')
            {
                // Expecting a ';' between parameters
                return(0);
            }
            offset++;

            offset += HttpRuleParser.GetWhitespaceLength(input, offset);

            //  cookie-av = expires-av / max-age-av / domain-av / path-av / secure-av / samesite-av / httponly-av / extension-av
            itemLength = HttpRuleParser.GetTokenLength(input, offset);
            if (itemLength == 0)
            {
                // Trailing ';' or leading into garbage. Let the next parser fail.
                break;
            }
            var token = input.Subsegment(offset, itemLength);
            offset += itemLength;

            //  expires-av = "Expires=" sane-cookie-date
            if (StringSegment.Equals(token, ExpiresToken, StringComparison.OrdinalIgnoreCase))
            {
                // = (no spaces)
                if (!ReadEqualsSign(input, ref offset))
                {
                    return(0);
                }
                // We don't want to include comma, becouse date may contain it (eg. Sun, 06 Nov...)
                var            dateString = ReadToSemicolonOrEnd(input, ref offset, includeComma: false);
                DateTimeOffset expirationDate;
                if (!HttpRuleParser.TryStringToDate(dateString, out expirationDate))
                {
                    // Invalid expiration date, abort
                    return(0);
                }
                result.Expires = expirationDate;
            }
            // max-age-av = "Max-Age=" non-zero-digit *DIGIT
            else if (StringSegment.Equals(token, MaxAgeToken, StringComparison.OrdinalIgnoreCase))
            {
                // = (no spaces)
                if (!ReadEqualsSign(input, ref offset))
                {
                    return(0);
                }

                itemLength = HttpRuleParser.GetNumberLength(input, offset, allowDecimal: false);
                if (itemLength == 0)
                {
                    return(0);
                }
                var  numberString = input.Subsegment(offset, itemLength);
                long maxAge;
                if (!HeaderUtilities.TryParseNonNegativeInt64(numberString, out maxAge))
                {
                    // Invalid expiration date, abort
                    return(0);
                }
                result.MaxAge = TimeSpan.FromSeconds(maxAge);
                offset       += itemLength;
            }
            // domain-av = "Domain=" domain-value
            // domain-value = <subdomain> ; defined in [RFC1034], Section 3.5, as enhanced by [RFC1123], Section 2.1
            else if (StringSegment.Equals(token, DomainToken, StringComparison.OrdinalIgnoreCase))
            {
                // = (no spaces)
                if (!ReadEqualsSign(input, ref offset))
                {
                    return(0);
                }
                // We don't do any detailed validation on the domain.
                result.Domain = ReadToSemicolonOrEnd(input, ref offset);
            }
            // path-av = "Path=" path-value
            // path-value = <any CHAR except CTLs or ";">
            else if (StringSegment.Equals(token, PathToken, StringComparison.OrdinalIgnoreCase))
            {
                // = (no spaces)
                if (!ReadEqualsSign(input, ref offset))
                {
                    return(0);
                }
                // We don't do any detailed validation on the path.
                result.Path = ReadToSemicolonOrEnd(input, ref offset);
            }
            // secure-av = "Secure"
            else if (StringSegment.Equals(token, SecureToken, StringComparison.OrdinalIgnoreCase))
            {
                result.Secure = true;
            }
            // samesite-av = "SameSite=" samesite-value
            // samesite-value = "Strict" / "Lax" / "None"
            else if (StringSegment.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase))
            {
                if (!ReadEqualsSign(input, ref offset))
                {
                    result.SameSite = SameSiteMode.Unspecified;
                }
                else
                {
                    var enforcementMode = ReadToSemicolonOrEnd(input, ref offset);

                    if (StringSegment.Equals(enforcementMode, SameSiteStrictToken, StringComparison.OrdinalIgnoreCase))
                    {
                        result.SameSite = SameSiteMode.Strict;
                    }
                    else if (StringSegment.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
                    {
                        result.SameSite = SameSiteMode.Lax;
                    }
                    else if (StringSegment.Equals(enforcementMode, SameSiteNoneToken, StringComparison.OrdinalIgnoreCase))
                    {
                        result.SameSite = SameSiteMode.None;
                    }
                    else
                    {
                        result.SameSite = SameSiteMode.Unspecified;
                    }
                }
            }
            // httponly-av = "HttpOnly"
            else if (StringSegment.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase))
            {
                result.HttpOnly = true;
            }
            // extension-av = <any CHAR except CTLs or ";">
            else
            {
                var tokenStart = offset - itemLength;
                ReadToSemicolonOrEnd(input, ref offset, includeComma: true);
                result.Extensions.Add(input.Subsegment(tokenStart, offset - tokenStart));
            }
        }

        parsedValue = result;
        return(offset - startIndex);
    }
Exemplo n.º 9
0
        internal static int GetAuthenticationLength(string input, int startIndex, out object parsedValue)
        {
            Contract.Requires(startIndex >= 0);

            parsedValue = null;

            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return(0);
            }

            // Parse the scheme string: <scheme> in '<scheme> <parameter>'
            int schemeLength = HttpRuleParser.GetTokenLength(input, startIndex);

            if (schemeLength == 0)
            {
                return(0);
            }

            AuthenticationHeaderValue result = new AuthenticationHeaderValue();

            result._scheme = input.Substring(startIndex, schemeLength);

            int current          = startIndex + schemeLength;
            int whitespaceLength = HttpRuleParser.GetWhitespaceLength(input, current);

            current = current + whitespaceLength;

            if ((current == input.Length) || (input[current] == ','))
            {
                // If we only have a scheme followed by whitespace, we're done.
                parsedValue = result;
                return(current - startIndex);
            }

            // We need at least one space between the scheme and parameters. If there are no whitespace, then we must
            // have reached the end of the string (i.e. scheme-only string).
            if (whitespaceLength == 0)
            {
                return(0);
            }

            // If we get here, we have a <scheme> followed by a whitespace. Now we expect the following:
            // '<scheme> <blob>[,<name>=<value>]*[, <otherscheme>...]*': <blob> potentially contains one
            // or more '=' characters, optionally followed by additional name/value pairs, optionally followed by
            // other schemes. <blob> may be a quoted string.
            // We look at the value after ',': if it is <token>=<value> then we have a parameter for <scheme>.
            // If we have either a <token>-only or <token><whitespace><blob> then we have another scheme.
            int parameterStartIndex = current;
            int parameterEndIndex   = current;

            if (!TrySkipFirstBlob(input, ref current, ref parameterEndIndex))
            {
                return(0);
            }

            if (current < input.Length)
            {
                if (!TryGetParametersEndIndex(input, ref current, ref parameterEndIndex))
                {
                    return(0);
                }
            }

            result._parameter = input.Substring(parameterStartIndex, parameterEndIndex - parameterStartIndex + 1);
            parsedValue       = result;
            return(current - startIndex);
        }
Exemplo n.º 10
0
        internal static int GetNextNonEmptyOrWhitespaceIndex(
            string input,
            int startIndex,
            bool skipEmptyValues,
            out bool separatorFound)
        {
            separatorFound = false;
            int index1 = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);

            if (index1 == input.Length || input[index1] != ',')
            {
                return(index1);
            }
            separatorFound = true;
            int startIndex1 = index1 + 1;
            int index2      = startIndex1 + HttpRuleParser.GetWhitespaceLength(input, startIndex1);
            int startIndex2;

            if (skipEmptyValues)
            {
                for (; index2 < input.Length && input[index2] == ','; index2 = startIndex2 + HttpRuleParser.GetWhitespaceLength(input, startIndex2))
                {
                    startIndex2 = index2 + 1;
                }
            }
            return(index2);
        }
Exemplo n.º 11
0
    private static int GetContentRangeLength(StringSegment input, int startIndex, out ContentRangeHeaderValue?parsedValue)
    {
        Contract.Requires(startIndex >= 0);

        parsedValue = null;

        if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
        {
            return(0);
        }

        // Parse the unit string: <unit> in '<unit> <from>-<to>/<length>'
        var unitLength = HttpRuleParser.GetTokenLength(input, startIndex);

        if (unitLength == 0)
        {
            return(0);
        }

        var unit            = input.Subsegment(startIndex, unitLength);
        var current         = startIndex + unitLength;
        var separatorLength = HttpRuleParser.GetWhitespaceLength(input, current);

        if (separatorLength == 0)
        {
            return(0);
        }

        current = current + separatorLength;

        if (current == input.Length)
        {
            return(0);
        }

        // Read range values <from> and <to> in '<unit> <from>-<to>/<length>'
        var fromStartIndex = current;

        if (!TryGetRangeLength(input, ref current, out var fromLength, out var toStartIndex, out var toLength))
        {
            return(0);
        }

        // After the range is read we expect the length separator '/'
        if ((current == input.Length) || (input[current] != '/'))
        {
            return(0);
        }

        current++; // Skip '/' separator
        current = current + HttpRuleParser.GetWhitespaceLength(input, current);

        if (current == input.Length)
        {
            return(0);
        }

        // We may not have a length (e.g. 'bytes 1-2/*'). But if we do, parse the length now.
        var lengthStartIndex = current;

        if (!TryGetLengthLength(input, ref current, out var lengthLength))
        {
            return(0);
        }

        if (!TryCreateContentRange(input, unit, fromStartIndex, fromLength, toStartIndex, toLength,
                                   lengthStartIndex, lengthLength, out parsedValue))
        {
            return(0);
        }

        return(current - startIndex);
    }