@@ -18,13 +18,16 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
18
18
/// </summary>
19
19
public class ChunkingCookieManager : ICookieManager
20
20
{
21
- public ChunkingCookieManager ( UrlEncoder urlEncoder )
21
+ private const string ChunkKeySuffix = "C" ;
22
+ private const string ChunkCountPrefix = "chunks-" ;
23
+
24
+ public ChunkingCookieManager ( )
22
25
{
23
26
// Lowest common denominator. Safari has the lowest known limit (4093), and we leave little extra just in case.
24
27
// 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 ;
26
30
ThrowForPartialCookies = true ;
27
- Encoder = urlEncoder ?? UrlEncoder . Default ;
28
31
}
29
32
30
33
/// <summary>
@@ -41,14 +44,12 @@ public ChunkingCookieManager(UrlEncoder urlEncoder)
41
44
/// </summary>
42
45
public bool ThrowForPartialCookies { get ; set ; }
43
46
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.
47
48
private static int ParseChunksCount ( string value )
48
49
{
49
- if ( value != null && value . StartsWith ( "chunks:" , StringComparison . Ordinal ) )
50
+ if ( value != null && value . StartsWith ( ChunkCountPrefix , StringComparison . Ordinal ) )
50
51
{
51
- var chunksCountString = value . Substring ( "chunks:" . Length ) ;
52
+ var chunksCountString = value . Substring ( ChunkCountPrefix . Length ) ;
52
53
int chunksCount ;
53
54
if ( int . TryParse ( chunksCountString , NumberStyles . None , CultureInfo . InvariantCulture , out chunksCount ) )
54
55
{
@@ -60,7 +61,7 @@ private static int ParseChunksCount(string value)
60
61
61
62
/// <summary>
62
63
/// 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.
64
65
/// </summary>
65
66
/// <param name="context"></param>
66
67
/// <param name="key"></param>
@@ -82,11 +83,10 @@ public string GetRequestCookie(HttpContext context, string key)
82
83
var chunksCount = ParseChunksCount ( value ) ;
83
84
if ( chunksCount > 0 )
84
85
{
85
- var quoted = false ;
86
86
var chunks = new string [ chunksCount ] ;
87
87
for ( var chunkId = 1 ; chunkId <= chunksCount ; chunkId ++ )
88
88
{
89
- var chunk = requestCookies [ key + "C" + chunkId . ToString ( CultureInfo . InvariantCulture ) ] ;
89
+ var chunk = requestCookies [ key + ChunkKeySuffix + chunkId . ToString ( CultureInfo . InvariantCulture ) ] ;
90
90
if ( string . IsNullOrEmpty ( chunk ) )
91
91
{
92
92
if ( ThrowForPartialCookies )
@@ -102,28 +102,19 @@ public string GetRequestCookie(HttpContext context, string key)
102
102
// Missing chunk, abort by returning the original cookie value. It may have been a false positive?
103
103
return value ;
104
104
}
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
+
111
106
chunks [ chunkId - 1 ] = chunk ;
112
107
}
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 ) ;
119
110
}
120
111
return value ;
121
112
}
122
113
123
114
/// <summary>
124
115
/// Appends a new response cookie to the Set-Cookie header. If the cookie is larger than the given size limit
125
116
/// 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=/
127
118
/// Set-Cookie: CookieNameC1=Segment1; path=/
128
119
/// Set-Cookie: CookieNameC2=Segment2; path=/
129
120
/// Set-Cookie: CookieNameC3=Segment3; path=/
@@ -149,9 +140,7 @@ public void AppendResponseCookie(HttpContext context, string key, string value,
149
140
throw new ArgumentNullException ( nameof ( options ) ) ;
150
141
}
151
142
152
- var escapedKey = Encoder . Encode ( key ) ;
153
-
154
- var template = new SetCookieHeaderValue ( escapedKey )
143
+ var template = new SetCookieHeaderValue ( key )
155
144
{
156
145
Domain = options . Domain ,
157
146
Expires = options . Expires ,
@@ -163,22 +152,14 @@ public void AppendResponseCookie(HttpContext context, string key, string value,
163
152
var templateLength = template . ToString ( ) . Length ;
164
153
165
154
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 ) ;
173
155
174
156
// 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 )
177
159
{
178
- template . Value = quoted ? Quote ( escapedValue ) : escapedValue ;
179
- responseHeaders . Append ( Constants . Headers . SetCookie , template . ToString ( ) ) ;
160
+ responseCookies . Append ( key , value , options ) ;
180
161
}
181
- else if ( ChunkSize . Value < templateLength + ( quoted ? 2 : 0 ) + 10 )
162
+ else if ( ChunkSize . Value < templateLength + 10 )
182
163
{
183
164
// 10 is the minimum data we want to put in an individual cookie, including the cookie chunk identifier "CXX".
184
165
// 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,
188
169
{
189
170
// Break the cookie down into multiple cookies.
190
171
// Key = CookieName, value = "Segment1Segment2Segment2"
191
- // Set-Cookie: CookieName=chunks: 3; path=/
172
+ // Set-Cookie: CookieName=chunks- 3; path=/
192
173
// Set-Cookie: CookieNameC1="Segment1"; path=/
193
174
// Set-Cookie: CookieNameC2="Segment2"; path=/
194
175
// 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 ) ;
197
178
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 ) ;
200
180
201
- var chunks = new string [ cookieChunkCount ] ;
202
181
var offset = 0 ;
203
182
for ( var chunkId = 1 ; chunkId <= cookieChunkCount ; chunkId ++ )
204
183
{
205
- var remainingLength = escapedValue . Length - offset ;
184
+ var remainingLength = value . Length - offset ;
206
185
var length = Math . Min ( dataSizePerCookie , remainingLength ) ;
207
- var segment = escapedValue . Substring ( offset , length ) ;
186
+ var segment = value . Substring ( offset , length ) ;
208
187
offset += length ;
209
188
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 ) ;
213
190
}
214
- responseHeaders . Append ( Constants . Headers . SetCookie , chunks ) ;
215
191
}
216
192
}
217
193
@@ -239,17 +215,16 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options)
239
215
throw new ArgumentNullException ( nameof ( options ) ) ;
240
216
}
241
217
242
- var escapedKey = Encoder . Encode ( key ) ;
243
218
var keys = new List < string > ( ) ;
244
- keys . Add ( escapedKey + "=" ) ;
219
+ keys . Add ( key + "=" ) ;
245
220
246
221
var requestCookie = context . Request . Cookies [ key ] ;
247
222
var chunks = ParseChunksCount ( requestCookie ) ;
248
223
if ( chunks > 0 )
249
224
{
250
225
for ( int i = 1 ; i <= chunks + 1 ; i ++ )
251
226
{
252
- var subkey = escapedKey + "C" + i . ToString ( CultureInfo . InvariantCulture ) ;
227
+ var subkey = key + ChunkKeySuffix + i . ToString ( CultureInfo . InvariantCulture ) ;
253
228
keys . Add ( subkey + "=" ) ;
254
229
}
255
230
}
@@ -304,35 +279,5 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options)
304
279
} ) ;
305
280
}
306
281
}
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
- }
337
282
}
338
283
}
0 commit comments