Never auto-generate a Content-Length header. (#63)

I keep finding more tricky cases where this isn't allowed, so it's
probably best to avoid getting too clever and just leave it up to the
user.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a980cf5..8d6d397 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.7+1
+
+* Never auto-generate a `Content-Length` header.
+
 ## 0.6.7
 
 * Add `Request.isEmpty` and `Response.isEmpty` getters which indicate whether a
diff --git a/lib/src/message.dart b/lib/src/message.dart
index df770fe..3350cb0 100644
--- a/lib/src/message.dart
+++ b/lib/src/message.dart
@@ -13,11 +13,6 @@
 
 Body getBody(Message message) => message._body;
 
-/// The default set of headers for a message created with no body and no
-/// explicit headers.
-final _defaultHeaders = new ShelfUnmodifiableMap<String>(
-    {"content-length": "0"}, ignoreKeyCase: true);
-
 /// Represents logic shared between [Request] and [Response].
 abstract class Message {
   /// The HTTP headers.
@@ -149,39 +144,20 @@
 Map<String, String> _adjustHeaders(
     Map<String, String> headers, Body body) {
   var sameEncoding = _sameEncoding(headers, body);
-  if (sameEncoding) {
-    if (!body.isEmpty || hasHeader(headers, 'content-length')) {
-      return headers ?? const ShelfUnmodifiableMap.empty();
-    } else if (body.isEmpty && (headers == null || headers.isEmpty)) {
-      return _defaultHeaders;
-    }
-  }
+  if (sameEncoding) return headers ?? const ShelfUnmodifiableMap.empty();
 
   var newHeaders = headers == null
       ? new CaseInsensitiveMap<String>()
       : new CaseInsensitiveMap<String>.from(headers);
 
-  if (!sameEncoding) {
-    if (newHeaders['content-type'] == null) {
-      newHeaders['content-type'] =
-          'application/octet-stream; charset=${body.encoding.name}';
-    } else {
-      var contentType = new MediaType.parse(newHeaders['content-type'])
-          .change(parameters: {'charset': body.encoding.name});
-      newHeaders['content-type'] = contentType.toString();
-    }
+  if (newHeaders['content-type'] == null) {
+    newHeaders['content-type'] =
+        'application/octet-stream; charset=${body.encoding.name}';
+  } else {
+    var contentType = new MediaType.parse(newHeaders['content-type'])
+        .change(parameters: {'charset': body.encoding.name});
+    newHeaders['content-type'] = contentType.toString();
   }
-
-  // Only add a content-length header if the length is 0 and there isn't already
-  // a content-length set. Content-length may not be sent with a chunked
-  // transfer encoding, and HEAD requests may send a non-zero content-length
-  // along with an empty body.
-  //
-  // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4.
-  if (body.isEmpty) {
-    newHeaders.putIfAbsent('content-length', () => '0');
-  }
-
   return newHeaders;
 }
 
diff --git a/lib/src/request.dart b/lib/src/request.dart
index 5d6530d..0d0154d 100644
--- a/lib/src/request.dart
+++ b/lib/src/request.dart
@@ -105,9 +105,6 @@
   /// in [headers] will be set appropriately. If there is no existing
   /// Content-Type header, it will be set to "application/octet-stream".
   ///
-  /// If a non-[Stream] object is passed for the [body], the Content-Length
-  /// header is automatically set to the length of that body.
-  ///
   /// The default value for [protocolVersion] is '1.1'.
   ///
   /// ## `onHijack`
diff --git a/lib/src/response.dart b/lib/src/response.dart
index b9ab197..214b005 100644
--- a/lib/src/response.dart
+++ b/lib/src/response.dart
@@ -208,9 +208,6 @@
   /// If [encoding] is passed, the "encoding" field of the Content-Type header
   /// in [headers] will be set appropriately. If there is no existing
   /// Content-Type header, it will be set to "application/octet-stream".
-  ///
-  /// If a non-[Stream] object is passed for the [body], the Content-Length
-  /// header is automatically set to the length of that body.
   Response(this.statusCode, {body, Map<String, String> headers,
       Encoding encoding, Map<String, Object> context})
       : super(body, encoding: encoding, headers: headers, context: context) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 72b0877..89dbea5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: shelf
-version: 0.6.7
+version: 0.6.7+1
 author: Dart Team <misc@dartlang.org>
 description: Web Server Middleware for Dart
 homepage: https://github.com/dart-lang/shelf
diff --git a/test/message_change_test.dart b/test/message_change_test.dart
index 638f9dc..6113203 100644
--- a/test/message_change_test.dart
+++ b/test/message_change_test.dart
@@ -73,8 +73,7 @@
 
     expect(copy.headers, {
       'test': 'test value',
-      'test2': 'test2 value',
-      'content-length': '0'
+      'test2': 'test2 value'
     });
   });
 
@@ -82,7 +81,7 @@
     var request = factory(headers: {'test': 'test value'});
     var copy = request.change(headers: {'test': 'new test value'});
 
-    expect(copy.headers, {'test': 'new test value', 'content-length': '0'});
+    expect(copy.headers, {'test': 'new test value'});
   });
 
   test('new context values are added', () {
diff --git a/test/message_test.dart b/test/message_test.dart
index 4175455..7685896 100644
--- a/test/message_test.dart
+++ b/test/message_test.dart
@@ -36,10 +36,9 @@
       expect(message.headers, containsPair('FOO', 'bar'));
     });
 
-    test('null header value becomes default', () {
+    test('null header value returns an empty unmodifiable map', () {
       var message = _createMessage();
-      expect(message.headers, equals({'content-length': '0'}));
-      expect(message.headers, containsPair('CoNtEnT-lEnGtH', '0'));
+      expect(message.headers, isEmpty);
       expect(message.headers, same(_createMessage().headers));
       expect(() => message.headers['h1'] = 'value1', throwsUnsupportedError);
     });
@@ -158,46 +157,41 @@
     });
   });
 
-  group("content-length", () {
-    test("is 0 with a default body and without a content-length header", () {
+  group("isEmpty", () {
+    test("is true with a default body and without a content-length header", () {
       var request = _createMessage();
-      expect(request.contentLength, 0);
+      expect(request.isEmpty, isTrue);
     });
 
-    test("is 0 with an empty byte body", () {
+    test("is true with an empty byte body", () {
       var request = _createMessage(body: []);
-      expect(request.contentLength, 0);
+      expect(request.isEmpty, isTrue);
     });
 
-    test("is 0 with an empty string body", () {
+    test("is true with an empty string body", () {
       var request = _createMessage(body: '');
-      expect(request.contentLength, 0);
+      expect(request.isEmpty, isTrue);
     });
 
-    test("is null for an empty stream body", () {
+    test("is false for an empty stream body", () {
       var request = _createMessage(body: new Stream.empty());
-      expect(request.contentLength, isNull);
+      expect(request.isEmpty, isFalse);
     });
 
-    test("is null for a non-empty byte body", () {
+    test("is false for a non-empty byte body", () {
       var request = _createMessage(body: [1, 2, 3]);
-      expect(request.contentLength, isNull);
+      expect(request.isEmpty, isFalse);
     });
 
-    test("is null for a non-empty string body", () {
+    test("is false for a non-empty string body", () {
       var request = _createMessage(body: "foo");
-      expect(request.contentLength, isNull);
+      expect(request.isEmpty, isFalse);
     });
 
-    test("uses the content-length header for a stream body", () {
+    test("is false for a stream body even if a content length is passed", () {
       var request = _createMessage(
-          body: new Stream.empty(), headers: {'content-length': '42'});
-      expect(request.contentLength, 42);
-    });
-
-    test("content-length header takes precedence over an empty body", () {
-      var request = _createMessage(headers: {'content-length': '42'});
-      expect(request.contentLength, 42);
+          body: new Stream.empty(), headers: {'content-length': '0'});
+      expect(request.isEmpty, isFalse);
     });
   });
 
diff --git a/test/shelf_io_test.dart b/test/shelf_io_test.dart
index c555b19..a3aabb2 100644
--- a/test/shelf_io_test.dart
+++ b/test/shelf_io_test.dart
@@ -335,26 +335,12 @@
     });
   });
 
-  group("doesn't use a chunked transfer-encoding", () {
-    test("for a response with an empty body", () {
-      _scheduleServer((request) => new Response.notModified());
+  test("doesn't use a chunked transfer-encoding for a response with an empty "
+      "body", () {
+    _scheduleServer((request) => new Response.notModified());
 
-      return _scheduleGet().then((response) {
-        expect(response.headers, isNot(contains('transfer-encoding')));
-        expect(response.headers, containsPair('content-length', '0'));
-      });
-    });
-
-    test("for a response with an empty body and a non-empty content-length",
-        () {
-      _scheduleServer((request) {
-        return new Response.ok(null, headers: {'content-length': '42'});
-      });
-
-      return _scheduleHead().then((response) {
-        expect(response.headers, isNot(contains('transfer-encoding')));
-        expect(response.headers, containsPair('content-length', '42'));
-      });
+    return _scheduleGet().then((response) {
+      expect(response.headers, isNot(contains('transfer-encoding')));
     });
   });