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

Commit 5ffb81d

Browse files
committed
#814 Rework CookieAuth for compat with CookiePolicy.
1 parent 59fc691 commit 5ffb81d

File tree

5 files changed

+142
-141
lines changed

5 files changed

+142
-141
lines changed

src/Microsoft.AspNetCore.Authentication.Cookies/ChunkingCookieManager.cs

Lines changed: 29 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
1818
/// </summary>
1919
public class ChunkingCookieManager : ICookieManager
2020
{
21-
public ChunkingCookieManager(UrlEncoder urlEncoder)
21+
private const string ChunkKeySuffix = "C";
22+
private const string ChunkCountPrefix = "chunks-";
23+
24+
public ChunkingCookieManager()
2225
{
2326
// Lowest common denominator. Safari has the lowest known limit (4093), and we leave little extra just in case.
2427
// See http://browsercookielimits.x64.me/.
25-
ChunkSize = 4090;
28+
// Leave at least 20 in case CookiePolicy tries to add 'secure' and/or 'httponly'.
29+
ChunkSize = 4070;
2630
ThrowForPartialCookies = true;
27-
Encoder = urlEncoder ?? UrlEncoder.Default;
2831
}
2932

3033
/// <summary>
@@ -41,14 +44,12 @@ public ChunkingCookieManager(UrlEncoder urlEncoder)
4144
/// </summary>
4245
public bool ThrowForPartialCookies { get; set; }
4346

44-
private UrlEncoder Encoder { get; set; }
45-
46-
// Parse the "chunks:XX" to determine how many chunks there should be.
47+
// Parse the "chunks-XX" to determine how many chunks there should be.
4748
private static int ParseChunksCount(string value)
4849
{
49-
if (value != null && value.StartsWith("chunks:", StringComparison.Ordinal))
50+
if (value != null && value.StartsWith(ChunkCountPrefix, StringComparison.Ordinal))
5051
{
51-
var chunksCountString = value.Substring("chunks:".Length);
52+
var chunksCountString = value.Substring(ChunkCountPrefix.Length);
5253
int chunksCount;
5354
if (int.TryParse(chunksCountString, NumberStyles.None, CultureInfo.InvariantCulture, out chunksCount))
5455
{
@@ -60,7 +61,7 @@ private static int ParseChunksCount(string value)
6061

6162
/// <summary>
6263
/// Get the reassembled cookie. Non chunked cookies are returned normally.
63-
/// Cookies with missing chunks just have their "chunks:XX" header returned.
64+
/// Cookies with missing chunks just have their "chunks-XX" header returned.
6465
/// </summary>
6566
/// <param name="context"></param>
6667
/// <param name="key"></param>
@@ -82,11 +83,10 @@ public string GetRequestCookie(HttpContext context, string key)
8283
var chunksCount = ParseChunksCount(value);
8384
if (chunksCount > 0)
8485
{
85-
var quoted = false;
8686
var chunks = new string[chunksCount];
8787
for (var chunkId = 1; chunkId <= chunksCount; chunkId++)
8888
{
89-
var chunk = requestCookies[key + "C" + chunkId.ToString(CultureInfo.InvariantCulture)];
89+
var chunk = requestCookies[key + ChunkKeySuffix + chunkId.ToString(CultureInfo.InvariantCulture)];
9090
if (string.IsNullOrEmpty(chunk))
9191
{
9292
if (ThrowForPartialCookies)
@@ -102,28 +102,19 @@ public string GetRequestCookie(HttpContext context, string key)
102102
// Missing chunk, abort by returning the original cookie value. It may have been a false positive?
103103
return value;
104104
}
105-
if (IsQuoted(chunk))
106-
{
107-
// Note: Since we assume these cookies were generated by our code, then we can assume that if one cookie has quotes then they all do.
108-
quoted = true;
109-
chunk = RemoveQuotes(chunk);
110-
}
105+
111106
chunks[chunkId - 1] = chunk;
112107
}
113-
var merged = string.Join(string.Empty, chunks);
114-
if (quoted)
115-
{
116-
merged = Quote(merged);
117-
}
118-
return merged;
108+
109+
return string.Join(string.Empty, chunks);
119110
}
120111
return value;
121112
}
122113

123114
/// <summary>
124115
/// Appends a new response cookie to the Set-Cookie header. If the cookie is larger than the given size limit
125116
/// then it will be broken down into multiple cookies as follows:
126-
/// Set-Cookie: CookieName=chunks:3; path=/
117+
/// Set-Cookie: CookieName=chunks-3; path=/
127118
/// Set-Cookie: CookieNameC1=Segment1; path=/
128119
/// Set-Cookie: CookieNameC2=Segment2; path=/
129120
/// Set-Cookie: CookieNameC3=Segment3; path=/
@@ -149,9 +140,7 @@ public void AppendResponseCookie(HttpContext context, string key, string value,
149140
throw new ArgumentNullException(nameof(options));
150141
}
151142

152-
var escapedKey = Encoder.Encode(key);
153-
154-
var template = new SetCookieHeaderValue(escapedKey)
143+
var template = new SetCookieHeaderValue(key)
155144
{
156145
Domain = options.Domain,
157146
Expires = options.Expires,
@@ -163,22 +152,14 @@ public void AppendResponseCookie(HttpContext context, string key, string value,
163152
var templateLength = template.ToString().Length;
164153

165154
value = value ?? string.Empty;
166-
var quoted = false;
167-
if (IsQuoted(value))
168-
{
169-
quoted = true;
170-
value = RemoveQuotes(value);
171-
}
172-
var escapedValue = Encoder.Encode(value);
173155

174156
// Normal cookie
175-
var responseHeaders = context.Response.Headers;
176-
if (!ChunkSize.HasValue || ChunkSize.Value > templateLength + escapedValue.Length + (quoted ? 2 : 0))
157+
var responseCookies = context.Response.Cookies;
158+
if (!ChunkSize.HasValue || ChunkSize.Value > templateLength + value.Length)
177159
{
178-
template.Value = quoted ? Quote(escapedValue) : escapedValue;
179-
responseHeaders.Append(Constants.Headers.SetCookie, template.ToString());
160+
responseCookies.Append(key, value, options);
180161
}
181-
else if (ChunkSize.Value < templateLength + (quoted ? 2 : 0) + 10)
162+
else if (ChunkSize.Value < templateLength + 10)
182163
{
183164
// 10 is the minimum data we want to put in an individual cookie, including the cookie chunk identifier "CXX".
184165
// No room for data, we can't chunk the options and name
@@ -188,30 +169,25 @@ public void AppendResponseCookie(HttpContext context, string key, string value,
188169
{
189170
// Break the cookie down into multiple cookies.
190171
// Key = CookieName, value = "Segment1Segment2Segment2"
191-
// Set-Cookie: CookieName=chunks:3; path=/
172+
// Set-Cookie: CookieName=chunks-3; path=/
192173
// Set-Cookie: CookieNameC1="Segment1"; path=/
193174
// Set-Cookie: CookieNameC2="Segment2"; path=/
194175
// Set-Cookie: CookieNameC3="Segment3"; path=/
195-
var dataSizePerCookie = ChunkSize.Value - templateLength - (quoted ? 2 : 0) - 3; // Budget 3 chars for the chunkid.
196-
var cookieChunkCount = (int)Math.Ceiling(escapedValue.Length * 1.0 / dataSizePerCookie);
176+
var dataSizePerCookie = ChunkSize.Value - templateLength - 3; // Budget 3 chars for the chunkid.
177+
var cookieChunkCount = (int)Math.Ceiling(value.Length * 1.0 / dataSizePerCookie);
197178

198-
template.Value = "chunks:" + cookieChunkCount.ToString(CultureInfo.InvariantCulture);
199-
responseHeaders.Append(Constants.Headers.SetCookie, template.ToString());
179+
responseCookies.Append(key, ChunkCountPrefix + cookieChunkCount.ToString(CultureInfo.InvariantCulture), options);
200180

201-
var chunks = new string[cookieChunkCount];
202181
var offset = 0;
203182
for (var chunkId = 1; chunkId <= cookieChunkCount; chunkId++)
204183
{
205-
var remainingLength = escapedValue.Length - offset;
184+
var remainingLength = value.Length - offset;
206185
var length = Math.Min(dataSizePerCookie, remainingLength);
207-
var segment = escapedValue.Substring(offset, length);
186+
var segment = value.Substring(offset, length);
208187
offset += length;
209188

210-
template.Name = escapedKey + "C" + chunkId.ToString(CultureInfo.InvariantCulture);
211-
template.Value = quoted ? Quote(segment) : segment;
212-
chunks[chunkId - 1] = template.ToString();
189+
responseCookies.Append(key + ChunkKeySuffix + chunkId.ToString(CultureInfo.InvariantCulture), segment, options);
213190
}
214-
responseHeaders.Append(Constants.Headers.SetCookie, chunks);
215191
}
216192
}
217193

@@ -239,17 +215,16 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options)
239215
throw new ArgumentNullException(nameof(options));
240216
}
241217

242-
var escapedKey = Encoder.Encode(key);
243218
var keys = new List<string>();
244-
keys.Add(escapedKey + "=");
219+
keys.Add(key + "=");
245220

246221
var requestCookie = context.Request.Cookies[key];
247222
var chunks = ParseChunksCount(requestCookie);
248223
if (chunks > 0)
249224
{
250225
for (int i = 1; i <= chunks + 1; i++)
251226
{
252-
var subkey = escapedKey + "C" + i.ToString(CultureInfo.InvariantCulture);
227+
var subkey = key + ChunkKeySuffix + i.ToString(CultureInfo.InvariantCulture);
253228
keys.Add(subkey + "=");
254229
}
255230
}
@@ -304,35 +279,5 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options)
304279
});
305280
}
306281
}
307-
308-
private static bool IsQuoted(string value)
309-
{
310-
if (value == null)
311-
{
312-
throw new ArgumentNullException(nameof(value));
313-
}
314-
315-
return value.Length >= 2 && value[0] == '"' && value[value.Length - 1] == '"';
316-
}
317-
318-
private static string RemoveQuotes(string value)
319-
{
320-
if (value == null)
321-
{
322-
throw new ArgumentNullException(nameof(value));
323-
}
324-
325-
return value.Substring(1, value.Length - 2);
326-
}
327-
328-
private static string Quote(string value)
329-
{
330-
if (value == null)
331-
{
332-
throw new ArgumentNullException(nameof(value));
333-
}
334-
335-
return '"' + value + '"';
336-
}
337282
}
338283
}

