Add SameSite attribute to SetCookie header
This commit is contained in:
parent
6e87b0f5eb
commit
e8123db21e
|
|
@ -42,6 +42,13 @@ namespace Microsoft.AspNetCore.Http
|
|||
/// <returns>true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false.</returns>
|
||||
public bool Secure { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value for the SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.Lax"/>
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="SameSiteMode"/> representing the enforcement mode of the cookie.</returns>
|
||||
public SameSiteMode SameSite { get; set; } = SameSiteMode.Lax;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that indicates whether a cookie is accessible by client-side script.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
// RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
|
||||
// This mirrors Microsoft.Net.Http.Headers.SameSiteMode
|
||||
public enum SameSiteMode
|
||||
{
|
||||
None = 0,
|
||||
Lax,
|
||||
Strict
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,8 @@ namespace Microsoft.AspNetCore.Http.Internal
|
|||
Path = options.Path,
|
||||
Expires = options.Expires,
|
||||
Secure = options.Secure,
|
||||
HttpOnly = options.HttpOnly,
|
||||
SameSite = (Net.Http.Headers.SameSiteMode)options.SameSite,
|
||||
HttpOnly = options.HttpOnly
|
||||
};
|
||||
|
||||
var cookieValue = setCookieHeaderValue.ToString();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Net.Http.Headers
|
||||
{
|
||||
// RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
|
||||
public enum SameSiteMode
|
||||
{
|
||||
None = 0,
|
||||
Lax,
|
||||
Strict
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,10 @@ namespace Microsoft.Net.Http.Headers
|
|||
private const string DomainToken = "domain";
|
||||
private const string PathToken = "path";
|
||||
private const string SecureToken = "secure";
|
||||
// RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
|
||||
private const string SameSiteToken = "samesite";
|
||||
private static readonly string SameSiteLaxToken = SameSiteMode.Lax.ToString().ToLower();
|
||||
private static readonly string SameSiteStrictToken = SameSiteMode.Strict.ToString().ToLower();
|
||||
private const string HttpOnlyToken = "httponly";
|
||||
private const string SeparatorToken = "; ";
|
||||
private const string EqualsToken = "=";
|
||||
|
|
@ -87,15 +91,18 @@ namespace Microsoft.Net.Http.Headers
|
|||
|
||||
public bool Secure { get; set; }
|
||||
|
||||
public SameSiteMode SameSite { get; set; }
|
||||
|
||||
public bool HttpOnly { get; set; }
|
||||
|
||||
// name="val ue"; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; httponly
|
||||
// name="value"; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax}; httponly
|
||||
public override string ToString()
|
||||
{
|
||||
var length = _name.Length + EqualsToken.Length + _value.Length;
|
||||
|
||||
string expires = null;
|
||||
string maxAge = null;
|
||||
string sameSite = null;
|
||||
|
||||
if (Expires.HasValue)
|
||||
{
|
||||
|
|
@ -124,6 +131,12 @@ namespace Microsoft.Net.Http.Headers
|
|||
length += SeparatorToken.Length + SecureToken.Length;
|
||||
}
|
||||
|
||||
if (SameSite != SameSiteMode.None)
|
||||
{
|
||||
sameSite = SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken;
|
||||
length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
|
||||
}
|
||||
|
||||
if (HttpOnly)
|
||||
{
|
||||
length += SeparatorToken.Length + HttpOnlyToken.Length;
|
||||
|
|
@ -160,6 +173,11 @@ namespace Microsoft.Net.Http.Headers
|
|||
AppendSegment(ref sb, SecureToken, null);
|
||||
}
|
||||
|
||||
if (SameSite != SameSiteMode.None)
|
||||
{
|
||||
AppendSegment(ref sb, SameSiteToken, sameSite);
|
||||
}
|
||||
|
||||
if (HttpOnly)
|
||||
{
|
||||
AppendSegment(ref sb, HttpOnlyToken, null);
|
||||
|
|
@ -218,6 +236,11 @@ namespace Microsoft.Net.Http.Headers
|
|||
AppendSegment(builder, SecureToken, null);
|
||||
}
|
||||
|
||||
if (SameSite != SameSiteMode.None)
|
||||
{
|
||||
AppendSegment(builder, SameSiteToken, SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken);
|
||||
}
|
||||
|
||||
if (HttpOnly)
|
||||
{
|
||||
AppendSegment(builder, HttpOnlyToken, null);
|
||||
|
|
@ -267,7 +290,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
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
|
||||
// name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax}; httponly
|
||||
private static int GetSetCookieLength(string input, int startIndex, out SetCookieHeaderValue parsedValue)
|
||||
{
|
||||
Contract.Requires(startIndex >= 0);
|
||||
|
|
@ -322,7 +345,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
|
||||
offset += HttpRuleParser.GetWhitespaceLength(input, offset);
|
||||
|
||||
// cookie-av = expires-av / max-age-av / domain-av / path-av / secure-av / httponly-av / extension-av
|
||||
// cookie-av = expires-av / max-age-av / domain-av / path-av / secure-av / samesite-av / httponly-av / extension-av
|
||||
itemLength = HttpRuleParser.GetTokenLength(input, offset);
|
||||
if (itemLength == 0)
|
||||
{
|
||||
|
|
@ -402,6 +425,28 @@ namespace Microsoft.Net.Http.Headers
|
|||
{
|
||||
result.Secure = true;
|
||||
}
|
||||
// samesite-av = "SameSite" / "SameSite=" samesite-value
|
||||
// samesite-value = "Strict" / "Lax"
|
||||
else if (string.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!ReadEqualsSign(input, ref offset))
|
||||
{
|
||||
result.SameSite = SameSiteMode.Strict;
|
||||
}
|
||||
else
|
||||
{
|
||||
var enforcementMode = ReadToSemicolonOrEnd(input, ref offset);
|
||||
|
||||
if (string.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.SameSite = SameSiteMode.Lax;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.SameSite = SameSiteMode.Strict;
|
||||
}
|
||||
}
|
||||
}
|
||||
// httponly-av = "HttpOnly"
|
||||
else if (string.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
@ -459,6 +504,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
&& string.Equals(Domain, other.Domain, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(Path, other.Path, StringComparison.OrdinalIgnoreCase)
|
||||
&& Secure == other.Secure
|
||||
&& SameSite == other.SameSite
|
||||
&& HttpOnly == other.HttpOnly;
|
||||
}
|
||||
|
||||
|
|
@ -471,6 +517,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
^ (Domain != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Domain) : 0)
|
||||
^ (Path != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Path) : 0)
|
||||
^ Secure.GetHashCode()
|
||||
^ SameSite.GetHashCode()
|
||||
^ HttpOnly.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ namespace Microsoft.Net.Http.Headers
|
|||
{
|
||||
Domain = "domain1",
|
||||
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
||||
SameSite = SameSiteMode.Strict,
|
||||
HttpOnly = true,
|
||||
MaxAge = TimeSpan.FromDays(1),
|
||||
Path = "path1",
|
||||
Secure = true
|
||||
};
|
||||
dataset.Add(header1, "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");
|
||||
dataset.Add(header1, "name1=n1=v1&n2=v2&n3=v3; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite=strict; httponly");
|
||||
|
||||
var header2 = new SetCookieHeaderValue("name2", "");
|
||||
dataset.Add(header2, "name2=");
|
||||
|
|
@ -46,6 +47,19 @@ namespace Microsoft.Net.Http.Headers
|
|||
};
|
||||
dataset.Add(header5, "name5=value5; expires=Sun, 06 Nov 1994 08:49:37 GMT; domain=domain1");
|
||||
|
||||
var header6 = new SetCookieHeaderValue("name6", "value6")
|
||||
{
|
||||
SameSite = SameSiteMode.Lax,
|
||||
};
|
||||
dataset.Add(header6, "name6=value6; samesite=lax");
|
||||
|
||||
var header7 = new SetCookieHeaderValue("name7", "value7")
|
||||
{
|
||||
SameSite = SameSiteMode.None,
|
||||
};
|
||||
dataset.Add(header7, "name7=value7");
|
||||
|
||||
|
||||
return dataset;
|
||||
}
|
||||
}
|
||||
|
|
@ -106,12 +120,13 @@ namespace Microsoft.Net.Http.Headers
|
|||
{
|
||||
Domain = "domain1",
|
||||
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
||||
SameSite = SameSiteMode.Strict,
|
||||
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 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; samesite=strict; httponly";
|
||||
|
||||
var header2 = new SetCookieHeaderValue("name2", "value2");
|
||||
var string2 = "name2=value2";
|
||||
|
|
@ -129,6 +144,21 @@ namespace Microsoft.Net.Http.Headers
|
|||
};
|
||||
var string4 = "name4=value4; expires=Sun, 06 Nov 1994 08:49:37 GMT; domain=domain1";
|
||||
|
||||
var header5 = new SetCookieHeaderValue("name5", "value5")
|
||||
{
|
||||
SameSite = SameSiteMode.Lax
|
||||
};
|
||||
var string5a = "name5=value5; samesite=lax";
|
||||
var string5b = "name5=value5; samesite=Lax";
|
||||
|
||||
var header6 = new SetCookieHeaderValue("name6", "value6")
|
||||
{
|
||||
SameSite = SameSiteMode.Strict
|
||||
};
|
||||
var string6a = "name6=value6; samesite";
|
||||
var string6b = "name6=value6; samesite=Strict";
|
||||
var string6c = "name6=value6; samesite=invalid";
|
||||
|
||||
dataset.Add(new[] { header1 }.ToList(), new[] { string1 });
|
||||
dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, string1 });
|
||||
dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, null, "", " ", ",", " , ", string1 });
|
||||
|
|
@ -138,6 +168,11 @@ namespace Microsoft.Net.Http.Headers
|
|||
dataset.Add(new[] { header2, header1 }.ToList(), new[] { string2 + ", " + string1 });
|
||||
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, string3, string4 });
|
||||
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, string4) });
|
||||
dataset.Add(new[] { header5 }.ToList(), new[] { string5a });
|
||||
dataset.Add(new[] { header5 }.ToList(), new[] { string5b });
|
||||
dataset.Add(new[] { header6 }.ToList(), new[] { string6a });
|
||||
dataset.Add(new[] { header6 }.ToList(), new[] { string6b });
|
||||
dataset.Add(new[] { header6 }.ToList(), new[] { string6c });
|
||||
|
||||
return dataset;
|
||||
}
|
||||
|
|
@ -152,12 +187,13 @@ namespace Microsoft.Net.Http.Headers
|
|||
{
|
||||
Domain = "domain1",
|
||||
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
||||
SameSite = SameSiteMode.Strict,
|
||||
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 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; samesite=Strict; httponly";
|
||||
|
||||
var header2 = new SetCookieHeaderValue("name2", "value2");
|
||||
var string2 = "name2=value2";
|
||||
|
|
|
|||
Loading…
Reference in New Issue