#515 Make forgiving vs strict header list parsers.

This commit is contained in:
Chris R 2016-02-03 11:03:16 -08:00
parent 77c137fc0a
commit 3e6c7171be
14 changed files with 868 additions and 66 deletions

View File

@ -7,11 +7,6 @@ namespace Microsoft.Net.Http.Headers
{
internal class CookieHeaderParser : HttpHeaderParser<CookieHeaderValue>
{
// The Cache-Control header is special: It is a header supporting a list of values, but we represent the list
// as _one_ instance of CacheControlHeaderValue. I.e we set 'SupportsMultipleValues' to 'true' since it is
// OK to have multiple Cache-Control headers in a request/response message. However, after parsing all
// Cache-Control headers, only one instance of CacheControlHeaderValue is created (if all headers contain valid
// values, otherwise we may have multiple strings containing the invalid values).
internal CookieHeaderParser(bool supportsMultipleValues)
: base(supportsMultipleValues)
{
@ -49,14 +44,11 @@ namespace Microsoft.Net.Http.Headers
}
CookieHeaderValue result = null;
int length = CookieHeaderValue.GetCookieLength(value, current, out result);
if (length == 0)
if (!CookieHeaderValue.TryGetCookieLength(value, ref current, out result))
{
return false;
}
current = current + length;
current = GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound);
// If we support multiple values and we've not reached the end of the string, then we must have a separator.
@ -91,6 +83,7 @@ namespace Microsoft.Net.Http.Headers
if (skipEmptyValues)
{
// Most headers only split on ',', but cookies primarily split on ';'
while ((current < input.Length) && ((input[current] == ',') || (input[current] == ';')))
{
current++; // skip delimiter.

View File

@ -107,22 +107,31 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.ParseValues(inputs);
}
public static IList<CookieHeaderValue> ParseStrictList(IList<string> inputs)
{
return MultipleValueParser.ParseStrictValues(inputs);
}
public static bool TryParseList(IList<string> inputs, out IList<CookieHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
}
// name=value; name="value"
internal static int GetCookieLength(string input, int startIndex, out CookieHeaderValue parsedValue)
public static bool TryParseStrictList(IList<string> inputs, out IList<CookieHeaderValue> parsedValues)
{
Contract.Requires(startIndex >= 0);
var offset = startIndex;
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
}
// name=value; name="value"
internal static bool TryGetCookieLength(string input, ref int offset, out CookieHeaderValue parsedValue)
{
Contract.Requires(offset >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (offset >= input.Length))
{
return 0;
return false;
}
var result = new CookieHeaderValue();
@ -135,7 +144,7 @@ namespace Microsoft.Net.Http.Headers
var itemLength = HttpRuleParser.GetTokenLength(input, offset);
if (itemLength == 0)
{
return 0;
return false;
}
result._name = input.Substring(offset, itemLength);
offset += itemLength;
@ -143,36 +152,33 @@ namespace Microsoft.Net.Http.Headers
// = (no spaces)
if (!ReadEqualsSign(input, ref offset))
{
return 0;
return false;
}
string value;
// value or "quoted value"
itemLength = GetCookieValueLength(input, offset, out value);
// The value may be empty
result._value = input.Substring(offset, itemLength);
offset += itemLength;
result._value = GetCookieValue(input, ref offset);
parsedValue = result;
return offset - startIndex;
return true;
}
// cookie-value = *cookie-octet / ( DQUOTE* cookie-octet DQUOTE )
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
// ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash
internal static int GetCookieValueLength(string input, int startIndex, out string value)
internal static string GetCookieValue(string input, ref int offset)
{
Contract.Requires(input != null);
Contract.Requires(startIndex >= 0);
Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
Contract.Requires(offset >= 0);
Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - offset)));
value = null;
if (startIndex >= input.Length)
var startIndex = offset;
if (offset >= input.Length)
{
return 0;
return string.Empty;
}
var inQuotes = false;
var offset = startIndex;
if (input[offset] == '"')
{
@ -195,19 +201,19 @@ namespace Microsoft.Net.Http.Headers
{
if (offset == input.Length || input[offset] != '"')
{
return 0; // Missing final quote
// Missing final quote
return string.Empty;
}
offset++;
}
int length = offset - startIndex;
if (length == 0)
if (offset > startIndex)
{
return 0;
return input.Substring(startIndex, length);
}
value = input.Substring(startIndex, length);
return length;
return string.Empty;
}
private static bool ReadEqualsSign(string input, ref int offset)
@ -252,8 +258,9 @@ namespace Microsoft.Net.Http.Headers
throw new ArgumentNullException(nameof(value));
}
string temp;
if (GetCookieValueLength(value, 0, out temp) != value.Length)
var offset = 0;
var result = GetCookieValue(value, ref offset);
if (result.Length != value.Length)
{
throw new ArgumentException("Invalid cookie value: " + value, parameterName);
}

View File

@ -129,11 +129,21 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.ParseValues(inputs);
}
public static IList<EntityTagHeaderValue> ParseStrictList(IList<string> inputs)
{
return MultipleValueParser.ParseStrictValues(inputs);
}
public static bool TryParseList(IList<string> inputs, out IList<EntityTagHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
}
public static bool TryParseStrictList(IList<string> inputs, out IList<EntityTagHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
}
internal static int GetEntityTagLength(string input, int startIndex, out EntityTagHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);

View File

@ -39,13 +39,23 @@ namespace Microsoft.Net.Http.Headers
T result;
if (!TryParseValue(value, ref index, out result))
{
throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid value '{0}'.",
value?.Substring(index) ?? "<null>"));
throw new FormatException(string.Format(CultureInfo.InvariantCulture,
"The header contains invalid values at index {0}: '{1}'", index, value ?? "<null>"));
}
return result;
}
public virtual bool TryParseValues(IList<string> values, out IList<T> parsedValues)
{
return TryParseValues(values, strict: false, parsedValues: out parsedValues);
}
public virtual bool TryParseStrictValues(IList<string> values, out IList<T> parsedValues)
{
return TryParseValues(values, strict: true, parsedValues: out parsedValues);
}
protected virtual bool TryParseValues(IList<string> values, bool strict, out IList<T> parsedValues)
{
Contract.Assert(_supportsMultipleValues);
// If a parser returns an empty list, it means there was no value, but that's valid (e.g. "Accept: "). The caller
@ -71,10 +81,15 @@ namespace Microsoft.Net.Http.Headers
results.Add(output);
}
}
else
else if (strict)
{
return false;
}
else
{
// Skip the invalid values and keep trying.
index++;
}
}
}
if (results.Count > 0)
@ -85,7 +100,17 @@ namespace Microsoft.Net.Http.Headers
return false;
}
public IList<T> ParseValues(IList<string> values)
public virtual IList<T> ParseValues(IList<string> values)
{
return ParseValues(values, strict: false);
}
public virtual IList<T> ParseStrictValues(IList<string> values)
{
return ParseValues(values, strict: true);
}
protected virtual IList<T> ParseValues(IList<string> values, bool strict)
{
Contract.Assert(_supportsMultipleValues);
// If a parser returns an empty list, it means there was no value, but that's valid (e.g. "Accept: "). The caller
@ -110,10 +135,15 @@ namespace Microsoft.Net.Http.Headers
parsedValues.Add(output);
}
}
else if (strict)
{
throw new FormatException(string.Format(CultureInfo.InvariantCulture,
"The header contains invalid values at index {0}: '{1}'", index, value));
}
else
{
throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid values '{0}'.",
value.Substring(index)));
// Skip the invalid values and keep trying.
index++;
}
}
}

View File

@ -391,11 +391,21 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.ParseValues(inputs);
}
public static IList<MediaTypeHeaderValue> ParseStrictList(IList<string> inputs)
{
return MultipleValueParser.ParseStrictValues(inputs);
}
public static bool TryParseList(IList<string> inputs, out IList<MediaTypeHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
}
public static bool TryParseStrictList(IList<string> inputs, out IList<MediaTypeHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
}
private static int GetMediaTypeLength(string input, int startIndex, out MediaTypeHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);

View File

@ -158,11 +158,21 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.ParseValues(input);
}
public static IList<NameValueHeaderValue> ParseStrictList(IList<string> input)
{
return MultipleValueParser.ParseStrictValues(input);
}
public static bool TryParseList(IList<string> input, out IList<NameValueHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseValues(input, out parsedValues);
}
public static bool TryParseStrictList(IList<string> input, out IList<NameValueHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
}
public override string ToString()
{
if (!string.IsNullOrEmpty(_value))

View File

@ -156,11 +156,21 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.ParseValues(inputs);
}
public static IList<SetCookieHeaderValue> ParseStrictList(IList<string> inputs)
{
return MultipleValueParser.ParseStrictValues(inputs);
}
public static bool TryParseList(IList<string> inputs, out IList<SetCookieHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
}
public static bool TryParseStrictList(IList<string> inputs, out IList<SetCookieHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
}
// name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; httponly
private static int GetSetCookieLength(string input, int startIndex, out SetCookieHeaderValue parsedValue)
{
@ -195,12 +205,9 @@ namespace Microsoft.Net.Http.Headers
return 0;
}
string value;
// value or "quoted value"
itemLength = CookieHeaderValue.GetCookieValueLength(input, offset, out value);
// The value may be empty
result._value = input.Substring(offset, itemLength);
offset += itemLength;
result._value = CookieHeaderValue.GetCookieValue(input, ref offset);
// *(';' SP cookie-av)
while (offset < input.Length)

