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.StartsWith(' ') || value.StartsWith('\t') || value.EndsWith(' ') || value.EndsWith('\t')) { ThrowFormatException(value); } if (value[0] == '"') { HttpParseResult parseResult = HttpRuleParser.GetQuotedStringLength(value, 0, out int valueLength); if (parseResult != HttpParseResult.Parsed || valueLength != value.Length) { ThrowFormatException(value); } } else if (HttpRuleParser.ContainsNewLine(value)) { ThrowFormatException(value); }
private static void AssertGetCommentLength(string input, int startIndex, int expectedLength, HttpParseResult expectedResult) { int length = 0; HttpParseResult result = HttpRuleParser.GetCommentLength(input, startIndex, out length); Assert.Equal(expectedResult, result); Assert.Equal(expectedLength, length); }
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)); } } }
// TEXT = <any OCTET except CTLs, but including LWS> // LWS = [CRLF] 1*( SP | HT ) // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> // // Since we don't really care about the content of a quoted string or comment, we're more tolerant and // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). // // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) // is unusual. private static HttpParseResult GetExpressionLength(string input, int startIndex, char openChar, char closeChar, bool supportsNesting, ref int nestedCount, out int length) { Debug.Assert(input != null); Debug.Assert((startIndex >= 0) && (startIndex < input.Length)); length = 0; if (input[startIndex] != openChar) { return(HttpParseResult.NotParsed); } int current = startIndex + 1; // Start parsing with the character next to the first open-char. while (current < input.Length) { // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. int quotedPairLength = 0; if ((current + 2 < input.Length) && (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) { // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). current = current + quotedPairLength; continue; } // If we support nested expressions and we find an open-char, then parse the nested expressions. if (supportsNesting && (input[current] == openChar)) { nestedCount++; try { // Check if we exceeded the number of nested calls. if (nestedCount > maxNestedCount) { return(HttpParseResult.InvalidFormat); } int nestedLength = 0; HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); switch (nestedResult) { case HttpParseResult.Parsed: current += nestedLength; // Add the length of the nested expression and continue. break; case HttpParseResult.NotParsed: Debug.Assert(false, "'NotParsed' is unexpected: We started nested expression " + "parsing, because we found the open-char. So either it's a valid nested " + "expression or it has invalid format."); break; case HttpParseResult.InvalidFormat: // If the nested expression is invalid, we can't continue, so we fail with invalid format. return(HttpParseResult.InvalidFormat); default: Debug.Assert(false, "Unknown enum result: " + nestedResult); break; } } finally { nestedCount--; } } if (input[current] == closeChar) { length = current - startIndex + 1; return(HttpParseResult.Parsed); } current++; } // We didn't find the final quote, therefore we have an invalid expression string. return(HttpParseResult.InvalidFormat); }