Example #1
0
        public KnownHeader(string name, HttpHeaderType headerType, HttpHeaderParser parser, string[] knownValues = 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;

            _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)' ';
        }
Example #2
0
    /// <summary>
    /// Try to find a target header value among the set of given header values and parse it as a
    /// <see cref="TimeSpan"/>.
    /// </summary>
    /// <param name="headerValues">
    /// The <see cref="StringValues"/> containing the set of header values to search.
    /// </param>
    /// <param name="targetValue">
    /// The target header value to look for.
    /// </param>
    /// <param name="value">
    /// When this method returns, contains the parsed <see cref="TimeSpan"/>, if the parsing succeeded, or
    /// null if the parsing failed. The conversion fails if the <paramref name="targetValue"/> was not
    /// found or could not be parsed as a <see cref="TimeSpan"/>. This parameter is passed uninitialized;
    /// any value originally supplied in result will be overwritten.
    /// </param>
    /// <returns>
    /// <see langword="true" /> if <paramref name="targetValue"/> is found and successfully parsed; otherwise,
    /// <see langword="false" />.
    /// </returns>
    // e.g. { "headerValue=10, targetHeaderValue=30" }
    public static bool TryParseSeconds(StringValues headerValues, string targetValue, [NotNullWhen(true)] out TimeSpan?value)
    {
        if (StringValues.IsNullOrEmpty(headerValues) || string.IsNullOrEmpty(targetValue))
        {
            value = null;
            return(false);
        }

        for (var i = 0; i < headerValues.Count; i++)
        {
            var segment = headerValues[i] ?? string.Empty;

            // Trim leading white space
            var current = HttpRuleParser.GetWhitespaceLength(segment, 0);

            while (current < segment.Length)
            {
                long seconds;
                var  initial     = current;
                var  tokenLength = HttpRuleParser.GetTokenLength(headerValues[i], current);
                if (tokenLength == targetValue.Length &&
                    string.Compare(headerValues[i], current, targetValue, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0 &&
                    TryParseNonNegativeInt64FromHeaderValue(current + tokenLength, segment, out seconds))
                {
                    // Token matches target value and seconds were parsed
                    value = TimeSpan.FromSeconds(seconds);
                    return(true);
                }

                current = AdvanceCacheDirectiveIndex(current + tokenLength, segment);

                // Ensure index was advanced
                if (current <= initial)
                {
                    Debug.Assert(false, $"Index '{nameof(current)}' not advanced, this is a bug.");
                    value = null;
                    return(false);
                }
            }
        }
        value = null;
        return(false);
    }
        private static int GetDispositionTypeExpressionLength(string input, int startIndex, out string?dispositionType)
        {
            Debug.Assert((input != null) && (input.Length > 0) && (startIndex < input.Length));

            // This method just parses the disposition type string, it does not parse parameters.
            dispositionType = null;

            // Parse the disposition type, i.e. <dispositiontype> in content-disposition string
            // "<dispositiontype>; param1=value1; param2=value2".
            int typeLength = HttpRuleParser.GetTokenLength(input, startIndex);

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

            dispositionType = input.Substring(startIndex, typeLength);
            return(typeLength);
        }
Example #4
0
    private static int GetDispositionTypeExpressionLength(StringSegment input, int startIndex, out StringSegment dispositionType)
    {
        Contract.Requires((input.Length > 0) && (startIndex < input.Length));

        // This method just parses the disposition type string, it does not parse parameters.
        dispositionType = null;

        // Parse the disposition type, i.e. <dispositiontype> in content-disposition string
        // "<dispositiontype>; param1=value1; param2=value2"
        var typeLength = HttpRuleParser.GetTokenLength(input, startIndex);

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

        dispositionType = input.Subsegment(startIndex, typeLength);
        return(typeLength);
    }
Example #5
0
        internal static int GetStringWithQualityLength(string?input, int startIndex, out object?parsedValue)
        {
            Debug.Assert(startIndex >= 0);

            parsedValue = null;

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

            // Parse the value string: <value> in '<value>; q=<quality>'
            int valueLength = HttpRuleParser.GetTokenLength(input, startIndex);

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

            string value   = input.Substring(startIndex, valueLength);
            int    current = startIndex + valueLength;

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

            if ((current == input.Length) || (input[current] != ';'))
            {
                parsedValue = new StringWithQualityHeaderValue(value);
                return(current - startIndex); // we have a valid token, but no quality.
            }

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

            // If we found a ';' separator, it must be followed by a quality information
            if (!TryReadQuality(input, out double quality, ref current))
            {
                return(0);
            }

            parsedValue = new StringWithQualityHeaderValue(value, quality);
            return(current - startIndex);
        }
Example #6
0
        internal static int GetNameValueLength(
            string input,
            int startIndex,
            Func <NameValueHeaderValue> nameValueCreator,
            out NameValueHeaderValue parsedValue)
        {
            parsedValue = (NameValueHeaderValue)null;
            if (string.IsNullOrEmpty(input) || startIndex >= input.Length)
            {
                return(0);
            }
            int tokenLength = HttpRuleParser.GetTokenLength(input, startIndex);

            if (tokenLength == 0)
            {
                return(0);
            }
            string str         = input.Substring(startIndex, tokenLength);
            int    startIndex1 = startIndex + tokenLength;
            int    startIndex2 = startIndex1 + HttpRuleParser.GetWhitespaceLength(input, startIndex1);

            if (startIndex2 == input.Length || input[startIndex2] != '=')
            {
                parsedValue       = nameValueCreator();
                parsedValue._name = str;
                return(startIndex2 + HttpRuleParser.GetWhitespaceLength(input, startIndex2) - startIndex);
            }
            int startIndex3 = startIndex2 + 1;
            int startIndex4 = startIndex3 + HttpRuleParser.GetWhitespaceLength(input, startIndex3);
            int valueLength = NameValueHeaderValue.GetValueLength(input, startIndex4);

            if (valueLength == 0)
            {
                return(0);
            }
            parsedValue        = nameValueCreator();
            parsedValue._name  = str;
            parsedValue._value = input.Substring(startIndex4, valueLength);
            int startIndex5 = startIndex4 + valueLength;

            return(startIndex5 + HttpRuleParser.GetWhitespaceLength(input, startIndex5) - startIndex);
        }
    internal static int GetValueLength(StringSegment input, int startIndex)
    {
        if (startIndex >= input.Length)
        {
            return(0);
        }

        var valueLength = HttpRuleParser.GetTokenLength(input, startIndex);

        if (valueLength == 0)
        {
            // A value can either be a token or a quoted string. Check if it is a quoted string.
            if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed)
            {
                // We have an invalid value. Reset the name and return.
                return(0);
            }
        }
        return(valueLength);
    }
