[io/http] Don't add zero-valued content-length header on GET, HEAD, DELETE, CONNECT requests.

Per https://tools.ietf.org/html/rfc7230#section-3.3.2:
"... A user agent SHOULD NOT send a
Content-Length header field when the request message does not contain
a payload body and the method semantics do not anticipate such a
body."

Fixes https://github.com/dart-lang/sdk/issues/45139

Change-Id: I96b735c06038eb3d12a303ee5329228a9b594726
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194881
Commit-Queue: Alexander Aprelev <aam@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
diff --git a/sdk/lib/_http/http_headers.dart b/sdk/lib/_http/http_headers.dart
index 7d87b88..77b9c22 100644
--- a/sdk/lib/_http/http_headers.dart
+++ b/sdk/lib/_http/http_headers.dart
@@ -503,8 +503,19 @@
     _mutable = false;
   }
 
-  void _build(BytesBuilder builder) {
+  void _build(BytesBuilder builder, {bool skipZeroContentLength = false}) {
+    // per https://tools.ietf.org/html/rfc7230#section-3.3.2
+    // A user agent SHOULD NOT send a
+    // Content-Length header field when the request message does not
+    // contain a payload body and the method semantics do not anticipate
+    // such a body.
+    String? ignoreHeader = _contentLength == 0 && skipZeroContentLength
+        ? HttpHeaders.contentLengthHeader
+        : null;
     _headers.forEach((String name, List<String> values) {
+      if (ignoreHeader == name) {
+        return;
+      }
       String originalName = _originalHeaderName(name);
       bool fold = _foldHeader(name);
       var nameData = originalName.codeUnits;
diff --git a/sdk/lib/_http/http_impl.dart b/sdk/lib/_http/http_impl.dart
index b61f273..6c7b768 100644
--- a/sdk/lib/_http/http_impl.dart
+++ b/sdk/lib/_http/http_impl.dart
@@ -1583,7 +1583,11 @@
     headers._finalize();
 
     // Write headers.
-    headers._build(buffer);
+    headers._build(buffer,
+        skipZeroContentLength: method == "CONNECT" ||
+            method == "DELETE" ||
+            method == "GET" ||
+            method == "HEAD");
     buffer.addByte(_CharCode.CR);
     buffer.addByte(_CharCode.LF);
     Uint8List headerBytes = buffer.takeBytes();
diff --git a/tests/standalone/io/http_content_length_test.dart b/tests/standalone/io/http_content_length_test.dart
index 15d6520..12e1e99 100644
--- a/tests/standalone/io/http_content_length_test.dart
+++ b/tests/standalone/io/http_content_length_test.dart
@@ -16,8 +16,8 @@
   int count = 0;
   HttpServer.bind("127.0.0.1", 0, backlog: totalConnections).then((server) {
     server.listen((HttpRequest request) {
-      Expect.equals("0", request.headers.value('content-length'));
-      Expect.equals(0, request.contentLength);
+      Expect.equals(null, request.headers.value('content-length'));
+      Expect.equals(-1, request.contentLength);
       var response = request.response;
       response.contentLength = 0;
       response.done.then((_) {
diff --git a/tests/standalone/io/http_detach_socket_test.dart b/tests/standalone/io/http_detach_socket_test.dart
index fdbc257..480c88d 100644
--- a/tests/standalone/io/http_detach_socket_test.dart
+++ b/tests/standalone/io/http_detach_socket_test.dart
@@ -110,14 +110,13 @@
       socket.listen((data) => body.write(new String.fromCharCodes(data)),
           onDone: () {
         List<String> lines = body.toString().split("\r\n");
-        Expect.equals(6, lines.length);
+        Expect.equals(5, lines.length);
         Expect.equals("GET / HTTP/1.1", lines[0]);
-        Expect.equals("", lines[4]);
-        Expect.equals("Some data", lines[5]);
-        lines.sort(); // Lines 1-3 becomes 3-5 in a fixed order.
+        Expect.equals("", lines[3]);
+        Expect.equals("Some data", lines[4]);
+        lines.sort(); // Lines 1-2 becomes 3-4 in a fixed order.
         Expect.equals("accept-encoding: gzip", lines[3]);
-        Expect.equals("content-length: 0", lines[4]);
-        Expect.equals("host: 127.0.0.1:${port}", lines[5]);
+        Expect.equals("host: 127.0.0.1:${port}", lines[4]);
         socket.close();
       });
       server.close();
diff --git a/tests/standalone_2/io/http_content_length_test.dart b/tests/standalone_2/io/http_content_length_test.dart
index 7b81398..708a7c1 100644
--- a/tests/standalone_2/io/http_content_length_test.dart
+++ b/tests/standalone_2/io/http_content_length_test.dart
@@ -18,8 +18,8 @@
   int count = 0;
   HttpServer.bind("127.0.0.1", 0, backlog: totalConnections).then((server) {
     server.listen((HttpRequest request) {
-      Expect.equals("0", request.headers.value('content-length'));
-      Expect.equals(0, request.contentLength);
+      Expect.equals(null, request.headers.value('content-length'));
+      Expect.equals(-1, request.contentLength);
       var response = request.response;
       response.contentLength = 0;
       response.done.then((_) {
diff --git a/tests/standalone_2/io/http_detach_socket_test.dart b/tests/standalone_2/io/http_detach_socket_test.dart
index bb33690..1613ace 100644
--- a/tests/standalone_2/io/http_detach_socket_test.dart
+++ b/tests/standalone_2/io/http_detach_socket_test.dart
@@ -112,14 +112,13 @@
       socket.listen((data) => body.write(new String.fromCharCodes(data)),
           onDone: () {
         List<String> lines = body.toString().split("\r\n");
-        Expect.equals(6, lines.length);
+        Expect.equals(5, lines.length);
         Expect.equals("GET / HTTP/1.1", lines[0]);
-        Expect.equals("", lines[4]);
-        Expect.equals("Some data", lines[5]);
-        lines.sort(); // Lines 1-3 becomes 3-5 in a fixed order.
+        Expect.equals("", lines[3]);
+        Expect.equals("Some data", lines[4]);
+        lines.sort(); // Lines 1-2 becomes 3-4 in a fixed order.
         Expect.equals("accept-encoding: gzip", lines[3]);
-        Expect.equals("content-length: 0", lines[4]);
-        Expect.equals("host: 127.0.0.1:${port}", lines[5]);
+        Expect.equals("host: 127.0.0.1:${port}", lines[4]);
         socket.close();
       });
       server.close();