Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit 3e6c717

Browse files
committed
#515 Make forgiving vs strict header list parsers.
1 parent 77c137f commit 3e6c717

14 files changed

+867
-65
lines changed

src/Microsoft.Net.Http.Headers/CookieHeaderParser.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@ namespace Microsoft.Net.Http.Headers
77
{
88
internal class CookieHeaderParser : HttpHeaderParser<CookieHeaderValue>
99
{
10-
// The Cache-Control header is special: It is a header supporting a list of values, but we represent the list
11-
// as _one_ instance of CacheControlHeaderValue. I.e we set 'SupportsMultipleValues' to 'true' since it is
12-
// OK to have multiple Cache-Control headers in a request/response message. However, after parsing all
13-
// Cache-Control headers, only one instance of CacheControlHeaderValue is created (if all headers contain valid
14-
// values, otherwise we may have multiple strings containing the invalid values).
1510
internal CookieHeaderParser(bool supportsMultipleValues)
1611
: base(supportsMultipleValues)
1712
{
@@ -49,14 +44,11 @@ public sealed override bool TryParseValue(string value, ref int index, out Cooki
4944
}
5045

5146
CookieHeaderValue result = null;
52-
int length = CookieHeaderValue.GetCookieLength(value, current, out result);
53-
54-
if (length == 0)
47+
if (!CookieHeaderValue.TryGetCookieLength(value, ref current, out result))
5548
{
5649
return false;
5750
}
5851

59-
current = current + length;
6052
current = GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound);
6153

6254
// 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 @@ private static int GetNextNonEmptyOrWhitespaceIndex(string input, int startIndex
9183

9284
if (skipEmptyValues)
9385
{
86+
// Most headers only split on ',', but cookies primarily split on ';'
9487
while ((current < input.Length) && ((input[current] == ',') || (input[current] == ';')))
9588
{
9689
current++; // skip delimiter.

src/Microsoft.Net.Http.Headers/CookieHeaderValue.cs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,31 @@ public static IList<CookieHeaderValue> ParseList(IList<string> inputs)
107107
return MultipleValueParser.ParseValues(inputs);
108108
}
109109

110+
public static IList<CookieHeaderValue> ParseStrictList(IList<string> inputs)
111+
{
112+
return MultipleValueParser.ParseStrictValues(inputs);
113+
}
114+
110115
public static bool TryParseList(IList<string> inputs, out IList<CookieHeaderValue> parsedValues)
111116
{
112117
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
113118
}
114119

120+
public static bool TryParseStrictList(IList<string> inputs, out IList<CookieHeaderValue> parsedValues)
121+
{
122+
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
123+
}
124+
115125
// name=value; name="value"
116-
internal static int GetCookieLength(string input, int startIndex, out CookieHeaderValue parsedValue)
126+
internal static bool TryGetCookieLength(string input, ref int offset, out CookieHeaderValue parsedValue)
117127
{
118-
Contract.Requires(startIndex >= 0);
119-
var offset = startIndex;
128+
Contract.Requires(offset >= 0);
120129

121130
parsedValue = null;
122131

123132
if (string.IsNullOrEmpty(input) || (offset >= input.Length))
124133
{
125-
return 0;
134+
return false;
126135
}
127136

128137
var result = new CookieHeaderValue();
@@ -135,44 +144,41 @@ internal static int GetCookieLength(string input, int startIndex, out CookieHead
135144
var itemLength = HttpRuleParser.GetTokenLength(input, offset);
136145
if (itemLength == 0)
137146
{
138-
return 0;
147+
return false;
139148
}
140149
result._name = input.Substring(offset, itemLength);
141150
offset += itemLength;
142151

143152
// = (no spaces)
144153
if (!ReadEqualsSign(input, ref offset))
145154
{
146-
return 0;
155+
return false;
147156
}
148157

149-
string value;
150158
// value or "quoted value"
151-
itemLength = GetCookieValueLength(input, offset, out value);
152159
// The value may be empty
153-
result._value = input.Substring(offset, itemLength);
154-
offset += itemLength;
160+
result._value = GetCookieValue(input, ref offset);
155161

156162
parsedValue = result;
157-
return offset - startIndex;
163+
return true;
158164
}
159165

160166
// cookie-value = *cookie-octet / ( DQUOTE* cookie-octet DQUOTE )
161167
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
162168
// ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash
163-
internal static int GetCookieValueLength(string input, int startIndex, out string value)
169+
internal static string GetCookieValue(string input, ref int offset)
164170
{
165171
Contract.Requires(input != null);
166-
Contract.Requires(startIndex >= 0);
167-
Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
172+
Contract.Requires(offset >= 0);
173+
Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - offset)));
174+
175+
var startIndex = offset;
168176

169-
value = null;
170-
if (startIndex >= input.Length)
177+
if (offset >= input.Length)
171178
{
172-
return 0;
179+
return string.Empty;
173180
}
174181
var inQuotes = false;
175-
var offset = startIndex;
176182

177183
if (input[offset] == '"')
178184
{
@@ -195,19 +201,19 @@ internal static int GetCookieValueLength(string input, int startIndex, out strin
195201
{
196202
if (offset == input.Length || input[offset] != '"')
197203
{
198-
return 0; // Missing final quote
204+
// Missing final quote
205+
return string.Empty;
199206
}
200207
offset++;
201208
}
202209

203210
int length = offset - startIndex;
204-
if (length == 0)
211+
if (offset > startIndex)
205212
{
206-
return 0;
213+
return input.Substring(startIndex, length);
207214
}
208215

209-
value = input.Substring(startIndex, length);
210-
return length;
216+
return string.Empty;
211217
}
212218

213219
private static bool ReadEqualsSign(string input, ref int offset)
@@ -252,8 +258,9 @@ internal static void CheckValueFormat(string value, string parameterName)
252258
throw new ArgumentNullException(nameof(value));
253259
}
254260

255-
string temp;
256-
if (GetCookieValueLength(value, 0, out temp) != value.Length)
261+
var offset = 0;
262+
var result = GetCookieValue(value, ref offset);
263+
if (result.Length != value.Length)
257264
{
258265
throw new ArgumentException("Invalid cookie value: " + value, parameterName);
259266
}

src/Microsoft.Net.Http.Headers/EntityTagHeaderValue.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,21 @@ public static IList<EntityTagHeaderValue> ParseList(IList<string> inputs)
129129
return MultipleValueParser.ParseValues(inputs);
130130
}
131131

132+
public static IList<EntityTagHeaderValue> ParseStrictList(IList<string> inputs)
133+
{
134+
return MultipleValueParser.ParseStrictValues(inputs);
135+
}
136+
132137
public static bool TryParseList(IList<string> inputs, out IList<EntityTagHeaderValue> parsedValues)
133138
{
134139
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
135140
}
136141

142+
public static bool TryParseStrictList(IList<string> inputs, out IList<EntityTagHeaderValue> parsedValues)
143+
{
144+
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
145+
}
146+
137147
internal static int GetEntityTagLength(string input, int startIndex, out EntityTagHeaderValue parsedValue)
138148
{
139149
Contract.Requires(startIndex >= 0);

src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,23 @@ public T ParseValue(string value, ref int index)
3939
T result;
4040
if (!TryParseValue(value, ref index, out result))
4141
{
42-
throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid value '{0}'.",
43-
value?.Substring(index) ?? "<null>"));
42+
throw new FormatException(string.Format(CultureInfo.InvariantCulture,
43+
"The header contains invalid values at index {0}: '{1}'", index, value ?? "<null>"));
4444
}
4545
return result;
4646
}
4747