Example #8
0
        public KnownHeader(string name, HttpHeaderType headerType, HttpHeaderParser?parser, string[]?knownValues = null, int?http2StaticTableIndex = null, int?http3StaticTableIndex = null)
        {
            Debug.Assert(!string.IsNullOrEmpty(name));
            Debug.Assert(name[0] == ':' || HttpRuleParser.GetTokenLength(name, 0) == name.Length);

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

            Initialize(http2StaticTableIndex, http3StaticTableIndex);

            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;
        }
Example #9
0
        public static bool TryGet(string headerName, out HeaderDescriptor descriptor)
        {
            Debug.Assert(!string.IsNullOrEmpty(headerName));

            KnownHeader knownHeader = KnownHeaders.TryGetKnownHeader(headerName);

            if (knownHeader != null)
            {
                descriptor = new HeaderDescriptor(knownHeader);
                return(true);
            }

            if (HttpRuleParser.GetTokenLength(headerName, 0) != headerName.Length)
            {
                descriptor = default(HeaderDescriptor);
                return(false);
            }

            descriptor = new HeaderDescriptor(headerName);
            return(true);
        }
Example #10
0
        internal static int GetNameValueLength(string input, int startIndex, Func <NameValueHeaderValue> nameValueCreator, out NameValueHeaderValue parsedValue)
        {
            parsedValue = null;
            if (string.IsNullOrEmpty(input) || startIndex >= input.Length)
            {
                return(0);
            }
            int tokenLength = HttpRuleParser.GetTokenLength(input, startIndex);

            if (tokenLength == 0)
            {
                return(0);
            }
            string text = input.Substring(startIndex, tokenLength);
            int    num  = startIndex + tokenLength;

            num += HttpRuleParser.GetWhitespaceLength(input, num);
            if (num == input.Length || input[num] != '=')
            {
                parsedValue      = nameValueCreator();
                parsedValue.name = text;
                num += HttpRuleParser.GetWhitespaceLength(input, num);
                return(num - startIndex);
            }
            num++;
            num += HttpRuleParser.GetWhitespaceLength(input, num);
            int valueLength = NameValueHeaderValue.GetValueLength(input, num);

            if (valueLength == 0)
            {
                return(0);
            }
            parsedValue       = nameValueCreator();
            parsedValue.name  = text;
            parsedValue.value = input.Substring(num, valueLength);
            num += valueLength;
            num += HttpRuleParser.GetWhitespaceLength(input, num);
            return(num - startIndex);
        }
Example #11
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>
    /// <see langword="true" /> if <paramref name="targetDirectives"/> is contained in <paramref name="cacheControlDirectives"/>;
    /// otherwise, <see langword="false" />.
    /// </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++)
        {
            var segment = cacheControlDirectives[i] ?? string.Empty;

            // Trim leading white space
            var current = HttpRuleParser.GetWhitespaceLength(segment, 0);

            while (current < segment.Length)
            {
                var initial = current;

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

                current = AdvanceCacheDirectiveIndex(current + tokenLength, segment);

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

        return(false);
    }
Example #12
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;
        }
    // 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);
    }
        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));
                }
            }
        }
Example #15
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);
        }
        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 #17
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);
        }
        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 #19
0
 public KnownHeader(string name) : this(name, HttpHeaderType.Custom, null)
 {
     Debug.Assert(!string.IsNullOrEmpty(name));
     Debug.Assert(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 }
        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);
        }
Example #21
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 #22
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 #23
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);
    }
Example #24
0
 private static void AssertGetTokenLength(string input, int startIndex, int expectedLength)
 {
     Assert.Equal(expectedLength, HttpRuleParser.GetTokenLength(input, startIndex));
 }
Example #25
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);
        }