diff --git a/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs b/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs
index c6580a56fc..64cb5d5705 100644
--- a/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs
+++ b/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs
@@ -13,6 +13,7 @@ namespace Microsoft.Net.Http.Headers
public static class HeaderUtilities
{
private static readonly int _int64MaxStringLength = 19;
+ private static readonly int _qualityValueMaxCharCount = 10; // Little bit more permissive than RFC7231 5.3.1
private const string QualityName = "q";
internal const string BytesUnit = "bytes";
@@ -62,9 +63,8 @@ namespace Microsoft.Net.Http.Headers
{
// Note that the RFC requires decimal '.' regardless of the culture. I.e. using ',' as decimal
// separator is considered invalid (even if the current culture would allow it).
- double qualityValue;
- if (double.TryParse(qualityParameter.Value, NumberStyles.AllowDecimalPoint,
- NumberFormatInfo.InvariantInfo, out qualityValue))
+ if (TryParseQualityDouble(qualityParameter.Value, 0, out var qualityValue, out var length))
+
{
return qualityValue;
}
@@ -480,6 +480,95 @@ namespace Microsoft.Net.Http.Headers
}
}
+ // Strict and fast RFC7231 5.3.1 Quality value parser (and without memory allocation)
+ // See https://tools.ietf.org/html/rfc7231#section-5.3.1
+ // Check is made to verify if the value is between 0 and 1 (and it returns False if the check fails).
+ internal static bool TryParseQualityDouble(string input, int startIndex, out double quality, out int length)
+ {
+ quality = 0;
+ length = 0;
+
+ var inputLength = input.Length;
+ var current = startIndex;
+ var limit = startIndex + _qualityValueMaxCharCount;
+
+ var intPart = 0;
+ var decPart = 0;
+ var decPow = 1;
+
+ if (current >= inputLength)
+ {
+ return false;
+ }
+
+ var ch = input[current];
+
+ if (ch >= '0' && ch <= '1') // Only values between 0 and 1 are accepted, according to RFC
+ {
+ intPart = ch - '0';
+ current++;
+ }
+ else
+ {
+ // The RFC doesn't allow decimal values starting with dot. I.e. value ".123" is invalid. It must be in the
+ // form "0.123".
+ return false;
+ }
+
+ if (current < inputLength)
+ {
+ ch = input[current];
+
+ if (ch >= '0' && ch <= '9')
+ {
+ // The RFC accepts only one digit before the dot
+ return false;
+ }
+
+ if (ch == '.')
+ {
+ current++;
+
+ while (current < inputLength)
+ {
+ ch = input[current];
+ if (ch >= '0' && ch <= '9')
+ {
+ if (current >= limit)
+ {
+ return false;
+ }
+
+ decPart = decPart * 10 + ch - '0';
+ decPow *= 10;
+ current++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if (decPart != 0)
+ {
+ quality = intPart + decPart / (double)decPow;
+ }
+ else
+ {
+ quality = intPart;
+ }
+
+ if (quality < 0 || quality > 1)
+ {
+ return false;
+ }
+
+ length = current - startIndex;
+ return true;
+ }
+
///
/// Converts the non-negative 64-bit numeric value to its equivalent string representation.
///
diff --git a/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs b/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs
index 4fe977ff19..383a304dcf 100644
--- a/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs
+++ b/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs
@@ -61,13 +61,14 @@ namespace Microsoft.Net.Http.Headers
// If a parser returns an empty list, it means there was no value, but that's valid (e.g. "Accept: "). The caller
// can ignore the value.
parsedValues = null;
- var results = new List();
+ List results = null;
if (values == null)
{
return false;
}
- foreach (var value in values)
+ for (var i = 0; i < values.Count; i++)
{
+ var value = values[i];
int index = 0;
while (!string.IsNullOrEmpty(value) && index < value.Length)
@@ -78,6 +79,10 @@ namespace Microsoft.Net.Http.Headers
// The entry may not contain an actual value, like " , "
if (output != null)
{
+ if (results == null)
+ {
+ results = new List(); // Allocate it only when used
+ }
results.Add(output);
}
}
@@ -92,7 +97,7 @@ namespace Microsoft.Net.Http.Headers
}
}
}
- if (results.Count > 0)
+ if (results != null)
{
parsedValues = results;
return true;
diff --git a/src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs b/src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs
index 0521c1a000..13ac4fb15c 100644
--- a/src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs
+++ b/src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs
@@ -204,21 +204,7 @@ namespace Microsoft.Net.Http.Headers
return false;
}
- int qualityLength = HttpRuleParser.GetNumberLength(input, current, true);
-
- if (qualityLength == 0)
- {
- return false;
- }
-
- double quality;
- if (!double.TryParse(input.Substring(current, qualityLength), NumberStyles.AllowDecimalPoint,
- NumberFormatInfo.InvariantInfo, out quality))
- {
- return false;
- }
-
- if ((quality < 0) || (quality > 1))
+ if (!HeaderUtilities.TryParseQualityDouble(input, current, out var quality, out var qualityLength))
{
return false;
}
diff --git a/test/Microsoft.Net.Http.Headers.Tests/StringWithQualityHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/StringWithQualityHeaderValueTest.cs
index 2771614c2f..49ee58b93e 100644
--- a/test/Microsoft.Net.Http.Headers.Tests/StringWithQualityHeaderValueTest.cs
+++ b/test/Microsoft.Net.Http.Headers.Tests/StringWithQualityHeaderValueTest.cs
@@ -121,6 +121,8 @@ namespace Microsoft.Net.Http.Headers
CheckValidParse(" t", new StringWithQualityHeaderValue("t"));
CheckValidParse("t;q=0.", new StringWithQualityHeaderValue("t", 0));
CheckValidParse("t;q=1.", new StringWithQualityHeaderValue("t", 1));
+ CheckValidParse("t;q=1.000", new StringWithQualityHeaderValue("t", 1));
+ CheckValidParse("t;q=0.12345678", new StringWithQualityHeaderValue("t", 0.12345678));
CheckValidParse("t ; q = 0", new StringWithQualityHeaderValue("t", 0));
CheckValidParse("iso-8859-5", new StringWithQualityHeaderValue("iso-8859-5"));
CheckValidParse("unicode-1-1; q=0.8", new StringWithQualityHeaderValue("unicode-1-1", 0.8));
@@ -154,6 +156,11 @@ namespace Microsoft.Net.Http.Headers
[InlineData("t;q=a")]
[InlineData("t;qa")]
[InlineData("t;q1")]
+ [InlineData("integer_part_too_long;q=01")]
+ [InlineData("integer_part_too_long;q=01.0")]
+ [InlineData("decimal_part_too_long;q=0.123456789")]
+ [InlineData("decimal_part_too_long;q=0.123456789 ")]
+ [InlineData("no_integer_part;q=.1")]
public void Parse_SetOfInvalidValueStrings_Throws(string input)
{
Assert.Throws(() => StringWithQualityHeaderValue.Parse(input));