4848
public virtual bool TryParseValues(IList<string> values, out IList<T> parsedValues)
49+
{
50+
return TryParseValues(values, strict: false, parsedValues: out parsedValues);
51+
}
52+
53+
public virtual bool TryParseStrictValues(IList<string> values, out IList<T> parsedValues)
54+
{
55+
return TryParseValues(values, strict: true, parsedValues: out parsedValues);
56+
}
57+
58+
protected virtual bool TryParseValues(IList<string> values, bool strict, out IList<T> parsedValues)
4959
{
5060
Contract.Assert(_supportsMultipleValues);
5161
// 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 @@ public virtual bool TryParseValues(IList<string> values, out IList<T> parsedValu
7181
results.Add(output);
7282
}
7383
}
74-
else
84+
else if (strict)
7585
{
7686
return false;
7787
}
88+
else
89+
{
90+
// Skip the invalid values and keep trying.
91+
index++;
92+
}
7893
}
7994
}
8095
if (results.Count > 0)
@@ -85,7 +100,17 @@ public virtual bool TryParseValues(IList<string> values, out IList<T> parsedValu
85100
return false;
86101
}
87102

88-
public IList<T> ParseValues(IList<string> values)
103+
public virtual IList<T> ParseValues(IList<string> values)
104+
{
105+
return ParseValues(values, strict: false);
106+
}
107+
108+
public virtual IList<T> ParseStrictValues(IList<string> values)
109+
{
110+
return ParseValues(values, strict: true);
111+
}
112+
113+
protected virtual IList<T> ParseValues(IList<string> values, bool strict)
89114
{
90115
Contract.Assert(_supportsMultipleValues);
91116
// 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 @@ public IList<T> ParseValues(IList<string> values)
110135
parsedValues.Add(output);
111136
}
112137
}
138+
else if (strict)
139+
{
140+
throw new FormatException(string.Format(CultureInfo.InvariantCulture,
141+
"The header contains invalid values at index {0}: '{1}'", index, value));
142+
}
113143
else
114144
{
115-
throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid values '{0}'.",
116-
value.Substring(index)));
145+
// Skip the invalid values and keep trying.
146+
index++;
117147
}
118148
}
119149
}

