From 3e6c7171be5e2a6a1b7eb2eabf2ed73df6c7536c Mon Sep 17 00:00:00 2001 From: Chris R Date: Wed, 3 Feb 2016 11:03:16 -0800 Subject: [PATCH] #515 Make forgiving vs strict header list parsers. --- .../CookieHeaderParser.cs | 11 +- .../CookieHeaderValue.cs | 59 ++++--- .../EntityTagHeaderValue.cs | 10 ++ .../HttpHeaderParser.cs | 42 ++++- .../MediaTypeHeaderValue.cs | 10 ++ .../NameValueHeaderValue.cs | 10 ++ .../SetCookieHeaderValue.cs | 15 +- .../StringWithQualityHeaderValue.cs | 10 ++ .../CookieHeaderValueTest.cs | 106 +++++++++++- .../EntityTagHeaderValueTest.cs | 135 ++++++++++++++- .../MediaTypeHeaderValueTest.cs | 93 +++++++++- .../NameValueHeaderValueTest.cs | 148 +++++++++++++++- .../SetCookieHeaderValueTest.cs | 125 +++++++++++++- .../StringWithQualityHeaderValueTest.cs | 160 +++++++++++++++++- 14 files changed, 868 insertions(+), 66 deletions(-) diff --git a/src/Microsoft.Net.Http.Headers/CookieHeaderParser.cs b/src/Microsoft.Net.Http.Headers/CookieHeaderParser.cs index 2143f299b0..9253e881e5 100644 --- a/src/Microsoft.Net.Http.Headers/CookieHeaderParser.cs +++ b/src/Microsoft.Net.Http.Headers/CookieHeaderParser.cs @@ -7,11 +7,6 @@ namespace Microsoft.Net.Http.Headers { internal class CookieHeaderParser : HttpHeaderParser { - // 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. diff --git a/src/Microsoft.Net.Http.Headers/CookieHeaderValue.cs b/src/Microsoft.Net.Http.Headers/CookieHeaderValue.cs index e7e72d3ce5..77adcf5441 100644 --- a/src/Microsoft.Net.Http.Headers/CookieHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/CookieHeaderValue.cs @@ -107,22 +107,31 @@ namespace Microsoft.Net.Http.Headers return MultipleValueParser.ParseValues(inputs); } + public static IList ParseStrictList(IList inputs) + { + return MultipleValueParser.ParseStrictValues(inputs); + } + public static bool TryParseList(IList inputs, out IList 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 inputs, out IList 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() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + Contract.Requires(offset >= 0); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (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); } diff --git a/src/Microsoft.Net.Http.Headers/EntityTagHeaderValue.cs b/src/Microsoft.Net.Http.Headers/EntityTagHeaderValue.cs index 216fa942a8..7f89a3e113 100644 --- a/src/Microsoft.Net.Http.Headers/EntityTagHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/EntityTagHeaderValue.cs @@ -129,11 +129,21 @@ namespace Microsoft.Net.Http.Headers return MultipleValueParser.ParseValues(inputs); } + public static IList ParseStrictList(IList inputs) + { + return MultipleValueParser.ParseStrictValues(inputs); + } + public static bool TryParseList(IList inputs, out IList parsedValues) { return MultipleValueParser.TryParseValues(inputs, out parsedValues); } + public static bool TryParseStrictList(IList inputs, out IList parsedValues) + { + return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues); + } + internal static int GetEntityTagLength(string input, int startIndex, out EntityTagHeaderValue parsedValue) { Contract.Requires(startIndex >= 0); diff --git a/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs b/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs index a12edbf3e0..4fe977ff19 100644 --- a/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs +++ b/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs @@ -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) ?? "")); + throw new FormatException(string.Format(CultureInfo.InvariantCulture, + "The header contains invalid values at index {0}: '{1}'", index, value ?? "")); } return result; } public virtual bool TryParseValues(IList values, out IList parsedValues) + { + return TryParseValues(values, strict: false, parsedValues: out parsedValues); + } + + public virtual bool TryParseStrictValues(IList values, out IList parsedValues) + { + return TryParseValues(values, strict: true, parsedValues: out parsedValues); + } + + protected virtual bool TryParseValues(IList values, bool strict, out IList 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 ParseValues(IList values) + public virtual IList ParseValues(IList values) + { + return ParseValues(values, strict: false); + } + + public virtual IList ParseStrictValues(IList values) + { + return ParseValues(values, strict: true); + } + + protected virtual IList ParseValues(IList 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++; } } } diff --git a/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs b/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs index b8b7c09e9e..0ec7666402 100644 --- a/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs @@ -391,11 +391,21 @@ namespace Microsoft.Net.Http.Headers return MultipleValueParser.ParseValues(inputs); } + public static IList ParseStrictList(IList inputs) + { + return MultipleValueParser.ParseStrictValues(inputs); + } + public static bool TryParseList(IList inputs, out IList parsedValues) { return MultipleValueParser.TryParseValues(inputs, out parsedValues); } + public static bool TryParseStrictList(IList inputs, out IList parsedValues) + { + return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues); + } + private static int GetMediaTypeLength(string input, int startIndex, out MediaTypeHeaderValue parsedValue) { Contract.Requires(startIndex >= 0); diff --git a/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs b/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs index fed6261a4d..a04b8d7164 100644 --- a/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs @@ -158,11 +158,21 @@ namespace Microsoft.Net.Http.Headers return MultipleValueParser.ParseValues(input); } + public static IList ParseStrictList(IList input) + { + return MultipleValueParser.ParseStrictValues(input); + } + public static bool TryParseList(IList input, out IList parsedValues) { return MultipleValueParser.TryParseValues(input, out parsedValues); } + public static bool TryParseStrictList(IList input, out IList parsedValues) + { + return MultipleValueParser.TryParseStrictValues(input, out parsedValues); + } + public override string ToString() { if (!string.IsNullOrEmpty(_value)) diff --git a/src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs b/src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs index 92cf35a41f..37372e013c 100644 --- a/src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs @@ -156,11 +156,21 @@ namespace Microsoft.Net.Http.Headers return MultipleValueParser.ParseValues(inputs); } + public static IList ParseStrictList(IList inputs) + { + return MultipleValueParser.ParseStrictValues(inputs); + } + public static bool TryParseList(IList inputs, out IList parsedValues) { return MultipleValueParser.TryParseValues(inputs, out parsedValues); } + public static bool TryParseStrictList(IList inputs, out IList 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) diff --git a/src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs b/src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs index 36e9a3fd58..0521c1a000 100644 --- a/src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs @@ -119,11 +119,21 @@ namespace Microsoft.Net.Http.Headers return MultipleValueParser.ParseValues(input); } + public static IList ParseStrictList(IList input) + { + return MultipleValueParser.ParseStrictValues(input); + } + public static bool TryParseList(IList input, out IList parsedValues) { return MultipleValueParser.TryParseValues(input, out parsedValues); } + public static bool TryParseStrictList(IList input, out IList parsedValues) + { + return MultipleValueParser.TryParseStrictValues(input, out parsedValues); + } + private static int GetStringWithQualityLength(string input, int startIndex, out StringWithQualityHeaderValue parsedValue) { Contract.Requires(startIndex >= 0); diff --git a/test/Microsoft.Net.Http.Headers.Tests/CookieHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/CookieHeaderValueTest.cs index 3644ed1df0..4b320c0e06 100644 --- a/test/Microsoft.Net.Http.Headers.Tests/CookieHeaderValueTest.cs +++ b/test/Microsoft.Net.Http.Headers.Tests/CookieHeaderValueTest.cs @@ -79,6 +79,7 @@ namespace Microsoft.Net.Http.Headers }; } } + public static TheoryData, string[]> ListOfCookieHeaderDataSet { get @@ -111,7 +112,48 @@ namespace Microsoft.Net.Http.Headers } } - // TODO: [Fact] + public static TheoryData, string[]> ListWithInvalidCookieHeaderDataSet + { + get + { + var dataset = new TheoryData, 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(() => 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 cookies, string[] input) + { + var results = CookieHeaderValue.ParseStrictList(input); + + Assert.Equal(cookies, results); + } + [Theory] [MemberData(nameof(ListOfCookieHeaderDataSet))] public void CookieHeaderValue_TryParseList_AcceptsValidValues(IList cookies, string[] input) { IList 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 cookies, string[] input) + { + IList 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 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(), results); + } + + [Theory] + [MemberData(nameof(ListWithInvalidCookieHeaderDataSet))] + public void CookieHeaderValue_TryParseList_ExcludesInvalidValues(IList cookies, string[] input) + { + IList 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 cookies, string[] input) + { + Assert.Throws(() => CookieHeaderValue.ParseStrictList(input)); + } + + [Theory] + [MemberData(nameof(ListWithInvalidCookieHeaderDataSet))] + public void CookieHeaderValue_TryParseStrictList_FailsForAnyInvalidValues(IList cookies, string[] input) + { + IList results; + var result = CookieHeaderValue.TryParseStrictList(input, out results); + Assert.Null(results); + Assert.False(result); + } } } diff --git a/test/Microsoft.Net.Http.Headers.Tests/EntityTagHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/EntityTagHeaderValueTest.cs index 303b910fcb..c17c9baa06 100644 --- a/test/Microsoft.Net.Http.Headers.Tests/EntityTagHeaderValueTest.cs +++ b/test/Microsoft.Net.Http.Headers.Tests/EntityTagHeaderValueTest.cs @@ -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 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 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(() => 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(() => EntityTagHeaderValue.ParseStrictList(inputs)); + } + + [Fact] + public void TryParseList_WithSomeInvlaidValues_ExcludesInvalidValues() { var inputs = new[] { @@ -279,7 +375,38 @@ namespace Microsoft.Net.Http.Headers "W/\"tag\"", }; IList 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 results; + Assert.False(EntityTagHeaderValue.TryParseStrictList(inputs, out results)); } private void CheckValidParse(string input, EntityTagHeaderValue expectedResult) diff --git a/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs index ba3a31b7d2..0a72951240 100644 --- a/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs +++ b/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs @@ -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 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(() => 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(() => 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 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 results; + Assert.False(MediaTypeHeaderValue.TryParseStrictList(inputs, out results)); } [Theory] diff --git a/test/Microsoft.Net.Http.Headers.Tests/NameValueHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/NameValueHeaderValueTest.cs index 46d1d30d75..c90a922574 100644 --- a/test/Microsoft.Net.Http.Headers.Tests/NameValueHeaderValueTest.cs +++ b/test/Microsoft.Net.Http.Headers.Tests/NameValueHeaderValueTest.cs @@ -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 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(() => 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(() => 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 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 results; + Assert.False(NameValueHeaderValue.TryParseStrictList(inputs, out results)); } #region Helper methods diff --git a/test/Microsoft.Net.Http.Headers.Tests/SetCookieHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/SetCookieHeaderValueTest.cs index f98886521d..5d2c138558 100644 --- a/test/Microsoft.Net.Http.Headers.Tests/SetCookieHeaderValueTest.cs +++ b/test/Microsoft.Net.Http.Headers.Tests/SetCookieHeaderValueTest.cs @@ -95,6 +95,7 @@ namespace Microsoft.Net.Http.Headers }; } } + public static TheoryData, string[]> ListOfSetCookieHeaderDataSet { get @@ -141,7 +142,73 @@ namespace Microsoft.Net.Http.Headers } } - // TODO: [Fact] + public static TheoryData, string[]> ListWithInvalidSetCookieHeaderDataSet + { + get + { + var dataset = new TheoryData, 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(() => 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 cookies, string[] input) + { + var results = SetCookieHeaderValue.ParseStrictList(input); + + Assert.Equal(cookies, results); + } + + [Theory] + [MemberData(nameof(ListOfSetCookieHeaderDataSet))] + public void SetCookieHeaderValue_TryParseStrictList_AcceptsValidValues(IList cookies, string[] input) + { + IList 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 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(), results); + } + + [Theory] + [MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))] + public void SetCookieHeaderValue_TryParseList_ExcludesInvalidValues(IList cookies, string[] input) + { + IList 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 cookies, string[] input) + { + Assert.Throws(() => SetCookieHeaderValue.ParseStrictList(input)); + } + + [Theory] + [MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))] + public void SetCookieHeaderValue_TryParseStrictList_FailsForAnyInvalidValues(IList cookies, string[] input) + { + IList results; + bool result = SetCookieHeaderValue.TryParseStrictList(input, out results); + Assert.Null(results); + Assert.False(result); + } } } diff --git a/test/Microsoft.Net.Http.Headers.Tests/StringWithQualityHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/StringWithQualityHeaderValueTest.cs index ceb29fed10..2771614c2f 100644 --- a/test/Microsoft.Net.Http.Headers.Tests/StringWithQualityHeaderValueTest.cs +++ b/test/Microsoft.Net.Http.Headers.Tests/StringWithQualityHeaderValueTest.cs @@ -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 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 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(() => 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(() => 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 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 results; + Assert.False(StringWithQualityHeaderValue.TryParseStrictList(inputs, out results)); } #region Helper methods