src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public CookieAuthenticationMiddleware(
4242
}
4343
if (Options.CookieManager == null)
4444
{
45-
Options.CookieManager = new ChunkingCookieManager(urlEncoder);
45+
Options.CookieManager = new ChunkingCookieManager();
4646
}
4747
if (!Options.LoginPath.HasValue)
4848
{

test/Microsoft.AspNetCore.Authentication.Test/Cookies/Infrastructure/CookieChunkingTests.cs

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void AppendLargeCookie_Appended()
1515
HttpContext context = new DefaultHttpContext();
1616

1717
string testString = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
18-
new ChunkingCookieManager(null) { ChunkSize = null }.AppendResponseCookie(context, "TestCookie", testString, new CookieOptions());
18+
new ChunkingCookieManager() { ChunkSize = null }.AppendResponseCookie(context, "TestCookie", testString, new CookieOptions());
1919
var values = context.Response.Headers["Set-Cookie"];
2020
Assert.Equal(1, values.Count);
2121
Assert.Equal("TestCookie=" + testString + "; path=/", values[0]);
@@ -27,12 +27,12 @@ public void AppendLargeCookieWithLimit_Chunked()
2727
HttpContext context = new DefaultHttpContext();
2828

2929
string testString = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
30-
new ChunkingCookieManager(null) { ChunkSize = 30 }.AppendResponseCookie(context, "TestCookie", testString, new CookieOptions());
30+
new ChunkingCookieManager() { ChunkSize = 30 }.AppendResponseCookie(context, "TestCookie", testString, new CookieOptions());
3131
var values = context.Response.Headers["Set-Cookie"];
3232
Assert.Equal(9, values.Count);
3333
Assert.Equal<string[]>(new[]
3434
{
35-
"TestCookie=chunks:8; path=/",
35+
"TestCookie=chunks-8; path=/",
3636
"TestCookieC1=abcdefgh; path=/",
3737
"TestCookieC2=ijklmnop; path=/",
3838
"TestCookieC3=qrstuvwx; path=/",
@@ -44,36 +44,13 @@ public void AppendLargeCookieWithLimit_Chunked()
4444
}, values);
4545
}
4646

