Skip to content

Commit 44415c4

Browse files
sortiecommit-bot@chromium.org
authored andcommitted
[dart:_http] Require Cookie name and value to be valid.
This is a breaking change. #37192 This change makes the name and value positional optional parameters in the Cookie class constructor mandatory by changing the signature from Cookie([String name, String value]) to Cookie(String name, String value) The parameters were already effectively mandatory as a bug introduced in Dart 1.3.0 (2014) meant the name and value parameters could not be null, and any such uses already threw a noSuchMethod exception because null did not have a length getter. As such, this is not a breaking change but adopts the current behavior as a null name and value was already of questionable use. Breaking change: This change adds validation to the String name and String value setters, which had not been validating the fields at all, unlike the constructor. This also forbids the name and value from being set to null. That meant potentially invalid cookies could be sent to servers if the cookie was modified after construction. This change adds the validation to follow the rule of least surprise. The documentation has been updated accordingly and improved a bit. Closes #37192 Closes #29463 Change-Id: Iffed3dc265ca9c68142c4372522913f9d1ff4d51 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103840 Commit-Queue: Jonas Termansen <sortie@google.com> Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
1 parent be260fe commit 44415c4

3 files changed

Lines changed: 99 additions & 38 deletions

File tree

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,32 @@
5050

5151
[29456]: https://github.com/dart-lang/sdk/issues/29456
5252

53+
#### `dart:io`
54+
55+
* **Breaking Change:** The `Cookie` class's constructor's `name` and `value`
56+
optional positional parameters are now mandatory (Issue [37192][]). The
57+
signature changes from:
58+
59+
Cookie([String name, String value])
60+
61+
to
62+
63+
Cookie(String name, String value)
64+
65+
However, it has not been possible to set `name` and `value` to null since Dart
66+
1.3.0 (2014) where a bug made it impossible. Any code not using both
67+
parameters or setting any to null would necessarily get a noSuchMethod
68+
exception at runtime. This change catches such erroneous uses at compile time.
69+
Since code could not previously correctly omit the parameters, this is not
70+
really a breaking change.
71+
72+
* **Breaking Change:** The `Cookie` class's `name` and `value` setters now
73+
validates that the strings are made from the allowed character set and are not
74+
null (Issue [37192][]). The constructor already made these checks and this
75+
fixes the loophole where the setters didn't also validate.
76+
77+
[37192]: https://github.com/dart-lang/sdk/issues/37192
78+
5379
### Dart VM
5480

5581
### Tools

sdk/lib/_http/http.dart

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -912,60 +912,78 @@ abstract class ContentType implements HeaderValue {
912912
}
913913

