[io] Fix a bug where NUL was allowed in HTTP headers.

Bug:https://github.com/dart-lang/sdk/issues/56636
Change-Id: I88c579cfaaf0884cb3b582084b8739b060d8f439
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402541
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75607d9..2ef801d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -223,6 +223,11 @@
   release. Users should migrate to using `dart:js_interop` and `package:web`.
   See [#59716][].
 
+#### `dart:io`
+
+- `HttpException` will be thrown by `HttpClient` and `HttpServer` if a `NUL`
+  (`0x00`) appears in a received HTTP header value.
+
 #### `dart:svg`
 
 - `dart:svg` is marked deprecated and will be removed in an upcoming release.
diff --git a/sdk/lib/_http/http_parser.dart b/sdk/lib/_http/http_parser.dart
index f877013..0267ac4 100644
--- a/sdk/lib/_http/http_parser.dart
+++ b/sdk/lib/_http/http_parser.dart
@@ -683,7 +683,7 @@
             _state = _State.HEADER_VALUE_FOLD_OR_END;
           } else if (byte != _CharCode.SP && byte != _CharCode.HT) {
             // Start of new header value.
-            _addWithValidation(_headerValue, byte);
+            _addToHeaderValueWithValidation(_headerValue, byte);
             _state = _State.HEADER_VALUE;
           }
           break;
@@ -694,7 +694,7 @@
           } else if (byte == _CharCode.LF) {
             _state = _State.HEADER_VALUE_FOLD_OR_END;
           } else {
-            _addWithValidation(_headerValue, byte);
+            _addToHeaderValueWithValidation(_headerValue, byte);
           }
           break;
 
@@ -710,7 +710,7 @@
             // prior to interpreting the field value or forwarding the
             // message downstream."
             // See https://www.rfc-editor.org/rfc/rfc7230#section-3.2.4
-            _addWithValidation(_headerValue, _CharCode.SP);
+            _addToHeaderValueWithValidation(_headerValue, _CharCode.SP);
             _state = _State.HEADER_VALUE_START; // Strips leading whitespace.
           } else {
             String headerField = String.fromCharCodes(_headerField);
@@ -1117,6 +1117,16 @@
     }
   }
 
