/// <summary>
    /// Initializes a new instance of the <see cref="EntityTagHeaderValue"/>.
    /// </summary>
    /// <param name="tag">A <see cref="StringSegment"/> that contains an <see cref="EntityTagHeaderValue"/>.</param>
    /// <param name="isWeak">A value that indicates if this entity-tag header is a weak validator.</param>
    public EntityTagHeaderValue(StringSegment tag, bool isWeak)
    {
        if (StringSegment.IsNullOrEmpty(tag))
        {
            throw new ArgumentException("An empty string is not allowed.", nameof(tag));
        }

        if (!isWeak && StringSegment.Equals(tag, "*", StringComparison.Ordinal))
        {
            // * is valid, but W/* isn't.
            _tag = tag;
        }
        else if ((HttpRuleParser.GetQuotedStringLength(tag, 0, out var length) != HttpParseResult.Parsed) ||
                 (length != tag.Length))
        {
            // Note that we don't allow 'W/' prefixes for weak ETags in the 'tag' parameter. If the user wants to
            // add a weak ETag, they can set 'isWeak' to true.
            throw new FormatException("Invalid ETag name");
        }

        _tag    = tag;
        _isWeak = isWeak;
    }
Example #2
0
        // Encode a string using RFC 5987 encoding.
        // encoding'lang'PercentEncodedSpecials
        internal static string Encode5987(string input)
        {
            // Encode a string using RFC 5987 encoding.
            // encoding'lang'PercentEncodedSpecials
            var builder = new ValueStringBuilder(stackalloc char[256]);

            byte[] utf8bytes = ArrayPool <byte> .Shared.Rent(Encoding.UTF8.GetMaxByteCount(input.Length));

            int utf8length = Encoding.UTF8.GetBytes(input, 0, input.Length, utf8bytes, 0);

            builder.Append("utf-8\'\'");
            for (int i = 0; i < utf8length; i++)
            {
                byte utf8byte = utf8bytes[i];

                // attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
                //      ; token except ( "*" / "'" / "%" )
                if (utf8byte > 0x7F) // Encodes as multiple utf-8 bytes
                {
                    AddHexEscaped(utf8byte, ref builder);
                }
                else if (!HttpRuleParser.IsTokenChar((char)utf8byte) || utf8byte == '*' || utf8byte == '\'' || utf8byte == '%')
                {
                    // ASCII - Only one encoded byte.
                    AddHexEscaped(utf8byte, ref builder);
                }
                else
                {
                    builder.Append((char)utf8byte);
                }
            }

            Array.Clear(utf8bytes, 0, utf8length);
            ArrayPool <byte> .Shared.Return(utf8bytes);

            return(builder.ToString());
        }
