/// <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; }
// 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()); }
/// <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); }
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); }
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); }
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); }
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); }
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); }
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 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); }
public void IsTokenChar_ValidTokenChars_ConsideredValid(char token) { Assert.True(HttpRuleParser.IsTokenChar(token)); }
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)); }
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; }
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); }
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); }
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)); }
private static void AssertGetTokenLength(string input, int startIndex, int expectedLength) { Assert.Equal(expectedLength, HttpRuleParser.GetTokenLength(input, startIndex)); }
public static bool TryParseDate(StringSegment input, out DateTimeOffset result) { return(HttpRuleParser.TryStringToDate(input, out result)); }
public void IsTokenChar_InvalidTokenChars_ConsideredInvalid(char token) { Assert.False(HttpRuleParser.IsTokenChar(token)); }
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); }
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); }