Skip to content

Commit

Permalink
Reland [dart:io] Stop forcing lower case on HttpHeaders
Browse files Browse the repository at this point in the history
This is a breaking change. Request: #33501

HttpHeaders use lowercase by default for all headers, since it is supposed to be case insensitive. Some servers incorrectly treat case as significant, however, and expect headers with capitalization or in uppercase. The current implementation forces headers to be lower cases when adding values. Users cannot even manually modify the headers.

This change removes this restriction here so that users can modify the headers to whatever form they want. The new behavior is backwards compatible except if class was implemented. All headers inside http.dart are written as lower cases, adding values to HttpHeaders is still receiving lower cases input.

The other cl (https://dart-review.googlesource.com/c/http_multi_server/+/121411) migrates multi_headers.dart to be compatible with this change.

Bug: #33501
Change-Id: Ieb9f4061b27ed3bbc6d82e6a408c77d11abb037b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/135357
Commit-Queue: Zichang Guo <zichangguo@google.com>
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
  • Loading branch information
zichangg authored and commit-bot@chromium.org committed Feb 14, 2020
1 parent 88a711d commit d39cdf0
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 79 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ used (see Issue [39627][]).

* Class `HttpParser` will no longer throw an exception when a HTTP response
status code is within [0, 999]. Customized status codes in this range are now valid.
* **Breaking change** [#33501](https://github.com/dart-lang/sdk/issues/33501):

An named parameter is added to `add` and `set` for class `HttpHeaders`.
The signature of has been changed from `void add(String name, Object value)` to
`void add(String name, Object value, {bool preserveHeaderCase: false})`.
Same change is applied to `set`. `preserveHeaderCase` will preserve the
case of `name` instead of converting them to lowercase.
`HttpHeader.forEach()` provides the current case of each header.

* The `Socket` class will now throw a `SocketException` if the socket has been
destroyed or upgraded to a secure socket upon setting or getting socket
Expand Down
6 changes: 3 additions & 3 deletions pkg/dev_compiler/tool/dart2js_nnbd_sdk_error_golden.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ ERROR|STATIC_TYPE_WARNING|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interc
ERROR|STATIC_TYPE_WARNING|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1661|17|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|STATIC_TYPE_WARNING|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1666|18|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|STATIC_TYPE_WARNING|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1666|44|1|The operator '&' isn't defined for the type 'JSInt'.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|1463|39|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|8345|60|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|9272|54|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|1476|39|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|8383|60|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|9310|54|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/developer/developer.dart|315|25|23|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/html/dart2js/html_dart2js.dart|4075|25|2|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/io/io.dart|9167|16|1|The left operand can't be null, so the right operand is never executed.
6 changes: 3 additions & 3 deletions pkg/dev_compiler/tool/dartdevc_nnbd_sdk_error_golden.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ ERROR|COMPILE_TIME_ERROR|BODY_MAY_COMPLETE_NORMALLY|lib/_internal/js_dev_runtime
ERROR|COMPILE_TIME_ERROR|BODY_MAY_COMPLETE_NORMALLY|lib/html/dart2js/html_dart2js.dart|23331|8|15|The body might complete normally, which would cause 'null' to be returned, but the return type is a potentially non-nullable type.
ERROR|COMPILE_TIME_ERROR|BODY_MAY_COMPLETE_NORMALLY|lib/html/dart2js/html_dart2js.dart|2978|13|11|The body might complete normally, which would cause 'null' to be returned, but the return type is a potentially non-nullable type.
ERROR|COMPILE_TIME_ERROR|BODY_MAY_COMPLETE_NORMALLY|lib/html/dart2js/html_dart2js.dart|41436|8|15|The body might complete normally, which would cause 'null' to be returned, but the return type is a potentially non-nullable type.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|1463|39|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|8345|60|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|9272|54|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|1476|39|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|8383|60|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/_http/http.dart|9310|54|5|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/collection/collection.dart|1076|46|13|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/developer/developer.dart|332|25|23|The left operand can't be null, so the right operand is never executed.
WARNING|STATIC_WARNING|DEAD_NULL_COALESCE|lib/html/dart2js/html_dart2js.dart|4075|25|2|The left operand can't be null, so the right operand is never executed.
Expand Down
49 changes: 34 additions & 15 deletions sdk/lib/_http/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -672,21 +672,36 @@ abstract class HttpHeaders {
String value(String name);

/**
* Adds a header value. The header named [name] will have the value
* [value] added to its list of values. Some headers are single
* valued, and for these adding a value will replace the previous
* value. If the value is of type DateTime a HTTP date format will be
* applied. If the value is a [:List:] each element of the list will
* be added separately. For all other types the default [:toString:]
* method will be used.
* Adds a header value.
*
* The header named [name] will have a string value derived from [value]
* added to its list of values.
*
* Some headers are single valued, and for these, adding a value will
* replace a previous value. If the [value] is a [DateTime], an
* HTTP date format will be applied. If the value is an [Iterable],
* each element will be added separately. For all other
* types the default [Object.toString] method will be used.
*
* Header names are converted to lower-case unless
* [preserveHeaderCase] is set to true. If two header names are
* the same when converted to lower-case, they are considered to be
* the same header, with one set of values.
*
* The current case of the a header name is that of the name used by
* the last [set] or [add] call for that header.
*/
void add(String name, Object value);
void add(String name, Object value,
{@Since("2.8") bool preserveHeaderCase = false});

/**
* Sets a header. The header named [name] will have all its values
* cleared before the value [value] is added as its value.
* Sets the header [name] to [value].
*
* Removes all existing values for the header named [name] and
* then [add]s [value] to it.
*/
void set(String name, Object value);
void set(String name, Object value,
{@Since("2.8") bool preserveHeaderCase = false});

/**
* Removes a specific value for a header name. Some headers have
Expand All @@ -704,11 +719,15 @@ abstract class HttpHeaders {
void removeAll(String name);

/**
* Enumerates the headers, applying the function [f] to each
* header. The header name passed in [:name:] will be all lower
* case.
* Performs the [action] on each header.
*
* The [action] function is called with each header's name and a list
* of the header's values. The casing of the name string is determined by
* the last [add] or [set] operation for that particular header,
* which defaults to lower-casing the header name unless explicitly
* set to preserve the case.
*/
void forEach(void f(String name, List<String> values));
void forEach(void action(String name, List<String> values));

/**
* Disables folding for the header named [name] when sending the HTTP
Expand Down
67 changes: 47 additions & 20 deletions sdk/lib/_http/http_headers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ part of dart._http;

class _HttpHeaders implements HttpHeaders {
final Map<String, List<String>> _headers;
// The original header names keyed by the lowercase header names.
Map<String, String> _originalHeaderNames;
final String protocolVersion;

bool _mutable = true; // Are the headers currently mutable?
Expand Down Expand Up @@ -40,10 +42,10 @@ class _HttpHeaders implements HttpHeaders {
}
}

List<String> operator [](String name) => _headers[name.toLowerCase()];
List<String> operator [](String name) => _headers[_validateField(name)];

String value(String name) {
name = name.toLowerCase();
name = _validateField(name);
List<String> values = _headers[name];
if (values == null) return null;
if (values.length > 1) {
Expand All @@ -52,13 +54,19 @@ class _HttpHeaders implements HttpHeaders {
return values[0];
}

void add(String name, value) {
void add(String name, value, {bool preserveHeaderCase = false}) {
_checkMutable();
_addAll(_validateField(name), value);
String lowercaseName = _validateField(name);

if (preserveHeaderCase && name != lowercaseName) {
(_originalHeaderNames ??= {})[lowercaseName] = name;
} else {
_originalHeaderNames?.remove(lowercaseName);
}
_addAll(lowercaseName, value);
}

void _addAll(String name, value) {
assert(name == _validateField(name));
if (value is Iterable) {
for (var v in value) {
_add(name, _validateValue(v));
Expand All @@ -68,14 +76,20 @@ class _HttpHeaders implements HttpHeaders {
}
}

void set(String name, Object value) {
void set(String name, Object value, {bool preserveHeaderCase = false}) {
_checkMutable();
name = _validateField(name);
_headers.remove(name);
if (name == HttpHeaders.transferEncodingHeader) {
String lowercaseName = _validateField(name);
_headers.remove(lowercaseName);
_originalHeaderNames?.remove(lowercaseName);
if (lowercaseName == HttpHeaders.transferEncodingHeader) {
_chunkedTransferEncoding = false;
}
_addAll(name, value);
if (preserveHeaderCase && name != lowercaseName) {
(_originalHeaderNames ??= {})[lowercaseName] = name;
} else {
_originalHeaderNames?.remove(lowercaseName);
}
_addAll(lowercaseName, value);
}

void remove(String name, Object value) {
Expand All @@ -88,7 +102,10 @@ class _HttpHeaders implements HttpHeaders {
if (index != -1) {
values.removeRange(index, index + 1);
}
if (values.length == 0) _headers.remove(name);
if (values.length == 0) {
_headers.remove(name);
_originalHeaderNames?.remove(name);
}
}
if (name == HttpHeaders.transferEncodingHeader && value == "chunked") {
_chunkedTransferEncoding = false;
Expand All @@ -99,10 +116,14 @@ class _HttpHeaders implements HttpHeaders {
_checkMutable();
name = _validateField(name);
_headers.remove(name);
_originalHeaderNames?.remove(name);
}

void forEach(void f(String name, List<String> values)) {
_headers.forEach(f);
void forEach(void action(String name, List<String> values)) {
_headers.forEach((String name, List<String> values) {
String originalName = _originalHeaderName(name);
action(originalName, values);
});
}

void noFolding(String name) {
Expand Down Expand Up @@ -498,14 +519,15 @@ class _HttpHeaders implements HttpHeaders {
String toString() {
StringBuffer sb = new StringBuffer();
_headers.forEach((String name, List<String> values) {
sb..write(name)..write(": ");
String originalName = _originalHeaderName(name);
sb..write(originalName)..write(": ");
bool fold = _foldHeader(name);
for (int i = 0; i < values.length; i++) {
if (i > 0) {
if (fold) {
sb.write(", ");
} else {
sb..write("\n")..write(name)..write(": ");
sb..write("\n")..write(originalName)..write(": ");
}
}
sb.write(values[i]);
Expand Down Expand Up @@ -591,22 +613,27 @@ class _HttpHeaders implements HttpHeaders {
for (var i = 0; i < field.length; i++) {
if (!_HttpParser._isTokenChar(field.codeUnitAt(i))) {
throw new FormatException(
"Invalid HTTP header field name: ${json.encode(field)}");
"Invalid HTTP header field name: ${json.encode(field)}", field, i);
}
}
return field.toLowerCase();
}

static _validateValue(value) {
static Object _validateValue(Object value) {
if (value is! String) return value;
for (var i = 0; i < value.length; i++) {
if (!_HttpParser._isValueChar(value.codeUnitAt(i))) {
for (var i = 0; i < (value as String).length; i++) {
if (!_HttpParser._isValueChar((value as String).codeUnitAt(i))) {
throw new FormatException(
"Invalid HTTP header field value: ${json.encode(value)}");
"Invalid HTTP header field value: ${json.encode(value)}", value, i);
}
}
return value;
}

String _originalHeaderName(String name) {
return (_originalHeaderNames == null ? null : _originalHeaderNames[name]) ??
name;
}
}

class _HeaderValue implements HeaderValue {
Expand Down
41 changes: 27 additions & 14 deletions sdk_nnbd/lib/_http/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -683,22 +683,31 @@ abstract class HttpHeaders {
* The header named [name] will have a string value derived from [value]
* added to its list of values.
*
* Some headers are single valued, and for these adding a value will replace
* a previous value.
* If the [value] is a [DateTime], an HTTP date format will be
* applied. If the value is a [List], each element of the list will
* be added separately. For all other types the default [Object.toString]
* method will be used.
* Some headers are single valued, and for these, adding a value will
* replace a previous value. If the [value] is a [DateTime], an
* HTTP date format will be applied. If the value is an [Iterable],
* each element will be added separately. For all other
* types the default [Object.toString] method will be used.
*
* Header names are converted to lower-case unless
* [preserveHeaderCase] is set to true. If two header names are
* the same when converted to lower-case, they are considered to be
* the same header, with one set of values.
*
* The current case of the a header name is that of the name used by
* the last [set] or [add] call for that header.
*/
void add(String name, Object value);
void add(String name, Object value,
{@Since("2.8") bool preserveHeaderCase = false});

/**
* Sets a header.
* Sets the header [name] to [value].
*
* Removes all existing values for [name], then adds [value] as
* if using [add].
* Removes all existing values for the header named [name] and
* then [add]s [value] to it.
*/
void set(String name, Object value);
void set(String name, Object value,
{@Since("2.8") bool preserveHeaderCase = false});

/**
* Removes a specific value for a header name.
Expand All @@ -723,11 +732,15 @@ abstract class HttpHeaders {
void removeAll(String name);

/**
* Enumerates the headers, applying the function [f] to each header.
* Performs the [action] on each header.
*
* The header name passed in [name] will be all lower case.
* The [action] function is called with each header's name and a list
* of the header's values. The casing of the name string is determined by
* the last [add] or [set] operation for that particular header,
* which defaults to lower-casing the header name unless explicitly
* set to preserve the case.
*/
void forEach(void f(String name, List<String> values));
void forEach(void action(String name, List<String> values));

/**
* Disables folding for the header named [name] when sending the HTTP header.
Expand Down
Loading

0 comments on commit d39cdf0

Please sign in to comment.