View File

@ -119,11 +119,21 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.ParseValues(input);
}
public static IList<StringWithQualityHeaderValue> ParseStrictList(IList<string> input)
{
return MultipleValueParser.ParseStrictValues(input);
}
public static bool TryParseList(IList<string> input, out IList<StringWithQualityHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseValues(input, out parsedValues);
}
public static bool TryParseStrictList(IList<string> input, out IList<StringWithQualityHeaderValue> parsedValues)
{
return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
}
private static int GetStringWithQualityLength(string input, int startIndex, out StringWithQualityHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);

View File

@ -79,6 +79,7 @@ namespace Microsoft.Net.Http.Headers
};
}
}
public static TheoryData<IList<CookieHeaderValue>, string[]> ListOfCookieHeaderDataSet
{
get
@ -111,7 +112,48 @@ namespace Microsoft.Net.Http.Headers
}
}
// TODO: [Fact]
public static TheoryData<IList<CookieHeaderValue>, string[]> ListWithInvalidCookieHeaderDataSet
{
get
{
var dataset = new TheoryData<IList<CookieHeaderValue>, string[]>();
var header1 = new CookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3");
var validString1 = "name1=n1=v1&n2=v2&n3=v3";
var header2 = new CookieHeaderValue("name2", "value2");
var validString2 = "name2=value2";
var header3 = new CookieHeaderValue("name3", "value3");
var validString3 = "name3=value3";
var invalidString1 = "ipt={\"v\":{\"L\":3},\"pt\":{\"d\":3},ct\":{},\"_t\":44,\"_v\":\"2\"}";
dataset.Add(null, new[] { invalidString1 });
dataset.Add(new[] { header1 }.ToList(), new[] { validString1, invalidString1 });
dataset.Add(new[] { header1 }.ToList(), new[] { validString1, null, "", " ", ";", " , ", invalidString1 });
dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1, null, "", " ", ";", " , ", validString1 });
dataset.Add(new[] { header1 }.ToList(), new[] { validString1 + ", " + invalidString1 });
dataset.Add(new[] { header2 }.ToList(), new[] { invalidString1 + ", " + validString2 });
dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1 + "; " + validString1 });
dataset.Add(new[] { header2 }.ToList(), new[] { validString2 + "; " + invalidString1 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { invalidString1, validString1, validString2, validString3 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, invalidString1, validString2, validString3 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, validString2, invalidString1, validString3 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, validString2, validString3, invalidString1 });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", invalidString1, validString1, validString2, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, invalidString1, validString2, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, validString2, invalidString1, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, validString2, validString3, invalidString1) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", invalidString1, validString1, validString2, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", validString1, invalidString1, validString2, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", validString1, validString2, invalidString1, validString3) });
dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", validString1, validString2, validString3, invalidString1) });
return dataset;
}
}
[Fact]
public void CookieHeaderValue_CtorThrowsOnNullName()
{
Assert.Throws<ArgumentNullException>(() => new CookieHeaderValue(null, "value"));
@ -182,7 +224,7 @@ namespace Microsoft.Net.Http.Headers
public void CookieHeaderValue_TryParse_AcceptsValidValues(CookieHeaderValue cookie, string expectedValue)
{
CookieHeaderValue header;
bool result = CookieHeaderValue.TryParse(expectedValue, out header);
var result = CookieHeaderValue.TryParse(expectedValue, out header);
Assert.True(result);
Assert.Equal(cookie, header);
@ -201,7 +243,7 @@ namespace Microsoft.Net.Http.Headers
public void CookieHeaderValue_TryParse_RejectsInvalidValues(string value)
{
CookieHeaderValue header;
bool result = CookieHeaderValue.TryParse(value, out header);
var result = CookieHeaderValue.TryParse(value, out header);
Assert.False(result);
}
@ -215,15 +257,71 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(cookies, results);
}
[Theory]
[MemberData(nameof(ListOfCookieHeaderDataSet))]
public void CookieHeaderValue_ParseStrictList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
{
var results = CookieHeaderValue.ParseStrictList(input);
Assert.Equal(cookies, results);
}
[Theory]
[MemberData(nameof(ListOfCookieHeaderDataSet))]
public void CookieHeaderValue_TryParseList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
{
IList<CookieHeaderValue> results;
bool result = CookieHeaderValue.TryParseList(input, out results);
var result = CookieHeaderValue.TryParseList(input, out results);
Assert.True(result);
Assert.Equal(cookies, results);
}
[Theory]
[MemberData(nameof(ListOfCookieHeaderDataSet))]
public void CookieHeaderValue_TryParseStrictList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
{
IList<CookieHeaderValue> results;
var result = CookieHeaderValue.TryParseStrictList(input, out results);
Assert.True(result);
Assert.Equal(cookies, results);
}
[Theory]
[MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
public void CookieHeaderValue_ParseList_ExcludesInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
{
var results = CookieHeaderValue.ParseList(input);
// ParseList aways returns a list, even if empty. TryParseList may return null (via out).
Assert.Equal(cookies ?? new List<CookieHeaderValue>(), results);
}
[Theory]
[MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
public void CookieHeaderValue_TryParseList_ExcludesInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
{
IList<CookieHeaderValue> results;
var result = CookieHeaderValue.TryParseList(input, out results);
Assert.Equal(cookies, results);
Assert.Equal(cookies?.Count > 0, result);
}
[Theory]
[MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
public void CookieHeaderValue_ParseStrictList_ThrowsForAnyInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
{
Assert.Throws<FormatException>(() => CookieHeaderValue.ParseStrictList(input));
}
[Theory]
[MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
public void CookieHeaderValue_TryParseStrictList_FailsForAnyInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
{
IList<CookieHeaderValue> results;
var result = CookieHeaderValue.TryParseStrictList(input, out results);
Assert.Null(results);
Assert.False(result);
}
}
}

View File

@ -212,6 +212,39 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(expectedResults, results);
}
[Fact]
public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
{
var inputs = new[]
{
"",
"\"tag\"",
"",
" \"tag\" ",
"\r\n \"tag\"\r\n ",
"\"tag会\"",
"\"tag\",\"tag\"",
"\"tag\", \"tag\"",
"W/\"tag\"",
};
IList<EntityTagHeaderValue> results = EntityTagHeaderValue.ParseStrictList(inputs);
var expectedResults = new[]
{
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag会\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
{
@ -246,7 +279,40 @@ namespace Microsoft.Net.Http.Headers
}
[Fact]
public void ParseList_WithSomeInvlaidValues_Throws()
public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
{
var inputs = new[]
{
"",
"\"tag\"",
"",
" \"tag\" ",
"\r\n \"tag\"\r\n ",
"\"tag会\"",
"\"tag\",\"tag\"",
"\"tag\", \"tag\"",
"W/\"tag\"",
};
IList<EntityTagHeaderValue> results;
Assert.True(EntityTagHeaderValue.TryParseStrictList(inputs, out results));
var expectedResults = new[]
{
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag会\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void ParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
{
var inputs = new[]
{
@ -260,11 +326,41 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
Assert.Throws<FormatException>(() => EntityTagHeaderValue.ParseList(inputs));
var results = EntityTagHeaderValue.ParseList(inputs);
var expectedResults = new[]
{
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag会\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseList_WithSomeInvlaidValues_ReturnsFalse()
public void ParseStrictList_WithSomeInvlaidValues_Throws()
{
var inputs = new[]
{
"",
"\"tag\", tag, \"tag\"",
"tag, \"tag\"",
"",
" \"tag ",
"\r\n tag\"\r\n ",
"\"tag会\"",
"\"tag\", \"tag\"",
"W/\"tag\"",
};
Assert.Throws<FormatException>(() => EntityTagHeaderValue.ParseStrictList(inputs));
}
[Fact]
public void TryParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
{
var inputs = new[]
{
@ -279,7 +375,38 @@ namespace Microsoft.Net.Http.Headers
"W/\"tag\"",
};
IList<EntityTagHeaderValue> results;
Assert.False(EntityTagHeaderValue.TryParseList(inputs, out results));
Assert.True(EntityTagHeaderValue.TryParseList(inputs, out results));
var expectedResults = new[]
{
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag会\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
{
var inputs = new[]
{
"",
"\"tag\", tag, \"tag\"",
"tag, \"tag\"",
"",
" \"tag ",
"\r\n tag\"\r\n ",
"\"tag会\"",
"\"tag\", \"tag\"",
"W/\"tag\"",
};
IList<EntityTagHeaderValue> results;
Assert.False(EntityTagHeaderValue.TryParseStrictList(inputs, out results));
}
private void CheckValidParse(string input, EntityTagHeaderValue expectedResult)

View File

@ -497,6 +497,24 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(expectedResults, results);
}
[Fact]
public void ParseStrictList_SetOfValidValueStrings_ReturnsValues()
{
var inputs = new[] { "text/html,application/xhtml+xml,", "application/xml;q=0.9,image/webp,*/*;q=0.8" };
var results = MediaTypeHeaderValue.ParseStrictList(inputs);
var expectedResults = new[]
{
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("application/xml", 0.9),
new MediaTypeHeaderValue("image/webp"),
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseList_SetOfValidValueStrings_ReturnsTrue()
{
@ -517,18 +535,60 @@ namespace Microsoft.Net.Http.Headers
}
[Fact]
public void ParseList_WithSomeInvlaidValues_Throws()
public void TryParseStrictList_SetOfValidValueStrings_ReturnsTrue()
{
var inputs = new[] { "text/html,application/xhtml+xml,", "application/xml;q=0.9,image/webp,*/*;q=0.8" };
IList<MediaTypeHeaderValue> results;
Assert.True(MediaTypeHeaderValue.TryParseStrictList(inputs, out results));
var expectedResults = new[]
{
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("application/xml", 0.9),
new MediaTypeHeaderValue("image/webp"),
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void ParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
{
var inputs = new[]
{
"text/html,application/xhtml+xml, ignore-this, ignore/this",
"application/xml;q=0.9,image/webp,*/*;q=0.8"
};
Assert.Throws<FormatException>(() => MediaTypeHeaderValue.ParseList(inputs));
var results = MediaTypeHeaderValue.ParseList(inputs);
var expectedResults = new[]
{
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("ignore/this"),
new MediaTypeHeaderValue("application/xml", 0.9),
new MediaTypeHeaderValue("image/webp"),
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseList_WithSomeInvlaidValues_ReturnsFalse()
public void ParseStrictList_WithSomeInvlaidValues_Throws()
{
var inputs = new[]
{
"text/html,application/xhtml+xml, ignore-this, ignore/this",
"application/xml;q=0.9,image/webp,*/*;q=0.8"
};
Assert.Throws<FormatException>(() => MediaTypeHeaderValue.ParseStrictList(inputs));
}
[Fact]
public void TryParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
{
var inputs = new[]
{
@ -537,7 +597,32 @@ namespace Microsoft.Net.Http.Headers
"application/xml;q=0 4"
};
IList<MediaTypeHeaderValue> results;
Assert.False(MediaTypeHeaderValue.TryParseList(inputs, out results));
Assert.True(MediaTypeHeaderValue.TryParseList(inputs, out results));
var expectedResults = new[]
{
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("ignore/this"),
new MediaTypeHeaderValue("application/xml", 0.9),
new MediaTypeHeaderValue("image/webp"),
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
{
var inputs = new[]
{
"text/html,application/xhtml+xml, ignore-this, ignore/this",
"application/xml;q=0.9,image/webp,*/*;q=0.8",
"application/xml;q=0 4"
};
IList<MediaTypeHeaderValue> results;
Assert.False(MediaTypeHeaderValue.TryParseStrictList(inputs, out results));
}
[Theory]

View File

@ -364,6 +364,39 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(expectedResults, results);
}
[Fact]
public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
{
var inputs = new[]
{
"",
"name=value1",
"",
" name = value2 ",
"\r\n name =value3\r\n ",
"name=\"value 4\"",
"name=\"value会5\"",
"name=value6,name=value7",
"name=\"value 8\", name= \"value 9\"",
};
var results = NameValueHeaderValue.ParseStrictList(inputs);
var expectedResults = new[]
{
new NameValueHeaderValue("name", "value1"),
new NameValueHeaderValue("name", "value2"),
new NameValueHeaderValue("name", "value3"),
new NameValueHeaderValue("name", "\"value 4\""),
new NameValueHeaderValue("name", "\"value会5\""),
new NameValueHeaderValue("name", "value6"),
new NameValueHeaderValue("name", "value7"),
new NameValueHeaderValue("name", "\"value 8\""),
new NameValueHeaderValue("name", "\"value 9\""),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
{
@ -399,7 +432,41 @@ namespace Microsoft.Net.Http.Headers
}
[Fact]
public void ParseList_WithSomeInvlaidValues_Throws()
public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
{
var inputs = new[]
{
"",
"name=value1",
"",
" name = value2 ",
"\r\n name =value3\r\n ",
"name=\"value 4\"",
"name=\"value会5\"",
"name=value6,name=value7",
"name=\"value 8\", name= \"value 9\"",
};
IList<NameValueHeaderValue> results;
Assert.True(NameValueHeaderValue.TryParseStrictList(inputs, out results));
var expectedResults = new[]
{
new NameValueHeaderValue("name", "value1"),
new NameValueHeaderValue("name", "value2"),
new NameValueHeaderValue("name", "value3"),
new NameValueHeaderValue("name", "\"value 4\""),
new NameValueHeaderValue("name", "\"value会5\""),
new NameValueHeaderValue("name", "value6"),
new NameValueHeaderValue("name", "value7"),
new NameValueHeaderValue("name", "\"value 8\""),
new NameValueHeaderValue("name", "\"value 9\""),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void ParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
{
var inputs = new[]
{
@ -413,11 +480,47 @@ namespace Microsoft.Net.Http.Headers
"name8=value8,name9=value9",
"name10=\"value 10\", name11= \"value 11\"",
};
Assert.Throws<FormatException>(() => NameValueHeaderValue.ParseList(inputs));
var results = NameValueHeaderValue.ParseList(inputs);
var expectedResults = new[]
{
new NameValueHeaderValue("name1", "value1"),
new NameValueHeaderValue("name2"),
new NameValueHeaderValue("name3", "3"),
new NameValueHeaderValue("a"),
new NameValueHeaderValue("name4", "value4"),
new NameValueHeaderValue("b"),
new NameValueHeaderValue("6"),
new NameValueHeaderValue("name7", "\"value会7\""),
new NameValueHeaderValue("name8", "value8"),
new NameValueHeaderValue("name9", "value9"),
new NameValueHeaderValue("name10", "\"value 10\""),
new NameValueHeaderValue("name11", "\"value 11\""),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseList_WithSomeInvlaidValues_ReturnsFalse()
public void ParseStrictList_WithSomeInvlaidValues_Throws()
{
var inputs = new[]
{
"",
"name1=value1",
"name2",
" name3 = 3, value a",
"name4 =value4, name5 = value5 b",
"name6=\"value 6",
"name7=\"value会7\"",
"name8=value8,name9=value9",
"name10=\"value 10\", name11= \"value 11\"",
};
Assert.Throws<FormatException>(() => NameValueHeaderValue.ParseStrictList(inputs));
}
[Fact]
public void TryParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
{
var inputs = new[]
{
@ -432,7 +535,44 @@ namespace Microsoft.Net.Http.Headers
"name10=\"value 10\", name11= \"value 11\"",
};
IList<NameValueHeaderValue> results;
Assert.False(NameValueHeaderValue.TryParseList(inputs, out results));
Assert.True(NameValueHeaderValue.TryParseList(inputs, out results));
var expectedResults = new[]
{
new NameValueHeaderValue("name1", "value1"),
new NameValueHeaderValue("name2"),
new NameValueHeaderValue("name3", "3"),
new NameValueHeaderValue("a"),
new NameValueHeaderValue("name4", "value4"),
new NameValueHeaderValue("b"),
new NameValueHeaderValue("6"),
new NameValueHeaderValue("name7", "\"value会7\""),
new NameValueHeaderValue("name8", "value8"),
new NameValueHeaderValue("name9", "value9"),
new NameValueHeaderValue("name10", "\"value 10\""),
new NameValueHeaderValue("name11", "\"value 11\""),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
{
var inputs = new[]
{
"",
"name1=value1",
"name2",
" name3 = 3, value a",
"name4 =value4, name5 = value5 b",
"name6=\"value 6",
"name7=\"value会7\"",
"name8=value8,name9=value9",
"name10=\"value 10\", name11= \"value 11\"",
};
IList<NameValueHeaderValue> results;
Assert.False(NameValueHeaderValue.TryParseStrictList(inputs, out results));
}
#region Helper methods

View File

@ -95,6 +95,7 @@ namespace Microsoft.Net.Http.Headers
};
}
}
public static TheoryData<IList<SetCookieHeaderValue>, string[]> ListOfSetCookieHeaderDataSet
{
get
@ -141,7 +142,73 @@ namespace Microsoft.Net.Http.Headers
}
}
// TODO: [Fact]
public static TheoryData<IList<SetCookieHeaderValue>, string[]> ListWithInvalidSetCookieHeaderDataSet
{
get
{
var dataset = new TheoryData<IList<SetCookieHeaderValue>, string[]>();
var header1 = new SetCookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3")
{
Domain = "domain1",
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
HttpOnly = true,
MaxAge = TimeSpan.FromDays(1),
Path = "path1",
Secure = true
};
var string1 = "name1=n1=v1&n2=v2&n3=v3; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; httponly";
var header2 = new SetCookieHeaderValue("name2", "value2");
var string2 = "name2=value2";
var header3 = new SetCookieHeaderValue("name3", "value3")
{
MaxAge = TimeSpan.FromDays(1),
};
var string3 = "name3=value3; max-age=86400";
var header4 = new SetCookieHeaderValue("name4", "value4")
{
Domain = "domain1",
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
};
var string4 = "name4=value4; expires=Sun, 06 Nov 1994 08:49:37 GMT; domain=domain1;";
var invalidString1 = "ipt={\"v\":{\"L\":3},\"pt:{\"d\":3},\"ct\":{},\"_t\":44,\"_v\":\"2\"}";
var invalidHeader2a = new SetCookieHeaderValue("expires", "Sun");
var invalidHeader2b = new SetCookieHeaderValue("domain", "domain1");
var invalidString2 = "ipt={\"v\":{\"L\":3},\"pt\":{d\":3},\"ct\":{},\"_t\":44,\"_v\":\"2\"}; expires=Sun, 06 Nov 1994 08:49:37 GMT; domain=domain1";
var invalidHeader3 = new SetCookieHeaderValue("domain", "domain1")
{
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
};
var invalidString3 = "ipt={\"v\":{\"L\":3},\"pt\":{\"d:3},\"ct\":{},\"_t\":44,\"_v\":\"2\"}; domain=domain1; expires=Sun, 06 Nov 1994 08:49:37 GMT";
dataset.Add(null, new[] { invalidString1 });
dataset.Add(new[] { invalidHeader2a, invalidHeader2b }.ToList(), new[] { invalidString2 });
dataset.Add(new[] { invalidHeader3 }.ToList(), new[] { invalidString3 });
dataset.Add(new[] { header1 }.ToList(), new[] { string1, invalidString1 });
dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1, null, "", " ", ",", " , ", string1 });
dataset.Add(new[] { header1 }.ToList(), new[] { string1 + ", " + invalidString1 });
dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1 + ", " + string1 });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { invalidString1, string1, string2, string3, string4 });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, invalidString1, string2, string3, string4 });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, invalidString1, string3, string4 });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, string3, invalidString1, string4 });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, string3, string4, invalidString1 });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", invalidString1, string1, string2, string3, string4) });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, invalidString1, string2, string3, string4) });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, invalidString1, string3, string4) });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, invalidString1, string4) });
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, string4, invalidString1) });
return dataset;
}
}
[Fact]
public void SetCookieHeaderValue_CtorThrowsOnNullName()
{
Assert.Throws<ArgumentNullException>(() => new SetCookieHeaderValue(null, "value"));
@ -255,5 +322,61 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(cookies, results);
}
[Theory]
[MemberData(nameof(ListOfSetCookieHeaderDataSet))]
public void SetCookieHeaderValue_ParseStrictList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
{
var results = SetCookieHeaderValue.ParseStrictList(input);
Assert.Equal(cookies, results);
}
[Theory]
[MemberData(nameof(ListOfSetCookieHeaderDataSet))]
public void SetCookieHeaderValue_TryParseStrictList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
{
IList<SetCookieHeaderValue> results;
bool result = SetCookieHeaderValue.TryParseStrictList(input, out results);
Assert.True(result);
Assert.Equal(cookies, results);
}
[Theory]
[MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
public void SetCookieHeaderValue_ParseList_ExcludesInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
{
var results = SetCookieHeaderValue.ParseList(input);
// ParseList aways returns a list, even if empty. TryParseList may return null (via out).
Assert.Equal(cookies ?? new List<SetCookieHeaderValue>(), results);
}
[Theory]
[MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
public void SetCookieHeaderValue_TryParseList_ExcludesInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
{
IList<SetCookieHeaderValue> results;
bool result = SetCookieHeaderValue.TryParseList(input, out results);
Assert.Equal(cookies, results);
Assert.Equal(cookies?.Count > 0, result);
}
[Theory]
[MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
public void SetCookieHeaderValue_ParseStrictList_ThrowsForAnyInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
{
Assert.Throws<FormatException>(() => SetCookieHeaderValue.ParseStrictList(input));
}
[Theory]
[MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
public void SetCookieHeaderValue_TryParseStrictList_FailsForAnyInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
{
IList<SetCookieHeaderValue> results;
bool result = SetCookieHeaderValue.TryParseStrictList(input, out results);
Assert.Null(results);
Assert.False(result);
}
}
}

View File

@ -233,6 +233,43 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(expectedResults, results);
}
[Fact]
public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
{
var inputs = new[]
{
"",
"text1",
"text2,",
"textA,textB",
"text3;q=0.5",
"text4;q=0.5,",
" text5 ; q = 0.50 ",
"\r\n text6 ; q = 0.05 ",
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
IList<StringWithQualityHeaderValue> results = StringWithQualityHeaderValue.ParseStrictList(inputs);
var expectedResults = new[]
{
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("text2"),
new StringWithQualityHeaderValue("textA"),
new StringWithQualityHeaderValue("textB"),
new StringWithQualityHeaderValue("text3", 0.5),
new StringWithQualityHeaderValue("text4", 0.5),
new StringWithQualityHeaderValue("text5", 0.5),
new StringWithQualityHeaderValue("text6", 0.05),
new StringWithQualityHeaderValue("text7"),
new StringWithQualityHeaderValue("text8", 0.5),
new StringWithQualityHeaderValue("text9"),
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
{
@ -272,7 +309,45 @@ namespace Microsoft.Net.Http.Headers
}
[Fact]
public void ParseList_WithSomeInvlaidValues_Throws()
public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
{
var inputs = new[]
{
"",
"text1",
"text2,",
"textA,textB",
"text3;q=0.5",
"text4;q=0.5,",
" text5 ; q = 0.50 ",
"\r\n text6 ; q = 0.05 ",
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
IList<StringWithQualityHeaderValue> results;
Assert.True(StringWithQualityHeaderValue.TryParseStrictList(inputs, out results));
var expectedResults = new[]
{
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("text2"),
new StringWithQualityHeaderValue("textA"),
new StringWithQualityHeaderValue("textB"),
new StringWithQualityHeaderValue("text3", 0.5),
new StringWithQualityHeaderValue("text4", 0.5),
new StringWithQualityHeaderValue("text5", 0.5),
new StringWithQualityHeaderValue("text6", 0.05),
new StringWithQualityHeaderValue("text7"),
new StringWithQualityHeaderValue("text8", 0.5),
new StringWithQualityHeaderValue("text9"),
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void ParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
{
var inputs = new[]
{
@ -288,11 +363,49 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.ParseList(inputs));
var results = StringWithQualityHeaderValue.ParseList(inputs);
var expectedResults = new[]
{
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("1"),
new StringWithQualityHeaderValue("text2"),
new StringWithQualityHeaderValue("text3", 0.5),
new StringWithQualityHeaderValue("text4", 0.5),
new StringWithQualityHeaderValue("stuff"),
new StringWithQualityHeaderValue("text5", 0.5),
new StringWithQualityHeaderValue("text6", 0.05),
new StringWithQualityHeaderValue("text7"),
new StringWithQualityHeaderValue("text8", 0.5),
new StringWithQualityHeaderValue("text9"),
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseList_WithSomeInvlaidValues_ReturnsFalse()
public void ParseStrictList_WithSomeInvlaidValues_Throws()
{
var inputs = new[]
{
"",
"text1",
"text 1",
"text2",
"\"text 2\",",
"text3;q=0.5",
"text4;q=0.5, extra stuff",
" text5 ; q = 0.50 ",
"\r\n text6 ; q = 0.05 ",
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.ParseStrictList(inputs));
}
[Fact]
public void TryParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
{
var inputs = new[]
{
@ -309,7 +422,46 @@ namespace Microsoft.Net.Http.Headers
" text9 , text10 ; q = 0.5 ",
};
IList<StringWithQualityHeaderValue> results;
Assert.False(StringWithQualityHeaderValue.TryParseList(inputs, out results));
Assert.True(StringWithQualityHeaderValue.TryParseList(inputs, out results));
var expectedResults = new[]
{
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("1"),
new StringWithQualityHeaderValue("text2"),
new StringWithQualityHeaderValue("text3", 0.5),
new StringWithQualityHeaderValue("text4", 0.5),
new StringWithQualityHeaderValue("stuff"),
new StringWithQualityHeaderValue("text5", 0.5),
new StringWithQualityHeaderValue("text6", 0.05),
new StringWithQualityHeaderValue("text7"),
new StringWithQualityHeaderValue("text8", 0.5),
new StringWithQualityHeaderValue("text9"),
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
Assert.Equal(expectedResults, results);
}
[Fact]
public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
{
var inputs = new[]
{
"",
"text1",
"text 1",
"text2",
"\"text 2\",",
"text3;q=0.5",
"text4;q=0.5, extra stuff",
" text5 ; q = 0.50 ",
"\r\n text6 ; q = 0.05 ",
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
IList<StringWithQualityHeaderValue> results;
Assert.False(StringWithQualityHeaderValue.TryParseStrictList(inputs, out results));
}
#region Helper methods