[dart:io] validate path component of class Cookie

Bug: https://github.com/dart-lang/sdk/issues/42823
Change-Id: Icb9e0fa84404acfe5c6f3c98014dae4cd16edf2c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/155843
Commit-Queue: Zichang Guo <zichangguo@google.com>
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3325b41..c9eefa2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@
 
 *   Adds `Abort` method to class `HttpClientRequest`, which allows users
     to cancel outgoing HTTP requests and stop following IO operations.
+*   A validtion check is added to `path` of class `Cookie`. Having characters
+    ranging from 0x00 to 0x1f and 0x3b (";") will lead to a `FormatException`.
 
 #### `dart:typed_data`
 
diff --git a/sdk/lib/_http/http_headers.dart b/sdk/lib/_http/http_headers.dart
index 73ed901..7d87b88 100644
--- a/sdk/lib/_http/http_headers.dart
+++ b/sdk/lib/_http/http_headers.dart
@@ -913,7 +913,7 @@
   DateTime? expires;
   int? maxAge;
   String? domain;
-  String? path;
+  String? _path;
   bool httpOnly = false;
   bool secure = false;
 
@@ -925,6 +925,13 @@
   String get name => _name;
   String get value => _value;
 
+  String? get path => _path;
+
+  set path(String? newPath) {
+    _validatePath(newPath);
+    _path = newPath;
+  }
+
   set name(String newName) {
     _validateName(newName);
     _name = newName;
@@ -1104,4 +1111,19 @@
     }
     return newValue;
   }
+
+  static void _validatePath(String? path) {
+    if (path == null) return;
+    for (int i = 0; i < path.length; i++) {
+      int codeUnit = path.codeUnitAt(i);
+      // According to RFC 6265, semicolon and controls should not occur in the
+      // path.
+      // path-value = <any CHAR except CTLs or ";">
+      // CTLs = %x00-1F / %x7F
+      if (codeUnit < 0x20 || codeUnit >= 0x7f || codeUnit == 0x3b /*;*/) {
+        throw FormatException(
+            "Invalid character in cookie path, code unit: '$codeUnit'");
+      }
+    }
+  }
 }
diff --git a/tests/standalone/io/http_cookie_test.dart b/tests/standalone/io/http_cookie_test.dart
index bfe9302..5aa1cf9 100644
--- a/tests/standalone/io/http_cookie_test.dart
+++ b/tests/standalone/io/http_cookie_test.dart
@@ -83,7 +83,35 @@
       () => Cookie.fromSetCookieValue('key="x""; HttpOnly'));
 }
 
+void testValidatePath() {
+  Cookie cookie = Cookie.fromSetCookieValue(" cname = cval; path= / ");
+  Expect.equals('/', cookie.path);
+  cookie.path = null;
+  Expect.throws<FormatException>(() {
+    cookie.path = "something; ";
+  }, (e) => e.toString().contains('Invalid character'));
+
+  StringBuffer buffer = StringBuffer();
+  buffer.writeCharCode(0x1f);
+  Expect.throws<FormatException>(() {
+    cookie.path = buffer.toString();
+  }, (e) => e.toString().contains('Invalid character'));
+
+  buffer.clear();
+  buffer.writeCharCode(0x7f);
+  Expect.throws<FormatException>(() {
+    cookie.path = buffer.toString();
+  }, (e) => e.toString().contains('Invalid character'));
+
+  buffer.clear();
+  buffer.writeCharCode(0x00);
+  Expect.throws<FormatException>(() {
+    cookie.path = buffer.toString();
+  }, (e) => e.toString().contains('Invalid character'));
+}
+
 void main() {
   testCookies();
   testValidateCookieWithDoubleQuotes();
+  testValidatePath();
 }
diff --git a/tests/standalone_2/io/http_cookie_test.dart b/tests/standalone_2/io/http_cookie_test.dart
index bfe9302..5aa1cf9 100644
--- a/tests/standalone_2/io/http_cookie_test.dart
+++ b/tests/standalone_2/io/http_cookie_test.dart
@@ -83,7 +83,35 @@
       () => Cookie.fromSetCookieValue('key="x""; HttpOnly'));
 }
 
+void testValidatePath() {
+  Cookie cookie = Cookie.fromSetCookieValue(" cname = cval; path= / ");
+  Expect.equals('/', cookie.path);
+  cookie.path = null;
+  Expect.throws<FormatException>(() {
+    cookie.path = "something; ";
+  }, (e) => e.toString().contains('Invalid character'));
+
+  StringBuffer buffer = StringBuffer();
+  buffer.writeCharCode(0x1f);
+  Expect.throws<FormatException>(() {
+    cookie.path = buffer.toString();
+  }, (e) => e.toString().contains('Invalid character'));
+
+  buffer.clear();
+  buffer.writeCharCode(0x7f);
+  Expect.throws<FormatException>(() {
+    cookie.path = buffer.toString();
+  }, (e) => e.toString().contains('Invalid character'));
+
+  buffer.clear();
+  buffer.writeCharCode(0x00);
+  Expect.throws<FormatException>(() {
+    cookie.path = buffer.toString();
+  }, (e) => e.toString().contains('Invalid character'));
+}
+
 void main() {
   testCookies();
   testValidateCookieWithDoubleQuotes();
+  testValidatePath();
 }