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>
|
/// <returns>true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false.</returns>
|
||||||
public bool Secure { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Gets or sets a value that indicates whether a cookie is accessible by client-side script.
|
/// Gets or sets a value that indicates whether a cookie is accessible by client-side script.
|
||||||
/// </summary>
|
/// </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,
|
Path = options.Path,
|
||||||
Expires = options.Expires,
|
Expires = options.Expires,
|
||||||
Secure = options.Secure,
|
Secure = options.Secure,
|
||||||
HttpOnly = options.HttpOnly,
|
SameSite = (Net.Http.Headers.SameSiteMode)options.SameSite,
|
||||||
|
HttpOnly = options.HttpOnly
|
||||||
};
|
};
|
||||||
|
|
||||||
var cookieValue = setCookieHeaderValue.ToString();
|
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 DomainToken = "domain";
|
||||||
private const string PathToken = "path";
|
private const string PathToken = "path";
|
||||||
private const string SecureToken = "secure";
|
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 HttpOnlyToken = "httponly";
|
||||||
private const string SeparatorToken = "; ";
|
private const string SeparatorToken = "; ";
|
||||||
private const string EqualsToken = "=";
|
private const string EqualsToken = "=";
|
||||||
|
|
@ -87,15 +91,18 @@ namespace Microsoft.Net.Http.Headers
|
||||||
|
|
||||||
public bool Secure { get; set; }
|
public bool Secure { get; set; }
|
||||||
|
|
||||||
|
public SameSiteMode SameSite { get; set; }
|
||||||
|
|
||||||
public bool HttpOnly { 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()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var length = _name.Length + EqualsToken.Length + _value.Length;
|
var length = _name.Length + EqualsToken.Length + _value.Length;
|
||||||
|
|
||||||
string expires = null;
|
string expires = null;
|
||||||
string maxAge = null;
|
string maxAge = null;
|
||||||
|
string sameSite = null;
|
||||||
|
|
||||||
if (Expires.HasValue)
|
if (Expires.HasValue)
|
||||||
{
|
{
|
||||||
|
|
@ -124,6 +131,12 @@ namespace Microsoft.Net.Http.Headers
|
||||||
length += SeparatorToken.Length + SecureToken.Length;
|
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)
|
if (HttpOnly)
|
||||||
{
|
{
|
||||||
length += SeparatorToken.Length + HttpOnlyToken.Length;
|
length += SeparatorToken.Length + HttpOnlyToken.Length;
|
||||||
|
|
@ -160,6 +173,11 @@ namespace Microsoft.Net.Http.Headers
|
||||||
AppendSegment(ref sb, SecureToken, null);
|
AppendSegment(ref sb, SecureToken, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SameSite != SameSiteMode.None)
|
||||||
|
{
|
||||||
|
AppendSegment(ref sb, SameSiteToken, sameSite);
|
||||||
|
}
|
||||||
|
|
||||||
if (HttpOnly)
|
if (HttpOnly)
|
||||||
{
|
{
|
||||||
AppendSegment(ref sb, HttpOnlyToken, null);
|
AppendSegment(ref sb, HttpOnlyToken, null);
|
||||||
|
|
@ -218,6 +236,11 @@ namespace Microsoft.Net.Http.Headers
|
||||||
AppendSegment(builder, SecureToken, null);
|
AppendSegment(builder, SecureToken, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SameSite != SameSiteMode.None)
|
||||||
|
{
|
||||||
|
AppendSegment(builder, SameSiteToken, SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken);
|
||||||
|
}
|
||||||
|
|
||||||
if (HttpOnly)
|
if (HttpOnly)
|
||||||
{
|
{
|
||||||
AppendSegment(builder, HttpOnlyToken, null);
|
AppendSegment(builder, HttpOnlyToken, null);
|
||||||
|
|
@ -267,7 +290,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
|
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)
|
private static int GetSetCookieLength(string input, int startIndex, out SetCookieHeaderValue parsedValue)
|
||||||
{
|
{
|
||||||
Contract.Requires(startIndex >= 0);
|
Contract.Requires(startIndex >= 0);
|
||||||
|
|
@ -322,7 +345,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
|
|
||||||
offset += HttpRuleParser.GetWhitespaceLength(input, offset);
|
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);
|
itemLength = HttpRuleParser.GetTokenLength(input, offset);
|
||||||
if (itemLength == 0)
|
if (itemLength == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -402,6 +425,28 @@ namespace Microsoft.Net.Http.Headers
|
||||||
{
|
{
|
||||||
result.Secure = true;
|
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"
|
// httponly-av = "HttpOnly"
|
||||||
else if (string.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase))
|
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(Domain, other.Domain, StringComparison.OrdinalIgnoreCase)
|
||||||
&& string.Equals(Path, other.Path, StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(Path, other.Path, StringComparison.OrdinalIgnoreCase)
|
||||||
&& Secure == other.Secure
|
&& Secure == other.Secure
|
||||||
|
&& SameSite == other.SameSite
|
||||||
&& HttpOnly == other.HttpOnly;
|
&& HttpOnly == other.HttpOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -471,6 +517,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
^ (Domain != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Domain) : 0)
|
^ (Domain != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Domain) : 0)
|
||||||
^ (Path != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Path) : 0)
|
^ (Path != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Path) : 0)
|
||||||
^ Secure.GetHashCode()
|
^ Secure.GetHashCode()
|
||||||
|
^ SameSite.GetHashCode()
|
||||||
^ HttpOnly.GetHashCode();
|
^ HttpOnly.GetHashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,13 @@ namespace Microsoft.Net.Http.Headers
|
||||||
{
|
{
|
||||||
Domain = "domain1",
|
Domain = "domain1",
|
||||||
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
||||||
|
SameSite = SameSiteMode.Strict,
|
||||||
HttpOnly = true,
|
HttpOnly = true,
|
||||||
MaxAge = TimeSpan.FromDays(1),
|
MaxAge = TimeSpan.FromDays(1),
|
||||||
Path = "path1",
|
Path = "path1",
|
||||||
Secure = true
|
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", "");
|
var header2 = new SetCookieHeaderValue("name2", "");
|
||||||
dataset.Add(header2, "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");
|
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;
|
return dataset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,12 +120,13 @@ namespace Microsoft.Net.Http.Headers
|
||||||
{
|
{
|
||||||
Domain = "domain1",
|
Domain = "domain1",
|
||||||
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
||||||
|
SameSite = SameSiteMode.Strict,
|
||||||
HttpOnly = true,
|
HttpOnly = true,
|
||||||
MaxAge = TimeSpan.FromDays(1),
|
MaxAge = TimeSpan.FromDays(1),
|
||||||
Path = "path1",
|
Path = "path1",
|
||||||
Secure = true
|
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 header2 = new SetCookieHeaderValue("name2", "value2");
|
||||||
var string2 = "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 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 }.ToList(), new[] { string1 });
|
||||||
dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, string1 });
|
dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, string1 });
|
||||||
dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, null, "", " ", ",", " , ", 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[] { 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[] { string1, string2, string3, string4 });
|
||||||
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", 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;
|
return dataset;
|
||||||
}
|
}
|
||||||
|
|
@ -152,12 +187,13 @@ namespace Microsoft.Net.Http.Headers
|
||||||
{
|
{
|
||||||
Domain = "domain1",
|
Domain = "domain1",
|
||||||
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
|
||||||
|
SameSite = SameSiteMode.Strict,
|
||||||
HttpOnly = true,
|
HttpOnly = true,
|
||||||
MaxAge = TimeSpan.FromDays(1),
|
MaxAge = TimeSpan.FromDays(1),
|
||||||
Path = "path1",
|
Path = "path1",
|
||||||
Secure = true
|
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 header2 = new SetCookieHeaderValue("name2", "value2");
|
||||||
var string2 = "name2=value2";
|
var string2 = "name2=value2";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue