5
5
using System . Collections . Generic ;
6
6
using System . Diagnostics . Contracts ;
7
7
using System . Text ;
8
+ using Microsoft . AspNetCore . Http ;
8
9
using Microsoft . Extensions . Primitives ;
9
10
10
11
namespace Microsoft . Net . Http . Headers
@@ -17,6 +18,9 @@ public class SetCookieHeaderValue
17
18
private const string DomainToken = "domain" ;
18
19
private const string PathToken = "path" ;
19
20
private const string SecureToken = "secure" ;
21
+ private const string SameSiteToken = "samesite" ;
22
+ private static readonly string SameSiteLaxToken = SameSiteEnforcementMode . Lax . ToString ( ) ;
23
+ private static readonly string SameSiteStrictToken = SameSiteEnforcementMode . Strict . ToString ( ) ;
20
24
private const string HttpOnlyToken = "httponly" ;
21
25
private const string SeparatorToken = "; " ;
22
26
private const string EqualsToken = "=" ;
@@ -87,15 +91,18 @@ public string Value
87
91
88
92
public bool Secure { get ; set ; }
89
93
94
+ public SameSiteEnforcementMode SameSite { get ; set ; }
95
+
90
96
public bool HttpOnly { get ; set ; }
91
97
92
- // name="val ue "; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; httponly
98
+ // name="value "; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax} ; httponly
93
99
public override string ToString ( )
94
100
{
95
101
var length = _name . Length + EqualsToken . Length + _value . Length ;
96
102
97
103
string expires = null ;
98
104
string maxAge = null ;
105
+ string sameSite = null ;
99
106
100
107
if ( Expires . HasValue )
101
108
{
@@ -124,6 +131,12 @@ public override string ToString()
124
131
length += SeparatorToken . Length + SecureToken . Length ;
125
132
}
126
133
134
+ if ( SameSite != SameSiteEnforcementMode . None )
135
+ {
136
+ sameSite = SameSite == SameSiteEnforcementMode . Lax ? SameSiteLaxToken : SameSiteStrictToken ;
137
+ length += SeparatorToken . Length + SameSiteToken . Length + EqualsToken . Length + sameSite . Length ;
138
+ }
139
+
127
140
if ( HttpOnly )
128
141
{
129
142
length += SeparatorToken . Length + HttpOnlyToken . Length ;
@@ -160,6 +173,11 @@ public override string ToString()
160
173
AppendSegment ( ref sb , SecureToken , null ) ;
161
174
}
162
175
176
+ if ( SameSite != SameSiteEnforcementMode . None )
177
+ {
178
+ AppendSegment ( ref sb , SameSiteToken , sameSite ) ;
179
+ }
180
+
163
181
if ( HttpOnly )
164
182
{
165
183
AppendSegment ( ref sb , HttpOnlyToken , null ) ;
@@ -218,6 +236,11 @@ public void AppendToStringBuilder(StringBuilder builder)
218
236
AppendSegment ( builder , SecureToken , null ) ;
219
237
}
220
238
239
+ if ( SameSite != SameSiteEnforcementMode . None )
240
+ {
241
+ AppendSegment ( builder , SameSiteToken , SameSite == SameSiteEnforcementMode . Lax ? SameSiteLaxToken : SameSiteStrictToken ) ;
242
+ }
243
+
221
244
if ( HttpOnly )
222
245
{
223
246
AppendSegment ( builder , HttpOnlyToken , null ) ;
@@ -267,7 +290,7 @@ public static bool TryParseStrictList(IList<string> inputs, out IList<SetCookieH
267
290
return MultipleValueParser . TryParseStrictValues ( inputs , out parsedValues ) ;
268
291
}
269
292
270
- // name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; httponly
293
+ // name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax}; httponly
271
294
private static int GetSetCookieLength ( string input , int startIndex , out SetCookieHeaderValue parsedValue )
272
295
{
273
296
Contract . Requires ( startIndex >= 0 ) ;
@@ -322,7 +345,7 @@ private static int GetSetCookieLength(string input, int startIndex, out SetCooki
322
345
323
346
offset += HttpRuleParser . GetWhitespaceLength ( input , offset ) ;
324
347
325
- // cookie-av = expires-av / max-age-av / domain-av / path-av / secure-av / httponly-av / extension-av
348
+ // cookie-av = expires-av / max-age-av / domain-av / path-av / secure-av / samesite-av / httponly-av / extension-av
326
349
itemLength = HttpRuleParser . GetTokenLength ( input , offset ) ;
327
350
if ( itemLength == 0 )
328
351
{
@@ -402,6 +425,28 @@ private static int GetSetCookieLength(string input, int startIndex, out SetCooki
402
425
{
403
426
result . Secure = true ;
404
427
}
428
+ // samesite-av = "SameSite" / "SameSite=" samesite-value
429
+ // samesite-value = "Strict" / "Lax"
430
+ else if ( string . Equals ( token , SameSiteToken , StringComparison . OrdinalIgnoreCase ) )
431
+ {
432
+ if ( ! ReadEqualsSign ( input , ref offset ) )
433
+ {
434
+ result . SameSite = SameSiteEnforcementMode . Strict ;
435
+ }
436
+ else
437
+ {
438
+ var enforcementMode = ReadToSemicolonOrEnd ( input , ref offset ) ;
439
+
440
+ if ( string . Equals ( enforcementMode , SameSiteLaxToken , StringComparison . OrdinalIgnoreCase ) )
441
+ {
442
+ result . SameSite = SameSiteEnforcementMode . Lax ;
443
+ }
444
+ else if ( string . Equals ( enforcementMode , SameSiteStrictToken , StringComparison . OrdinalIgnoreCase ) )
445
+ {
446
+ result . SameSite = SameSiteEnforcementMode . Strict ;
447
+ }
448
+ }
449
+ }
405
450
// httponly-av = "HttpOnly"
406
451
else if ( string . Equals ( token , HttpOnlyToken , StringComparison . OrdinalIgnoreCase ) )
407
452
{
@@ -459,6 +504,7 @@ public override bool Equals(object obj)
459
504
&& string . Equals ( Domain , other . Domain , StringComparison . OrdinalIgnoreCase )
460
505
&& string . Equals ( Path , other . Path , StringComparison . OrdinalIgnoreCase )
461
506
&& Secure == other . Secure
507
+ && SameSite == other . SameSite
462
508
&& HttpOnly == other . HttpOnly ;
463
509
}
464
510
@@ -471,6 +517,7 @@ public override int GetHashCode()
471
517
^ ( Domain != null ? StringComparer . OrdinalIgnoreCase . GetHashCode ( Domain ) : 0 )
472
518
^ ( Path != null ? StringComparer . OrdinalIgnoreCase . GetHashCode ( Path ) : 0 )
473
519
^ Secure . GetHashCode ( )
520
+ ^ SameSite . GetHashCode ( )
474
521
^ HttpOnly . GetHashCode ( ) ;
475
522
}
476
523
}
0 commit comments