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')));
});
});