Example #3
0
        /// <summary>
        /// Check if a target directive exists among the set of given cache control directives.
        /// </summary>
        /// <param name="cacheControlDirectives">
        /// The <see cref="StringValues"/> containing the set of cache control directives.
        /// </param>
        /// <param name="targetDirectives">
        /// The target cache control directives to look for.
        /// </param>
        /// <returns>
        /// <code>true</code> if <paramref name="targetDirectives"/> is contained in <paramref name="cacheControlDirectives"/>;
        /// otherwise, <code>false</code>.
        /// </returns>
        public static bool ContainsCacheDirective(StringValues cacheControlDirectives, string targetDirectives)
        {
            if (StringValues.IsNullOrEmpty(cacheControlDirectives) || string.IsNullOrEmpty(targetDirectives))
            {
                return(false);
            }

            for (var i = 0; i < cacheControlDirectives.Count; i++)
            {
                // Trim leading white space
                var current = HttpRuleParser.GetWhitespaceLength(cacheControlDirectives[i], 0);

                while (current < cacheControlDirectives[i].Length)
                {
                    var initial = current;

                    var tokenLength = HttpRuleParser.GetTokenLength(cacheControlDirectives[i], current);
                    if (tokenLength == targetDirectives.Length &&
                        string.Compare(cacheControlDirectives[i], current, targetDirectives, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        // Token matches target value
                        return(true);
                    }

                    current = AdvanceCacheDirectiveIndex(current + tokenLength, cacheControlDirectives[i]);

                    // Ensure index was advanced
                    if (current <= initial)
                    {
                        Debug.Assert(false, $"Index '{nameof(current)}' not advanced, this is a bug.");
                        return(false);
                    }
                }
            }

            return(false);
        }
Example #4
0
        public KnownHeader(string name, HttpHeaderType headerType, HttpHeaderParser parser, string[] knownValues = null, int?http2StaticTableIndex = null)
        {
            Debug.Assert(!string.IsNullOrEmpty(name));
            Debug.Assert(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
            Debug.Assert((headerType == HttpHeaderType.Custom) == (parser == null));
            Debug.Assert(knownValues == null || headerType != HttpHeaderType.Custom);

            Name        = name;
            HeaderType  = headerType;
            Parser      = parser;
            KnownValues = knownValues;

            Http2EncodedName = http2StaticTableIndex.HasValue ?
                               HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(http2StaticTableIndex.GetValueOrDefault()) :
                               HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewNameToAllocatedArray(name);

            var asciiBytesWithColonSpace = new byte[name.Length + 2]; // + 2 for ':' and ' '
            int asciiBytes = Encoding.ASCII.GetBytes(name, asciiBytesWithColonSpace);

            Debug.Assert(asciiBytes == name.Length);
            asciiBytesWithColonSpace[asciiBytesWithColonSpace.Length - 2] = (byte)':';
            asciiBytesWithColonSpace[asciiBytesWithColonSpace.Length - 1] = (byte)' ';
            AsciiBytesWithColonSpace = asciiBytesWithColonSpace;
        }
        private static bool TryReadAgent(string input, int startIndex, ref int current, out string agent)
        {
            agent = null;

            int agentLength = HttpRuleParser.GetHostLength(input, startIndex, true, out agent);

            if (agentLength == 0)
            {
                return(false);
            }

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

            current = current + whitespaceLength;

            // At least one whitespace required after <agent>. Also make sure we have characters left for <text>
            if ((whitespaceLength == 0) || (current == input.Length))
            {
                return(false);
            }

            return(true);
        }
        private static void CheckValueFormat(string?value)
        {
            // Either value is null/empty or a valid token/quoted string https://tools.ietf.org/html/rfc7230#section-3.2.6
            if (string.IsNullOrEmpty(value))
            {
                return;
            }

            // Trailing/leading space are not allowed
            if (value[0] == ' ' || value[0] == '\t' || value[^ 1] == ' ' || value[^ 1] == '\t')
            {
                throw new FormatException(SR.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_headers_invalid_value, value));
            }

            // If it's not a token we check if it's a valid quoted string
            if (HttpRuleParser.GetTokenLength(value, 0) == 0)
            {
                HttpParseResult parseResult = HttpRuleParser.GetQuotedStringLength(value, 0, out int valueLength);
                if ((parseResult == HttpParseResult.Parsed && valueLength != value.Length) || parseResult != HttpParseResult.Parsed)
                {
                    throw new FormatException(SR.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_headers_invalid_value, value));
                }
            }
        }
    // Returns input for decoding failures, as the content might not be encoded
    private StringSegment EncodeAndQuoteMime(StringSegment input)
    {
        var result      = input;
        var needsQuotes = false;

        // Remove bounding quotes, they'll get re-added later
        if (IsQuoted(result))
        {
            result      = result.Subsegment(1, result.Length - 2);
            needsQuotes = true;
        }

        if (RequiresEncoding(result))
        {
            // EncodeMimeWithQuotes will Base64 encode any quotes in the input, and surround the payload in quotes
            // so there is no need to add quotes
            needsQuotes = false;
            result      = EncodeMimeWithQuotes(result); // "=?utf-8?B?asdfasdfaesdf?="
        }
        else if (!needsQuotes && HttpRuleParser.GetTokenLength(result, 0) != result.Length)
        {
            needsQuotes = true;
        }

        if (needsQuotes)
        {
            if (result.IndexOfAny(EscapeChars) != -1)
            {
                // '\' and '"' must be escaped in a quoted string
                result = result.ToString().Replace(@"\", @"\\").Replace(@"""", @"\""");
            }
            // Re-add quotes "value"
            result = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", result);
        }
        return(result);
    }
Example #8
0
        internal static int GetDispositionTypeLength(
            string input,
            int startIndex,
            out object parsedValue)
        {
            parsedValue = (object)null;
            if (string.IsNullOrEmpty(input) || startIndex >= input.Length)
            {
                return(0);
            }
            string dispositionType  = (string)null;
            int    expressionLength = ContentDispositionHeaderValue.GetDispositionTypeExpressionLength(input, startIndex, out dispositionType);

            if (expressionLength == 0)
            {
                return(0);
            }
            int startIndex1 = startIndex + expressionLength;
            int index       = startIndex1 + HttpRuleParser.GetWhitespaceLength(input, startIndex1);
            ContentDispositionHeaderValue dispositionHeaderValue = new ContentDispositionHeaderValue();

            dispositionHeaderValue._dispositionType = dispositionType;
            if (index < input.Length && input[index] == ';')
            {
                int startIndex2         = index + 1;
                int nameValueListLength = NameValueHeaderValue.GetNameValueListLength(input, startIndex2, ';', (ObjectCollection <NameValueHeaderValue>)dispositionHeaderValue.Parameters);
                if (nameValueListLength == 0)
                {
                    return(0);
                }
                parsedValue = (object)dispositionHeaderValue;
                return(startIndex2 + nameValueListLength - startIndex);
            }
            parsedValue = (object)dispositionHeaderValue;
            return(index - startIndex);
        }
Example #9
0
        internal static bool IsInputEncoded5987(string input, out string output)
        {
            // Encode a string using RFC 5987 encoding.
            // encoding'lang'PercentEncodedSpecials
            bool          wasEncoded = false;
            StringBuilder builder    = StringBuilderCache.Acquire();

            builder.Append("utf-8\'\'");
            foreach (char c in input)
            {
                // attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
                //      ; token except ( "*" / "'" / "%" )
                if (c > 0x7F) // Encodes as multiple utf-8 bytes
                {
                    byte[] bytes = Encoding.UTF8.GetBytes(c.ToString());
                    foreach (byte b in bytes)
                    {
                        AddHexEscaped((char)b, builder);
                        wasEncoded = true;
                    }
                }
                else if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%')
                {
                    // ASCII - Only one encoded byte.
                    AddHexEscaped(c, builder);
                    wasEncoded = true;
                }
                else
                {
                    builder.Append(c);
                }
            }

            output = StringBuilderCache.GetStringAndRelease(builder);
            return(wasEncoded);
        }
Example #10
0
        private static bool TryGetLengthLength(string input, ref int current, out int lengthLength)
        {
            lengthLength = 0;

            if (input[current] == '*')
            {
                current++;
            }
            else
            {
                // Parse length value: <length> in '<unit> <from>-<to>/<length>'
                lengthLength = HttpRuleParser.GetNumberLength(input, current, false);

                if ((lengthLength == 0) || (lengthLength > HttpRuleParser.MaxInt64Digits))
                {
                    return(false);
                }

                current = current + lengthLength;
            }

            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
            return(true);
        }
Example #11
0
    private static int GetRangeConditionLength(StringSegment input, int startIndex, out RangeConditionHeaderValue?parsedValue)
    {
        Contract.Requires(startIndex >= 0);

        parsedValue = null;

        // Make sure we have at least 2 characters
        if (StringSegment.IsNullOrEmpty(input) || (startIndex + 1 >= input.Length))
        {
            return(0);
        }

        var current = startIndex;

        // Caller must remove leading whitespaces.
        DateTimeOffset       date      = DateTimeOffset.MinValue;
        EntityTagHeaderValue?entityTag = null;

        // Entity tags are quoted strings optionally preceded by "W/". By looking at the first two character we
        // can determine whether the string is en entity tag or a date.
        var firstChar  = input[current];
        var secondChar = input[current + 1];

        if ((firstChar == '\"') || (((firstChar == 'w') || (firstChar == 'W')) && (secondChar == '/')))
        {
            // trailing whitespaces are removed by GetEntityTagLength()
            var entityTagLength = EntityTagHeaderValue.GetEntityTagLength(input, current, out entityTag);

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

            current = current + entityTagLength;

            // RangeConditionHeaderValue only allows 1 value. There must be no delimiter/other chars after an
            // entity tag.
            if (current != input.Length)
            {
                return(0);
            }
        }
        else
        {
            if (!HttpRuleParser.TryStringToDate(input.Subsegment(current), out date))
            {
                return(0);
            }

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

        parsedValue = new RangeConditionHeaderValue();
        if (entityTag == null)
        {
            parsedValue._lastModified = date;
        }
        else
        {
            parsedValue._entityTag = entityTag;
        }

        return(current - startIndex);
    }
Example #12
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);
        }
Example #13
0
        internal static int GetViaLength(string input, int startIndex, out object parsedValue)
        {
            Debug.Assert(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);
        }
        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);
        }
        internal static int GetRangeItemLength(string?input, int startIndex, out RangeItemHeaderValue?parsedValue)
        {
            Debug.Assert(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 whitespace. 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);

            // After 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, fromStartIndex, fromLength, out from))
            {
                return(0);
            }

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

            if ((toLength > 0) && !HeaderUtilities.TryParseInt64(input, 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);
        }
Example #16
0
 public void IsTokenChar_ValidTokenChars_ConsideredValid(char token)
 {
     Assert.True(HttpRuleParser.IsTokenChar(token));
 }
Example #17
0
 public void GetNumberLength_SetOfInvalidNumbers_ReturnsZero()
 {
     Assert.Equal(0, HttpRuleParser.GetNumberLength(".456", 0, true));
     Assert.Equal(0, HttpRuleParser.GetNumberLength("-1", 0, true));
     Assert.Equal(0, HttpRuleParser.GetNumberLength("a", 0, true));
 }
Example #18
0
        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.
                    Debug.Assert(startIndex == 0);
                    Debug.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);
        }
        protected override int GetParsedValueLength(string value, int startIndex, object?storeValue,
                                                    out object?parsedValue)
        {
            Debug.Assert(startIndex >= 0);
            Debug.Assert(startIndex < value.Length);

            if (string.IsNullOrEmpty(value))
            {
                parsedValue = null;
                return(0);
            }

            int idx = startIndex;

            if (!TryReadPercentEncodedAlpnProtocolName(value, idx, out string?alpnProtocolName, out int alpnProtocolNameLength))
            {
                parsedValue = null;
                return(0);
            }

            idx += alpnProtocolNameLength;

            if (alpnProtocolName == "clear")
            {
                if (idx != value.Length)
                {
                    // Clear has no parameters and should be the only Alt-Svc value present, so there should be nothing after it.
                    parsedValue = null;
                    return(0);
                }

                parsedValue = AltSvcHeaderValue.Clear;
                return(idx - startIndex);
            }

            if (idx == value.Length || value[idx++] != '=')
            {
                parsedValue = null;
                return(0);
            }

            if (!TryReadQuotedAltAuthority(value, idx, out string?altAuthorityHost, out int altAuthorityPort, out int altAuthorityLength))
            {
                parsedValue = null;
                return(0);
            }
            idx += altAuthorityLength;

            // Parse parameters: *( OWS ";" OWS parameter )
            int? maxAge  = null;
            bool persist = false;

            while (idx != value.Length)
            {
                // Skip OWS before semicolon.
                while (idx != value.Length && IsOptionalWhiteSpace(value[idx]))
                {
                    ++idx;
                }

                if (idx == value.Length)
                {
                    parsedValue = null;
                    return(0);
                }

                char ch = value[idx];

                if (ch == ',')
                {
                    // Multi-value header: return this value; will get called again to parse the next.
                    break;
                }

                if (ch != ';')
                {
                    // Expecting parameters starting with semicolon; fail out.
                    parsedValue = null;
                    return(0);
                }

                ++idx;

                // Skip OWS after semicolon / before value.
                while (idx != value.Length && IsOptionalWhiteSpace(value[idx]))
                {
                    ++idx;
                }

                // Get the parameter key length.
                int tokenLength = HttpRuleParser.GetTokenLength(value, idx);
                if (tokenLength == 0)
                {
                    parsedValue = null;
                    return(0);
                }

                if ((idx + tokenLength) >= value.Length || value[idx + tokenLength] != '=')
                {
                    parsedValue = null;
                    return(0);
                }

                if (tokenLength == 2 && value[idx] == 'm' && value[idx + 1] == 'a')
                {
                    // Parse "ma" (Max Age).

                    idx += 3; // Skip "ma="
                    if (!TryReadTokenOrQuotedInt32(value, idx, out int maxAgeTmp, out int parameterLength))
                    {
                        parsedValue = null;
                        return(0);
                    }

                    if (maxAge == null)
                    {
                        maxAge = maxAgeTmp;
                    }
                    else
                    {
                        // RFC makes it unclear what to do if a duplicate parameter is found. For now, take the minimum.
                        maxAge = Math.Min(maxAge.GetValueOrDefault(), maxAgeTmp);
                    }

                    idx += parameterLength;
                }
                else if (value.AsSpan(idx).StartsWith("persist="))
                {
                    idx += 8; // Skip "persist="
                    if (TryReadTokenOrQuotedInt32(value, idx, out int persistInt, out int parameterLength))
                    {
                        persist = persistInt == 1;
                    }
                    else if (!TrySkipTokenOrQuoted(value, idx, out parameterLength))
                    {
                        // Cold path: unsupported value, just skip the parameter.
                        parsedValue = null;
                        return(0);
                    }

                    idx += parameterLength;
                }
Example #20
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 whitespaces, we're done.
                parsedValue = result;
                return(current - startIndex);
            }

            // We need at least one space between the scheme and parameters. If there are no whitespaces, 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);
        }
Example #21
0
 public KnownHeader(string name, int?http2StaticTableIndex = null, int?http3StaticTableIndex = null) :
     this(name, HttpHeaderType.Custom, parser : null, knownValues : null, http2StaticTableIndex, http3StaticTableIndex)
 {
     Debug.Assert(!string.IsNullOrEmpty(name));
     Debug.Assert(name[0] == ':' || HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 }
Example #22
0
        internal static int GetContentRangeLength(string input, int startIndex, out object parsedValue)
        {
            Contract.Requires(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);
        }
        private static bool TrySetOptionalTokenList(NameValueHeaderValue nameValue, ref bool boolField,
                                                    ref ObjectCollection <string> destination)
        {
            Debug.Assert(nameValue != null);

            if (nameValue.Value == null)
            {
                boolField = true;
                return(true);
            }

            // We need the string to be at least 3 chars long: 2x quotes and at least 1 character. Also make sure we
            // have a quoted string. Note that NameValueHeaderValue will never have leading/trailing whitespace.
            string valueString = nameValue.Value;

            if ((valueString.Length < 3) || (valueString[0] != '\"') || (valueString[valueString.Length - 1] != '\"'))
            {
                return(false);
            }

            // We have a quoted string. Now verify that the string contains a list of valid tokens separated by ','.
            int  current            = 1;                      // skip the initial '"' character.
            int  maxLength          = valueString.Length - 1; // -1 because we don't want to parse the final '"'.
            bool separatorFound     = false;
            int  originalValueCount = destination == null ? 0 : destination.Count;

            while (current < maxLength)
            {
                current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(valueString, current, true,
                                                                           out separatorFound);

                if (current == maxLength)
                {
                    break;
                }

                int tokenLength = HttpRuleParser.GetTokenLength(valueString, current);

                if (tokenLength == 0)
                {
                    // We already skipped whitespace and separators. If we don't have a token it must be an invalid
                    // character.
                    return(false);
                }

                if (destination == null)
                {
                    destination = new ObjectCollection <string>(s_checkIsValidToken);
                }

                destination.Add(valueString.Substring(current, tokenLength));

                current = current + tokenLength;
            }

            // After parsing a valid token list, we expect to have at least one value
            if ((destination != null) && (destination.Count > originalValueCount))
            {
                boolField = true;
                return(true);
            }

            return(false);
        }
        public override string ToString(object value)
        {
            Debug.Assert(value is DateTimeOffset);

            return(HttpRuleParser.DateToString((DateTimeOffset)value));
        }
Example #25
0
 private static void AssertGetTokenLength(string input, int startIndex, int expectedLength)
 {
     Assert.Equal(expectedLength, HttpRuleParser.GetTokenLength(input, startIndex));
 }
Example #26
0
 public static bool TryParseDate(StringSegment input, out DateTimeOffset result)
 {
     return(HttpRuleParser.TryStringToDate(input, out result));
 }
Example #27
0
 public void IsTokenChar_InvalidTokenChars_ConsideredInvalid(char token)
 {
     Assert.False(HttpRuleParser.IsTokenChar(token));
 }
Example #28
0
 public KnownHeader(string name) : this(name, HttpHeaderType.Custom, null)
 {
     Debug.Assert(!string.IsNullOrEmpty(name));
     Debug.Assert(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 }
        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);
        }
Example #30
0
        internal bool RemoveParsedValue(string name, object value)
        {
            Contract.Requires((name != null) && (name.Length > 0));
            Contract.Requires(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
            Contract.Requires(value != null);

            if (headerStore == null)
            {
                return(false);
            }

            // If we have a value for this header, then verify if we have a single value. If so, compare that
            // value with 'item'. If we have a list of values, then remove 'item' from the list.
            HeaderStoreItemInfo info = null;

            if (TryGetAndParseHeaderInfo(name, out info))
            {
                Contract.Assert(info.Parser != null, "Can't add parsed value if there is no parser available.");
                Contract.Assert(info.Parser.SupportsMultipleValues,
                                "This method should not be used for single-value headers. Use Remove(string) instead.");

                bool result = false;

                // If there is no entry, just return.
                if (info.ParsedValue == null)
                {
                    return(false);
                }

                IEqualityComparer comparer = info.Parser.Comparer;

                List <object> parsedValues = info.ParsedValue as List <object>;
                if (parsedValues == null)
                {
                    Contract.Assert(info.ParsedValue.GetType() == value.GetType(),
                                    "Stored value does not have the same type as 'value'.");

                    if (AreEqual(value, info.ParsedValue, comparer))
                    {
                        info.ParsedValue = null;
                        result           = true;
                    }
                }
                else
                {
                    foreach (object item in parsedValues)
                    {
                        Contract.Assert(item.GetType() == value.GetType(),
                                        "One of the stored values does not have the same type as 'value'.");

                        if (AreEqual(value, item, comparer))
                        {
                            // Remove 'item' rather than 'value', since the 'comparer' may consider two values
                            // equal even though the default obj.Equals() may not (e.g. if 'comparer' does
                            // case-insentive comparison for strings, but string.Equals() is case-sensitive).
                            result = parsedValues.Remove(item);
                            break;
                        }
                    }

                    // If we removed the last item in a list, remove the list.
                    if (parsedValues.Count == 0)
                    {
                        info.ParsedValue = null;
                    }
                }

                // If there is no value for the header left, remove the header.
                if (info.IsEmpty)
                {
                    bool headerRemoved = Remove(name);
                    Contract.Assert(headerRemoved, "Existing header '" + name + "' couldn't be removed.");
                }

                return(result);
            }
            return(false);
        }