src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,11 +391,21 @@ public static IList<MediaTypeHeaderValue> ParseList(IList<string> inputs)
391391
return MultipleValueParser.ParseValues(inputs);
392392
}
393393

394+
public static IList<MediaTypeHeaderValue> ParseStrictList(IList<string> inputs)
395+
{
396+
return MultipleValueParser.ParseStrictValues(inputs);
397+
}
398+
394399
public static bool TryParseList(IList<string> inputs, out IList<MediaTypeHeaderValue> parsedValues)
395400
{
396401
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
397402
}
398403

404+
public static bool TryParseStrictList(IList<string> inputs, out IList<MediaTypeHeaderValue> parsedValues)
405+
{
406+
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
407+
}
408+
399409
private static int GetMediaTypeLength(string input, int startIndex, out MediaTypeHeaderValue parsedValue)
400410
{
401411
Contract.Requires(startIndex >= 0);

src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,21 @@ public static IList<NameValueHeaderValue> ParseList(IList<string> input)
158158
return MultipleValueParser.ParseValues(input);
159159
}
160160

161+
public static IList<NameValueHeaderValue> ParseStrictList(IList<string> input)
162+
{
163+
return MultipleValueParser.ParseStrictValues(input);
164+
}
165+
161166
public static bool TryParseList(IList<string> input, out IList<NameValueHeaderValue> parsedValues)
162167
{
163168
return MultipleValueParser.TryParseValues(input, out parsedValues);
164169
}
165170

171+
public static bool TryParseStrictList(IList<string> input, out IList<NameValueHeaderValue> parsedValues)
172+
{
173+
return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
174+
}
175+
166176
public override string ToString()
167177
{
168178
if (!string.IsNullOrEmpty(_value))

src/Microsoft.Net.Http.Headers/SetCookieHeaderValue.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,21 @@ public static IList<SetCookieHeaderValue> ParseList(IList<string> inputs)
156156
return MultipleValueParser.ParseValues(inputs);
157157
}
158158

159+
public static IList<SetCookieHeaderValue> ParseStrictList(IList<string> inputs)
160+
{
161+
return MultipleValueParser.ParseStrictValues(inputs);
162+
}
163+
159164
public static bool TryParseList(IList<string> inputs, out IList<SetCookieHeaderValue> parsedValues)
160165
{
161166
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
162167
}
163168

169+
public static bool TryParseStrictList(IList<string> inputs, out IList<SetCookieHeaderValue> parsedValues)
170+
{
171+
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
172+
}
173+
164174
// name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; httponly
165175
private static int GetSetCookieLength(string input, int startIndex, out SetCookieHeaderValue parsedValue)
166176
{
@@ -195,12 +205,9 @@ private static int GetSetCookieLength(string input, int startIndex, out SetCooki
195205
return 0;
196206
}
197207

198-
string value;
199208
// value or "quoted value"
200-
itemLength = CookieHeaderValue.GetCookieValueLength(input, offset, out value);
201209
// The value may be empty
202-
result._value = input.Substring(offset, itemLength);
203-
offset += itemLength;
210+
result._value = CookieHeaderValue.GetCookieValue(input, ref offset);
204211

205212
// *(';' SP cookie-av)
206213
while (offset < input.Length)

src/Microsoft.Net.Http.Headers/StringWithQualityHeaderValue.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,21 @@ public static IList<StringWithQualityHeaderValue> ParseList(IList<string> input)
119119
return MultipleValueParser.ParseValues(input);
120120
}
121121

122+
public static IList<StringWithQualityHeaderValue> ParseStrictList(IList<string> input)
123+
{
124+
return MultipleValueParser.ParseStrictValues(input);
125+
}
126+
122127
public static bool TryParseList(IList<string> input, out IList<StringWithQualityHeaderValue> parsedValues)
123128
{
124129
return MultipleValueParser.TryParseValues(input, out parsedValues);
125130
}
126131

132+
public static bool TryParseStrictList(IList<string> input, out IList<StringWithQualityHeaderValue> parsedValues)
133+
{
134+
return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
135+
}
136+
127137
private static int GetStringWithQualityLength(string input, int startIndex, out StringWithQualityHeaderValue parsedValue)
128138
{
129139
Contract.Requires(startIndex >= 0);

0 commit comments

Comments
 (0)