914914
/**
915-
* Representation of a cookie. For cookies received by the server as
916-
* Cookie header values only [:name:] and [:value:] fields will be
917-
* set. When building a cookie for the 'set-cookie' header in the server
918-
* and when receiving cookies in the client as 'set-cookie' headers all
919-
* fields can be used.
915+
* Representation of a cookie. For cookies received by the server as Cookie
916+
* header values only [name] and [value] properties will be set. When building a
917+
* cookie for the 'set-cookie' header in the server and when receiving cookies
918+
* in the client as 'set-cookie' headers all fields can be used.
920919
*/
921920
abstract class Cookie {
922921
/**
923-
* Gets and sets the name.
922+
* The name of the cookie.
923+
*
924+
* Must be a `token` as specified in RFC 6265.
925+
*
926+
* The allowed characters in a `token` are the visible ASCII characters,
927+
* U+0021 (`!`) through U+007E (`~`), except the separator characters:
928+
* `(`, `)`, `<`, `>`, `@`, `,`, `;`, `:`, `\`, `"`, `/`, `[`, `]`, `?`, `=`,
929+
* `{`, and `}`.
924930
*/
925931
String name;
926932

927933
/**
928-
* Gets and sets the value.
934+
* The value of the cookie.
935+
*
936+
* Must be a `cookie-value` as specified in RFC 6265.
937+
*
938+
* The allowed characters in a cookie value are the visible ASCII characters,
939+
* U+0021 (`!`) through U+007E (`~`) except the characters:
940+
* `"`, `,`, `;` and `\`.
941+
* Cookie values may be wrapped in a single pair of double quotes
942+
* (U+0022, `"`).
929943
*/
930944
String value;
931945

932946
/**
933-
* Gets and sets the expiry date.
947+
* The time at which the cookie expires.
934948
*/
935949
DateTime expires;
936950

937951
/**
938-
* Gets and sets the max age. A value of [:0:] means delete cookie
939-
* now.
952+
* The number of seconds until the cookie expires. A zero or negative value
953+
* means the cookie has expired.
940954
*/
941955
int maxAge;
942956

943957
/**
944-
* Gets and sets the domain.
958+
* The domain the cookie applies to.
945959
*/
946960
String domain;
947961

948962
/**
949-
* Gets and sets the path.
963+
* The path within the [domain] the cookie applies to.
950964
*/
951965
String path;
952966

953967
/**
954-
* Gets and sets whether this cookie is secure.
968+
* Whether to only send this cookie on secure connections.
955969
*/
956970
bool secure;
957971

958972
/**
959-
* Gets and sets whether this cookie is HTTP only.
973+
* Whether the cookie is only sent in the HTTP request and is not made
974+
* available to client side scripts.
960975
*/
961976
bool httpOnly;
962977

963978
/**
964-
* Creates a new cookie optionally setting the name and value.
979+
* Creates a new cookie setting the name and value.
980+
*
981+
* [name] and [value] must be composed of valid characters according to RFC
982+
* 6265.
965983
*
966984
* By default the value of `httpOnly` will be set to `true`.
967985
*/
968-
factory Cookie([String name, String value]) => new _Cookie(name, value);
986+
factory Cookie(String name, String value) => new _Cookie(name, value);
969987

970988
/**
971989
* Creates a new cookie by parsing a header value from a 'set-cookie'

sdk/lib/_http/http_headers.dart

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -829,19 +829,31 @@ class _ContentType extends _HeaderValue implements ContentType {
829829
}
830830

831831
class _Cookie implements Cookie {
832-
String name;
833-
String value;
832+
String _name;
833+
String _value;
834834
DateTime expires;
835835
int maxAge;
836836
String domain;
837837
String path;
838838
bool httpOnly = false;
839839
bool secure = false;
840840

841-
_Cookie([this.name, this.value]) {
842-
// Default value of httponly is true.
843-
httpOnly = true;
844-
_validate();
841+
_Cookie(String name, String value)
842+
: _name = _validateName(name),
843+
_value = _validateValue(value),
844+
httpOnly = true;
845+
846+
String get name => _name;
847+
String get value => _value;
848+
849+
set name(String newName) {
850+
_validateName(newName);
851+
_name = newName;
852+
}
853+
854+
set value(String newValue) {
855+
_validateValue(newValue);
856+
_value = newValue;
845857
}
846858

847859
_Cookie.fromSetCookieValue(String value) {
@@ -924,21 +936,20 @@ class _Cookie implements Cookie {
924936
}
925937
}
926938

927-
name = parseName();
928-
if (done() || name.length == 0) {
939+
_name = _validateName(parseName());
940+
if (done() || _name.length == 0) {
929941
throw new HttpException("Failed to parse header value [$s]");
930942
}
931943
index++; // Skip the = character.
932-
value = parseValue();
933-
_validate();
944+
_value = _validateValue(parseValue());
934945
if (done()) return;
935946
index++; // Skip the ; character.
936947
parseAttributes();
937948
}
938949

939950
String toString() {
940951
StringBuffer sb = new StringBuffer();
941-
sb..write(name)..write("=")..write(value);
952+
sb..write(_name)..write("=")..write(_value);
942953
if (expires != null) {
943954
sb..write("; Expires=")..write(HttpDate.format(expires));
944955
}
@@ -956,7 +967,7 @@ class _Cookie implements Cookie {
956967
return sb.toString();
957968
}
958969

959-
void _validate() {
970+
static String _validateName(String newName) {
960971
const separators = const [
961972
"(",
962973
")",
@@ -976,41 +987,47 @@ class _Cookie implements Cookie {
976987
"{",
977988
"}"
978989
];
979-
for (int i = 0; i < name.length; i++) {
980-
int codeUnit = name.codeUnits[i];
990+
if (newName == null) throw new ArgumentError.notNull("name");
991+
for (int i = 0; i < newName.length; i++) {
992+
int codeUnit = newName.codeUnits[i];
981993
if (codeUnit <= 32 ||
982994
codeUnit >= 127 ||
983-
separators.indexOf(name[i]) >= 0) {
995+
separators.indexOf(newName[i]) >= 0) {
984996
throw new FormatException(
985997
"Invalid character in cookie name, code unit: '$codeUnit'",
986-
name,
998+
newName,
987999
i);
9881000
}
9891001
}
1002+
return newName;
1003+
}
9901004

1005+
static String _validateValue(String newValue) {
1006+
if (newValue == null) throw new ArgumentError.notNull("value");
9911007
// Per RFC 6265, consider surrounding "" as part of the value, but otherwise
9921008
// double quotes are not allowed.
9931009
int start = 0;
994-
int end = value.length;
995-
if (2 <= value.length &&
996-
value.codeUnits[start] == 0x22 &&
997-
value.codeUnits[end - 1] == 0x22) {
1010+
int end = newValue.length;
1011+
if (2 <= newValue.length &&
1012+
newValue.codeUnits[start] == 0x22 &&
1013+
newValue.codeUnits[end - 1] == 0x22) {
9981014
start++;
9991015
end--;
10001016
}
10011017

10021018
for (int i = start; i < end; i++) {
1003-
int codeUnit = value.codeUnits[i];
1019+
int codeUnit = newValue.codeUnits[i];
10041020
if (!(codeUnit == 0x21 ||
10051021
(codeUnit >= 0x23 && codeUnit <= 0x2B) ||
10061022
(codeUnit >= 0x2D && codeUnit <= 0x3A) ||
10071023
(codeUnit >= 0x3C && codeUnit <= 0x5B) ||
10081024
(codeUnit >= 0x5D && codeUnit <= 0x7E))) {
10091025
throw new FormatException(
10101026
"Invalid character in cookie value, code unit: '$codeUnit'",
1011-
value,
1027+
newValue,
10121028
i);
10131029
}
10141030
}
1031+
return newValue;
10151032
}
10161033
}

0 commit comments

Comments
 (0)