+  void _addToHeaderValueWithValidation(List<int> list, int byte) {
+    // From RFC-9110:
+    // Field values containing CR, LF, or NUL characters are invalid and
+    // dangerous.
+    if (byte == 0 || byte == _CharCode.LF || byte == _CharCode.CR) {
+      throw HttpException("Illegal value $byte in HTTP header");
+    }
+    _addWithValidation(list, byte);
+  }
+
   void _addWithValidation(List<int> list, int byte) {
     _headersReceivedSize++;
     if (_headersReceivedSize < _headerTotalSizeLimit) {
diff --git a/tests/standalone/io/http_parser_test.dart b/tests/standalone/io/http_parser_test.dart
index c073aa6..7f79737 100644
--- a/tests/standalone/io/http_parser_test.dart
+++ b/tests/standalone/io/http_parser_test.dart
@@ -34,15 +34,18 @@
   }
 
   void _testParseRequest(
-      String request, String expectedMethod, String expectedUri,
-      {int expectedTransferLength = 0,
-      int expectedBytesReceived = 0,
-      Map<String, String?>? expectedHeaders = null,
-      bool chunked = false,
-      bool upgrade = false,
-      int unparsedLength = 0,
-      bool connectionClose = false,
-      String expectedVersion = "1.1"}) {
+    String request,
+    String expectedMethod,
+    String expectedUri, {
+    int expectedTransferLength = 0,
+    int expectedBytesReceived = 0,
+    Map<String, String?>? expectedHeaders = null,
+    bool chunked = false,
+    bool upgrade = false,
+    int unparsedLength = 0,
+    bool connectionClose = false,
+    String expectedVersion = "1.1",
+  }) {
     late StreamController<Uint8List> controller;
     void reset() {
       _HttpParser httpParser = new _HttpParser.requestParser();
@@ -71,33 +74,41 @@
           Expect.equals(-1, incoming.transferLength);
         }
         if (expectedHeaders != null) {
-          expectedHeaders.forEach((String name, String? value) =>
-              Expect.equals(value, headers?[name]?[0]));
+          expectedHeaders.forEach(
+            (String name, String? value) =>
+                Expect.equals(value, headers?[name]?[0]),
+          );
         }
-        incoming.listen((List<int> data) {
-          Expect.isFalse(upgraded);
-          bytesReceived += data.length;
-        }, onDone: () {
-          port2.close();
-          Expect.equals(expectedMethod, method);
-          Expect.stringEquals(expectedUri, uri.toString());
-          Expect.equals(expectedVersion, headers!.protocolVersion);
-          if (upgrade) {
-            Expect.equals(0, bytesReceived);
-            // port1 is closed by the listener on the detached data.
-          } else {
-            Expect.equals(expectedBytesReceived, bytesReceived);
-          }
-        });
+        incoming.listen(
+          (List<int> data) {
+            Expect.isFalse(upgraded);
+            bytesReceived += data.length;
+          },
+          onDone: () {
+            port2.close();
+            Expect.equals(expectedMethod, method);
+            Expect.stringEquals(expectedUri, uri.toString());
+            Expect.equals(expectedVersion, headers!.protocolVersion);
+            if (upgrade) {
+              Expect.equals(0, bytesReceived);
+              // port1 is closed by the listener on the detached data.
+            } else {
+              Expect.equals(expectedBytesReceived, bytesReceived);
+            }
+          },
+        );
 
         if (upgraded) {
           port1.close();
-          httpParser.detachIncoming().listen((List<int> data) {
-            unparsedBytesReceived += data.length;
-          }, onDone: () {
-            Expect.equals(unparsedLength, unparsedBytesReceived);
-            port2.close();
-          });
+          httpParser.detachIncoming().listen(
+            (List<int> data) {
+              unparsedBytesReceived += data.length;
+            },
+            onDone: () {
+              Expect.equals(unparsedLength, unparsedBytesReceived);
+              port2.close();
+            },
+          );
         }
 
         incoming.dataDone.then((_) {
@@ -118,42 +129,54 @@
 
     // Test parsing the request three times delivering the data in
     // different chunks.
-    List<int> requestData =
-        new Uint8List.fromList(transform(request).codeUnits);
+    List<int> requestData = new Uint8List.fromList(
+      transform(request).codeUnits,
+    );
     testWrite(requestData);
     testWrite(requestData, 10);
     testWrite(requestData, 1);
   }
 
   void _testParseRequestLean(
-      String request, String expectedMethod, String expectedUri,
-      {int expectedTransferLength = 0,
-      int expectedBytesReceived = 0,
-      Map<String, String>? expectedHeaders = null,
-      bool chunked = false,
-      bool upgrade = false,
-      int unparsedLength = 0,
-      bool connectionClose = false,
-      String expectedVersion = "1.1"}) {
-    _testParseRequest(request, expectedMethod, expectedUri,
-        expectedTransferLength: expectedTransferLength,
-        expectedBytesReceived: expectedBytesReceived,
-        expectedHeaders: expectedHeaders,
-        chunked: chunked,
-        upgrade: upgrade,
-        unparsedLength: unparsedLength,
-        connectionClose: connectionClose,
-        expectedVersion: expectedVersion);
+    String request,
+    String expectedMethod,
+    String expectedUri, {
+    int expectedTransferLength = 0,
+    int expectedBytesReceived = 0,
+    Map<String, String>? expectedHeaders = null,
+    bool chunked = false,
+    bool upgrade = false,
+    int unparsedLength = 0,
+    bool connectionClose = false,
+    String expectedVersion = "1.1",
+  }) {
+    _testParseRequest(
+      request,
+      expectedMethod,
+      expectedUri,
+      expectedTransferLength: expectedTransferLength,
+      expectedBytesReceived: expectedBytesReceived,
+      expectedHeaders: expectedHeaders,
+      chunked: chunked,
+      upgrade: upgrade,
+      unparsedLength: unparsedLength,
+      connectionClose: connectionClose,
+      expectedVersion: expectedVersion,
+    );
     // Same test but with only \n instead of \r\n terminating each header line.
-    _testParseRequest(request.replaceAll('\r', ''), expectedMethod, expectedUri,
-        expectedTransferLength: expectedTransferLength,
-        expectedBytesReceived: expectedBytesReceived,
-        expectedHeaders: expectedHeaders,
-        chunked: chunked,
-        upgrade: upgrade,
-        unparsedLength: unparsedLength,
-        connectionClose: connectionClose,
-        expectedVersion: expectedVersion);
+    _testParseRequest(
+      request.replaceAll('\r', ''),
+      expectedMethod,
+      expectedUri,
+      expectedTransferLength: expectedTransferLength,
+      expectedBytesReceived: expectedBytesReceived,
+      expectedHeaders: expectedHeaders,
+      chunked: chunked,
+      upgrade: upgrade,
+      unparsedLength: unparsedLength,
+      connectionClose: connectionClose,
+      expectedVersion: expectedVersion,
+    );
   }
 
   void _testParseInvalidRequest(String request) {
@@ -182,9 +205,11 @@
     void testWrite(List<int> requestData, [int chunkSize = -1]) {
       if (chunkSize == -1) chunkSize = requestData.length;
       reset();
-      for (int pos = 0;
-          pos < requestData.length && !errorCalled;
-          pos += chunkSize) {
+      for (
+        int pos = 0;
+        pos < requestData.length && !errorCalled;
+        pos += chunkSize
+      ) {
         int end = min(requestData.length, pos + chunkSize);
         controller.add(requestData.sublist(pos, end) as Uint8List);
       }
@@ -193,25 +218,29 @@
 
     // Test parsing the request three times delivering the data in
     // different chunks.
-    List<int> requestData =
-        new Uint8List.fromList(transform(request).codeUnits);
+    List<int> requestData = new Uint8List.fromList(
+      transform(request).codeUnits,
+    );
     testWrite(requestData);
     testWrite(requestData, 10);
     testWrite(requestData, 1);
   }
 
   void _testParseResponse(
-      String response, int expectedStatusCode, String expectedReasonPhrase,
-      {int expectedTransferLength = 0,
-      int expectedBytesReceived = 0,
-      Map<String, String>? expectedHeaders = null,
-      bool chunked = false,
-      bool close = false,
-      String? responseToMethod = null,
-      bool connectionClose = false,
-      bool upgrade = false,
-      int unparsedLength = 0,
-      String expectedVersion = "1.1"}) {
+    String response,
+    int expectedStatusCode,
+    String expectedReasonPhrase, {
+    int expectedTransferLength = 0,
+    int expectedBytesReceived = 0,
+    Map<String, String>? expectedHeaders = null,
+    bool chunked = false,
+    bool close = false,
+    String? responseToMethod = null,
+    bool connectionClose = false,
+    bool upgrade = false,
+    int unparsedLength = 0,
+    String expectedVersion = "1.1",
+  }) {
     late StreamController<Uint8List> controller;
     bool upgraded;
 
@@ -265,14 +294,17 @@
         }
         Expect.equals(upgrade, httpParser.upgrade);
         headersCompleteCalled = true;
-        incoming.listen((List<int> data) {
-          Expect.isTrue(headersCompleteCalled);
-          bytesReceived += data.length;
-        }, onDone: () {
-          dataEndCalled = true;
-          dataEndClose = close;
-          whenDone();
-        });
+        incoming.listen(
+          (List<int> data) {
+            Expect.isTrue(headersCompleteCalled);
+            bytesReceived += data.length;
+          },
+          onDone: () {
+            dataEndCalled = true;
+            dataEndClose = close;
+            whenDone();
+          },
+        );
       }, onDone: whenDone);
     }
 
@@ -288,8 +320,9 @@
 
     // Test parsing the request three times delivering the data in
     // different chunks.
-    List<int> responseData =
-        new Uint8List.fromList(transform(response).codeUnits);
+    List<int> responseData = new Uint8List.fromList(
+      transform(response).codeUnits,
+    );
     testWrite(responseData);
     testWrite(responseData, 10);
     testWrite(responseData, 1);
@@ -306,10 +339,13 @@
       var port = new ReceivePort();
       httpParser.listenToStream(controller.stream);
       var subscription = httpParser.listen((incoming) {
-        incoming.listen((data) {}, onError: (e) {
-          Expect.isFalse(errorCalled);
-          errorCalled = true;
-        });
+        incoming.listen(
+          (data) {},
+          onError: (e) {
+            Expect.isFalse(errorCalled);
+            errorCalled = true;
+          },
+        );
       });
       subscription.onError((e) {
         Expect.isFalse(errorCalled);
@@ -321,9 +357,11 @@
       });
 
       errorCalled = false;
-      for (int pos = 0;
-          pos < requestData.length && !errorCalled;
-          pos += chunkSize) {
+      for (
+        int pos = 0;
+        pos < requestData.length && !errorCalled;
+        pos += chunkSize
+      ) {
         int end = min(requestData.length, pos + chunkSize);
         controller.add(requestData.sublist(pos, end) as Uint8List);
       }
@@ -332,8 +370,9 @@
 
     // Test parsing the request three times delivering the data in
     // different chunks.
-    List<int> responseData =
-        new Uint8List.fromList(transform(response).codeUnits);
+    List<int> responseData = new Uint8List.fromList(
+      transform(response).codeUnits,
+    );
     testWrite(responseData);
     testWrite(responseData, 10);
     testWrite(responseData, 1);
@@ -350,7 +389,7 @@
       // WebDAV methods from RFC 5323.
       "SEARCH",
       // Methods with HTTP prefix.
-      "H", "HT", "HTT", "HTTP", "HX", "HTX", "HTTX", "HTTPX"
+      "H", "HT", "HTT", "HTTP", "HX", "HTX", "HTTX", "HTTPX",
     ];
     methods = ['GET'];
     methods.forEach((method) {
@@ -360,8 +399,13 @@
       _testParseRequestLean(request, method, "/index.html");
     });
     request = "GET / HTTP/1.0\r\n\r\n";
-    _testParseRequestLean(request, "GET", "/",
-        expectedVersion: "1.0", connectionClose: true);
+    _testParseRequestLean(
+      request,
+      "GET",
+      "/",
+      expectedVersion: "1.0",
+      connectionClose: true,
+    );
 
     request = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n";
     _testParseRequestLean(request, "GET", "/", expectedVersion: "1.0");
@@ -480,8 +524,13 @@
 Content-Length: 10\r
 \r
 0123456789""";
-    _testParseRequestLean(request, "POST", "/test",
-        expectedTransferLength: 10, expectedBytesReceived: 10);
+    _testParseRequestLean(
+      request,
+      "POST",
+      "/test",
+      expectedTransferLength: 10,
+      expectedBytesReceived: 10,
+    );
 
     // Test connection close header.
     request = "GET /test HTTP/1.1\r\nConnection: close\r\n\r\n";
@@ -497,8 +546,14 @@
 5\r
 56789\r
 0\r\n\r\n""";
-    _testParseRequest(request, "POST", "/test",
-        expectedTransferLength: -1, expectedBytesReceived: 10, chunked: true);
+    _testParseRequest(
+      request,
+      "POST",
+      "/test",
+      expectedTransferLength: -1,
+      expectedBytesReceived: 10,
+      chunked: true,
+    );
 
     // Test LWS around chunked encoding header value.
     request = """
@@ -510,8 +565,14 @@
 5\r
 56789\r
 0\r\n\r\n""";
-    _testParseRequest(request, "POST", "/test",
-        expectedTransferLength: -1, expectedBytesReceived: 10, chunked: true);
+    _testParseRequest(
+      request,
+      "POST",
+      "/test",
+      expectedTransferLength: -1,
+      expectedBytesReceived: 10,
+      chunked: true,
+    );
 
     // Test upper and lower case hex digits in chunked encoding.
     request = """
@@ -523,8 +584,14 @@
 1e\r
 012345678901234567890123456789\r
 0\r\n\r\n""";
-    _testParseRequest(request, "POST", "/test",
-        expectedTransferLength: -1, expectedBytesReceived: 60, chunked: true);
+    _testParseRequest(
+      request,
+      "POST",
+      "/test",
+      expectedTransferLength: -1,
+      expectedBytesReceived: 60,
+      chunked: true,
+    );
 
     // Test chunk extensions in chunked encoding.
     request = """
@@ -536,8 +603,14 @@
 1E;yyy=zzz\r
 012345678901234567890123456789\r
 0\r\n\r\n""";
-    _testParseRequest(request, "POST", "/test",
-        expectedTransferLength: -1, expectedBytesReceived: 60, chunked: true);
+    _testParseRequest(
+      request,
+      "POST",
+      "/test",
+      expectedTransferLength: -1,
+      expectedBytesReceived: 60,
+      chunked: true,
+    );
 
     // Content-Length and "Transfer-Encoding: chunked" are specified.
     request = """
@@ -550,14 +623,15 @@
 5\r
 56789\r
 0\r\n\r\n""";
-    _testParseRequest(request, "POST", "/test",
-        expectedTransferLength: -1,
-        expectedBytesReceived: 10,
-        chunked: true,
-        expectedHeaders: {
-          'content-length': null,
-          'transfer-encoding': 'chunked'
-        });
+    _testParseRequest(
+      request,
+      "POST",
+      "/test",
+      expectedTransferLength: -1,
+      expectedBytesReceived: 10,
+      chunked: true,
+      expectedHeaders: {'content-length': null, 'transfer-encoding': 'chunked'},
+    );
 
     request = """
 POST /test HTTP/1.1\r
@@ -569,14 +643,15 @@
 5\r
 56789\r
 0\r\n\r\n""";
-    _testParseRequest(request, "POST", "/test",
-        expectedTransferLength: -1,
-        expectedBytesReceived: 10,
-        chunked: true,
-        expectedHeaders: {
-          'content-length': null,
-          'transfer-encoding': 'chunked'
-        });
+    _testParseRequest(
+      request,
+      "POST",
+      "/test",
+      expectedTransferLength: -1,
+      expectedBytesReceived: 10,
+      chunked: true,
+      expectedHeaders: {'content-length': null, 'transfer-encoding': 'chunked'},
+    );
 
     // Test HTTP upgrade.
     request = """
@@ -586,8 +661,14 @@
 \r\n\x01\x01\x01\x01\x01\x02\x02\x02\x02\xFF""";
     headers = new Map();
     headers["upgrade"] = "irc/1.2";
-    _testParseRequest(request, "GET", "/irc",
-        expectedHeaders: headers, upgrade: true, unparsedLength: 10);
+    _testParseRequest(
+      request,
+      "GET",
+      "/irc",
+      expectedHeaders: headers,
+      upgrade: true,
+      unparsedLength: 10,
+    );
 
     // Test HTTP upgrade with protocol data.
     request = """
@@ -597,8 +678,13 @@
 \r\n""";
     headers = new Map();
     headers["upgrade"] = "irc/1.2";
-    _testParseRequest(request, "GET", "/irc",
-        expectedHeaders: headers, upgrade: true);
+    _testParseRequest(
+      request,
+      "GET",
+      "/irc",
+      expectedHeaders: headers,
+      upgrade: true,
+    );
 
     // Test websocket upgrade.
     request = """
@@ -616,8 +702,13 @@
     headers["sec-websocket-key"] = "dGhlIHNhbXBsZSBub25jZQ==";
     headers["origin"] = "http://example.com";
     headers["sec-websocket-version"] = "13";
-    _testParseRequest(request, "GET", "/chat",
-        expectedHeaders: headers, upgrade: true);
+    _testParseRequest(
+      request,
+      "GET",
+      "/chat",
+      expectedHeaders: headers,
+      upgrade: true,
+    );
 
     // Test websocket upgrade with protocol data. NOTE: When using the
     // WebSocket protocol this should never happen as the client
@@ -639,8 +730,14 @@
     headers["sec-websocket-key"] = "dGhlIHNhbXBsZSBub25jZQ==";
     headers["origin"] = "http://example.com";
     headers["sec-websocket-version"] = "13";
-    _testParseRequest(request, "GET", "/chat",
-        expectedHeaders: headers, upgrade: true, unparsedLength: 7);
+    _testParseRequest(
+      request,
+      "GET",
+      "/chat",
+      expectedHeaders: headers,
+      upgrade: true,
+      unparsedLength: 7,
+    );
   }
 
   void testParseResponse() {
@@ -653,32 +750,59 @@
     _testParseResponse(response, 100, "Continue");
 
     response = "HTTP/1.1 100 Continue\r\nContent-Length: 10\r\n\r\n";
-    _testParseResponse(response, 100, "Continue",
-        expectedTransferLength: 10, expectedBytesReceived: 0);
+    _testParseResponse(
+      response,
+      100,
+      "Continue",
+      expectedTransferLength: 10,
+      expectedBytesReceived: 0,
+    );
 
     response = "HTTP/1.1 100 Continue\r\nContent-Length: \t  10 \t \r\n\r\n";
-    _testParseResponse(response, 100, "Continue",
-        expectedTransferLength: 10, expectedBytesReceived: 0);
+    _testParseResponse(
+      response,
+      100,
+      "Continue",
+      expectedTransferLength: 10,
+      expectedBytesReceived: 0,
+    );
 
-    response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"
+    response =
+        "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"
         "Connection: Close\r\n\r\n";
     _testParseResponse(response, 200, "OK", connectionClose: true);
 
     response = "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n";
-    _testParseResponse(response, 200, "OK",
-        expectedVersion: "1.0", connectionClose: true);
+    _testParseResponse(
+      response,
+      200,
+      "OK",
+      expectedVersion: "1.0",
+      connectionClose: true,
+    );
 
-    response = "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n"
+    response =
+        "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n"
         "Connection: Keep-Alive\r\n\r\n";
     _testParseResponse(response, 200, "OK", expectedVersion: "1.0");
 
     response = "HTTP/1.1 204 No Content\r\nContent-Length: 11\r\n\r\n";
-    _testParseResponse(response, 204, "No Content",
-        expectedTransferLength: 11, expectedBytesReceived: 0);
+    _testParseResponse(
+      response,
+      204,
+      "No Content",
+      expectedTransferLength: 11,
+      expectedBytesReceived: 0,
+    );
 
     response = "HTTP/1.1 304 Not Modified\r\nContent-Length: 12\r\n\r\n";
-    _testParseResponse(response, 304, "Not Modified",
-        expectedTransferLength: 12, expectedBytesReceived: 0);
+    _testParseResponse(
+      response,
+      304,
+      "Not Modified",
+      expectedTransferLength: 12,
+      expectedBytesReceived: 0,
+    );
 
     response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
     _testParseResponse(response, 200, "OK");
@@ -698,11 +822,15 @@
     headers = new Map();
     headers["content-length"] = "20";
     headers["content-type"] = "text/html";
-    _testParseResponse(response, 200, "OK",
-        responseToMethod: "HEAD",
-        expectedTransferLength: 20,
-        expectedBytesReceived: 0,
-        expectedHeaders: headers);
+    _testParseResponse(
+      response,
+      200,
+      "OK",
+      responseToMethod: "HEAD",
+      expectedTransferLength: 20,
+      expectedBytesReceived: 0,
+      expectedHeaders: headers,
+    );
 
     // _testParseRequestLean encodes the request as ISO-8859-1. Test that the
     // HTTP parser decodes header values as ISO-8859-1.
@@ -722,8 +850,13 @@
 Content-Length: 20\r
 \r
 01234567890123456789""";
-    _testParseResponse(response, 200, "OK",
-        expectedTransferLength: 20, expectedBytesReceived: 20);
+    _testParseResponse(
+      response,
+      200,
+      "OK",
+      expectedTransferLength: 20,
+      expectedBytesReceived: 20,
+    );
 
     // Test upper and lower case hex digits in chunked encoding.
     response = """
@@ -735,8 +868,14 @@
 1f\r
 0123456789012345678901234567890\r
 0\r\n\r\n""";
-    _testParseResponse(response, 200, "OK",
-        expectedTransferLength: -1, expectedBytesReceived: 57, chunked: true);
+    _testParseResponse(
+      response,
+      200,
+      "OK",
+      expectedTransferLength: -1,
+      expectedBytesReceived: 57,
+      chunked: true,
+    );
 
     // Test connection close header.
     response = """
@@ -754,11 +893,15 @@
 01234567890123456789012345
 0123456789012345678901234567890
 """;
-    _testParseResponse(response, 200, "OK",
-        expectedTransferLength: -1,
-        expectedBytesReceived: 59,
-        close: true,
-        connectionClose: true);
+    _testParseResponse(
+      response,
+      200,
+      "OK",
+      expectedTransferLength: -1,
+      expectedBytesReceived: 59,
+      close: true,
+      connectionClose: true,
+    );
 
     // Test HTTP upgrade.
     response = """
@@ -768,8 +911,13 @@
 \r\n""";
     headers = new Map();
     headers["upgrade"] = "irc/1.2";
-    _testParseResponse(response, 101, "Switching Protocols",
-        expectedHeaders: headers, upgrade: true);
+    _testParseResponse(
+      response,
+      101,
+      "Switching Protocols",
+      expectedHeaders: headers,
+      upgrade: true,
+    );
 
     // Test HTTP upgrade with protocol data.
     response = """
@@ -779,8 +927,14 @@
 \r\n\x00\x10\x20\x30\x40\x50\x60\x70\x80\x90\xA0\xB0\xC0\xD0\xE0\xF0""";
     headers = new Map();
     headers["upgrade"] = "irc/1.2";
-    _testParseResponse(response, 101, "Switching Protocols",
-        expectedHeaders: headers, upgrade: true, unparsedLength: 16);
+    _testParseResponse(
+      response,
+      101,
+      "Switching Protocols",
+      expectedHeaders: headers,
+      upgrade: true,
+      unparsedLength: 16,
+    );
 
     // Test websocket upgrade.
     response = """
@@ -792,8 +946,13 @@
     headers = new Map();
     headers["upgrade"] = "websocket";
     headers["sec-websocket-accept"] = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
-    _testParseResponse(response, 101, "Switching Protocols",
-        expectedHeaders: headers, upgrade: true);
+    _testParseResponse(
+      response,
+      101,
+      "Switching Protocols",
+      expectedHeaders: headers,
+      upgrade: true,
+    );
 
     // Test websocket upgrade with protocol data.
     response = """
@@ -805,8 +964,14 @@
     headers = new Map();
     headers["upgrade"] = "websocket";
     headers["sec-websocket-accept"] = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
-    _testParseResponse(response, 101, "Switching Protocols",
-        expectedHeaders: headers, upgrade: true, unparsedLength: 4);
+    _testParseResponse(
+      response,
+      101,
+      "Switching Protocols",
+      expectedHeaders: headers,
+      upgrade: true,
+      unparsedLength: 4,
+    );
   }
 
   void testParseInvalidRequest() {
@@ -881,6 +1046,12 @@
     response = "HTTP/1.1 200 OK\r\nbadheader\r\n\r\n";
     _testParseInvalidResponse(response);
 
+    response = "HTTP/1.1 200 OK\r\nbadvalue: \x00\r\n\r\n";
+    _testParseInvalidResponse(response);
+
+    response = "HTTP/1.1 200 OK\r\nbadvalue: bad\x00value\r\n\r\n";
+    _testParseInvalidResponse(response);
+
     response = """
 HTTP/1.1 200 OK\r
 Transfer-Encoding: chunked\r