[http] support SameSite in Cookies.

Closes https://github.com/dart-lang/sdk/pull/51457

GitOrigin-RevId: 4e0bece11ae2c1b7b7eac5a43293ae43682e22d2
Change-Id: I0fe39aede037b713b5a3fdbf7950a4a44a02ea1d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283984
Reviewed-by: Lasse Nielsen <lrn@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Brian Quinlan <bquinlan@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
This commit is contained in:
Sun Jiao 2023-05-02 23:51:22 +00:00 committed by Commit Queue
parent d57046b535
commit f9ccf90c97
6 changed files with 130 additions and 0 deletions

View file

@ -4,6 +4,14 @@
### Libraries
#### `dart:io`
- **Breaking change** [#51486][]:
- Added `sameSite` to the `Cookie` class.
- Added class `SameSite`.
[#51486]: https://github.com/dart-lang/sdk/issues/51486
### Tools
## 3.0.0

View file

@ -735,6 +735,43 @@ abstract interface class ContentType implements HeaderValue {
String? get charset;
}
/// Cookie cross-site availability configuration.
///
/// The value of [Cookie.sameSite], which defines whether an
/// HTTP cookie is available from other sites or not.
///
/// Has three possible values: [lax], [strict] and [none].
final class SameSite {
/// Default value, cookie with this value will generally not be sent on
/// cross-site requests, unless the user is navigated to the original site.
static const lax = SameSite._("Lax");
/// Cookie with this value will never be sent on cross-site requests.
static const strict = SameSite._("Strict");
/// Cookie with this value will be sent in all requests.
///
/// [Cookie.secure] must also be set to true, otherwise the `none` value
/// will have no effect.
static const none = SameSite._("None");
static const List<SameSite> values = [lax, strict, none];
final String name;
const SameSite._(this.name);
static SameSite _byName(String name) {
for (var value in values) {
if (name.toLowerCase() == value.name.toLowerCase()) return value;
}
throw HttpException('SameSite value should be one of Lax, Strict or None.');
}
@override
String toString() => "SameSite=$name";
}
/// Representation of a cookie. For cookies received by the server as Cookie
/// header values only [name] and [value] properties will be set. When building a
/// cookie for the 'set-cookie' header in the server and when receiving cookies
@ -781,6 +818,13 @@ abstract interface class Cookie {
/// available to client side scripts.
bool httpOnly = false;
/// Whether the cookie is available from other sites.
///
/// This value is `null` if the SameSite attribute is not present.
///
/// See [SameSite] for more information.
SameSite? sameSite;
/// Creates a new cookie setting the name and value.
///
/// [name] and [value] must be composed of valid characters according to RFC

View file

@ -949,6 +949,7 @@ class _Cookie implements Cookie {
String? _path;
bool httpOnly = false;
bool secure = false;
SameSite? sameSite;
_Cookie(String name, String value)
: _name = _validateName(name),
@ -1044,6 +1045,8 @@ class _Cookie implements Cookie {
httpOnly = true;
} else if (name == "secure") {
secure = true;
} else if (name == "samesite") {
sameSite = SameSite._byName(value);
}
if (!done()) index++; // Skip the ; character
}
@ -1089,6 +1092,8 @@ class _Cookie implements Cookie {
}
if (secure) sb.write("; Secure");
if (httpOnly) sb.write("; HttpOnly");
if (sameSite != null) sb.write("; $sameSite");
return sb.toString();
}

View file

@ -217,6 +217,7 @@ export 'dart:_http'
HeaderValue,
HttpSession,
ContentType,
SameSite,
Cookie,
HttpRequest,
HttpResponse,

View file

@ -110,8 +110,44 @@ void testValidatePath() {
}, (e) => e.toString().contains('Invalid character'));
}
void testCookieSameSite() {
Cookie cookie1 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; Secure; "
"HttpOnly; Path=/; SameSite=None");
Expect.equals(cookie1.sameSite, SameSite.none);
Cookie cookie2 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite=Lax");
Expect.equals(cookie2.sameSite, SameSite.lax);
Cookie cookie3 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite=LAX");
Expect.equals(cookie3.sameSite, SameSite.lax);
Cookie cookie4 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite= Lax");
Expect.equals(cookie4.sameSite, SameSite.lax);
Cookie cookie5 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; sAmEsItE= nOnE");
Expect.equals(cookie5.sameSite, SameSite.none);
Expect.throws<HttpException>(() => Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite=Relax"),
(e) => e.message == "SameSite value should be one of Lax, Strict or None.");
Expect.throws<HttpException>(() => Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite="),
(e) => e.message == "SameSite value should be one of Lax, Strict or None.");
Expect.throws<HttpException>(() => Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite=无"),
(e) => e.message == "SameSite value should be one of Lax, Strict or None.");
}
void main() {
testCookies();
testValidateCookieWithDoubleQuotes();
testValidatePath();
testCookieSameSite();
}

View file

@ -112,8 +112,44 @@ void testValidatePath() {
}, (e) => e.toString().contains('Invalid character'));
}
void testCookieSameSite() {
Cookie cookie1 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; Secure; "
"HttpOnly; Path=/; SameSite=None");
Expect.equals(cookie1.sameSite, SameSite.none);
Cookie cookie2 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite=Lax");
Expect.equals(cookie2.sameSite, SameSite.lax);
Cookie cookie3 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite=LAX");
Expect.equals(cookie3.sameSite, SameSite.lax);
Cookie cookie4 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite= Lax");
Expect.equals(cookie4.sameSite, SameSite.lax);
Cookie cookie5 = Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; sAmEsItE= nOnE");
Expect.equals(cookie5.sameSite, SameSite.none);
Expect.throws<HttpException>(() => Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite=Relax"),
(e) => e.message == "SameSite value should be one of Lax, Strict or None.");
Expect.throws<HttpException>(() => Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite="),
(e) => e.message == "SameSite value should be one of Lax, Strict or None.");
Expect.throws<HttpException>(() => Cookie.fromSetCookieValue(
"name=cookie_name; Expires=Sat, 01 Apr 2023 00:00:00 GMT; HttpOnly; "
"Path=/; SameSite=无"),
(e) => e.message == "SameSite value should be one of Lax, Strict or None.");
}
void main() {
testCookies();
testValidateCookieWithDoubleQuotes();
testValidatePath();
testCookieSameSite();
}