47-
[Fact]
48-
public void AppendLargeQuotedCookieWithLimit_QuotedChunked()
49-
{
50-
HttpContext context = new DefaultHttpContext();
51-
52-
string testString = "\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"";
53-
new ChunkingCookieManager(null) { ChunkSize = 32 }.AppendResponseCookie(context, "TestCookie", testString, new CookieOptions());
54-
var values = context.Response.Headers["Set-Cookie"];
55-
Assert.Equal(9, values.Count);
56-
Assert.Equal<string[]>(new[]
57-
{
58-
"TestCookie=chunks:8; path=/",
59-
"TestCookieC1=\"abcdefgh\"; path=/",
60-
"TestCookieC2=\"ijklmnop\"; path=/",
61-
"TestCookieC3=\"qrstuvwx\"; path=/",
62-
"TestCookieC4=\"yz012345\"; path=/",
63-
"TestCookieC5=\"6789ABCD\"; path=/",
64-
"TestCookieC6=\"EFGHIJKL\"; path=/",
65-
"TestCookieC7=\"MNOPQRST\"; path=/",
66-
"TestCookieC8=\"UVWXYZ\"; path=/",
67-
}, values);
68-
}
69-
7047
[Fact]
7148
public void GetLargeChunkedCookie_Reassembled()
7249
{
7350
HttpContext context = new DefaultHttpContext();
7451
context.Request.Headers["Cookie"] = new[]
7552
{
76-
"TestCookie=chunks:7",
53+
"TestCookie=chunks-7",
7754
"TestCookieC1=abcdefghi",
7855
"TestCookieC2=jklmnopqr",
7956
"TestCookieC3=stuvwxyz0",
@@ -83,39 +60,18 @@ public void GetLargeChunkedCookie_Reassembled()
8360
"TestCookieC7=STUVWXYZ"
8461
};
8562

86-
string result = new ChunkingCookieManager(null).GetRequestCookie(context, "TestCookie");
63+
string result = new ChunkingCookieManager().GetRequestCookie(context, "TestCookie");
8764
string testString = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
8865
Assert.Equal(testString, result);
8966
}
9067

91-
[Fact]
92-
public void GetLargeChunkedCookieWithQuotes_Reassembled()
93-
{
94-
HttpContext context = new DefaultHttpContext();
95-
context.Request.Headers["Cookie"] = new[]
96-
{
97-
"TestCookie=chunks:7",
98-
"TestCookieC1=\"abcdefghi\"",
99-
"TestCookieC2=\"jklmnopqr\"",
100-
"TestCookieC3=\"stuvwxyz0\"",
101-
"TestCookieC4=\"123456789\"",
102-
"TestCookieC5=\"ABCDEFGHI\"",
103-
"TestCookieC6=\"JKLMNOPQR\"",
104-
"TestCookieC7=\"STUVWXYZ\""
105-
};
106-
107-
string result = new ChunkingCookieManager(null).GetRequestCookie(context, "TestCookie");
108-
string testString = "\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"";
109-
Assert.Equal(testString, result);
110-
}
111-
11268
[Fact]
11369
public void GetLargeChunkedCookieWithMissingChunk_ThrowingEnabled_Throws()
11470
{
11571
HttpContext context = new DefaultHttpContext();
11672
context.Request.Headers["Cookie"] = new[]
11773
{
118-
"TestCookie=chunks:7",
74+
"TestCookie=chunks-7",
11975
"TestCookieC1=abcdefghi",
12076
// Missing chunk "TestCookieC2=jklmnopqr",
12177
"TestCookieC3=stuvwxyz0",
@@ -125,7 +81,7 @@ public void GetLargeChunkedCookieWithMissingChunk_ThrowingEnabled_Throws()
12581
"TestCookieC7=STUVWXYZ"
12682
};
12783

128-
Assert.Throws<FormatException>(() => new ChunkingCookieManager(null).GetRequestCookie(context, "TestCookie"));
84+
Assert.Throws<FormatException>(() => new ChunkingCookieManager().GetRequestCookie(context, "TestCookie"));
12985
}
13086

13187
[Fact]
@@ -134,7 +90,7 @@ public void GetLargeChunkedCookieWithMissingChunk_ThrowingDisabled_NotReassemble
13490
HttpContext context = new DefaultHttpContext();
13591
context.Request.Headers["Cookie"] = new[]
13692
{
137-
"TestCookie=chunks:7",
93+
"TestCookie=chunks-7",
13894
"TestCookieC1=abcdefghi",
13995
// Missing chunk "TestCookieC2=jklmnopqr",
14096
"TestCookieC3=stuvwxyz0",
@@ -144,18 +100,18 @@ public void GetLargeChunkedCookieWithMissingChunk_ThrowingDisabled_NotReassemble
144100
"TestCookieC7=STUVWXYZ"
145101
};
146102

147-
string result = new ChunkingCookieManager(null) { ThrowForPartialCookies = false }.GetRequestCookie(context, "TestCookie");
148-
string testString = "chunks:7";
103+
string result = new ChunkingCookieManager() { ThrowForPartialCookies = false }.GetRequestCookie(context, "TestCookie");
104+
string testString = "chunks-7";
149105
Assert.Equal(testString, result);
150106
}
151107

152108
[Fact]
153109
public void DeleteChunkedCookieWithOptions_AllDeleted()
154110
{
155111
HttpContext context = new DefaultHttpContext();
156-
context.Request.Headers.Append("Cookie", "TestCookie=chunks:7");
112+
context.Request.Headers.Append("Cookie", "TestCookie=chunks-7");
157113

158-
new ChunkingCookieManager(null).DeleteCookie(context, "TestCookie", new CookieOptions() { Domain = "foo.com" });
114+
new ChunkingCookieManager().DeleteCookie(context, "TestCookie", new CookieOptions() { Domain = "foo.com" });
159115
var cookies = context.Response.Headers["Set-Cookie"];
160116
Assert.Equal(8, cookies.Count);
161117
Assert.Equal(new[]

0 commit comments

Comments
 (0)