Revert "[dart:io] Stop forcing lower case on HttpHeaders"

This reverts commit b2b7337ad4.

Reason for revert: flutter build broke!!

Original change's description:
> [dart:io] Stop forcing lower case on HttpHeaders
> 
> This is a breaking change. Request: https://github.com/dart-lang/sdk/issues/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: https://github.com/dart-lang/sdk/issues/33501
> Change-Id: I6f7f2ef907b229773c283140c07f2de4cd500981
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/119100
> Commit-Queue: Zichang Guo <zichangguo@google.com>
> Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>

TBR=whesse@google.com,sortie@google.com,lrn@google.com,zichangguo@google.com

Change-Id: I4d4299393ad6549b250053df8823e726855e2baf
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: https://github.com/dart-lang/sdk/issues/33501
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/134102
Reviewed-by: Zichang Guo <zichangguo@google.com>
Commit-Queue: Zichang Guo <zichangguo@google.com>
This commit is contained in:
Zichang Guo 2020-01-31 22:32:22 +00:00 committed by commit-bot@chromium.org
parent aa916d6367
commit 91a3ea89c8
7 changed files with 68 additions and 235 deletions

View file

@ -17,14 +17,6 @@ used (see Issue [39627][]).
#### `dart:io`
* **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.
### Dart VM
### Tools

2
DEPS
View file

@ -92,7 +92,7 @@ vars = {
"glob_tag": "1.1.7",
"html_tag" : "0.14.0+1",
"http_io_rev": "2fa188caf7937e313026557713f7feffedd4978b",
"http_multi_server_tag" : "98c2789fc1c47544afc8efbec5fa9c1499ecf5e2",
"http_multi_server_tag" : "2.0.5",
"http_parser_tag" : "3.1.3",
"http_retry_tag": "0.1.1",
"http_tag" : "0.12.0+2",

View file

@ -672,36 +672,21 @@ abstract class HttpHeaders {
String value(String name);
/**
* 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.
* 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.
*/
void add(String name, Object value,
{@Since("2.8") bool preserveHeaderCase: false});
void add(String name, Object value);
/**
* Sets the header [name] to [value].
*
* Removes all existing values for the header named [name] and
* then [add]s [value] to it.
* Sets a header. The header named [name] will have all its values
* cleared before the value [value] is added as its value.
*/
void set(String name, Object value,
{@Since("2.8") bool preserveHeaderCase: false});
void set(String name, Object value);
/**
* Removes a specific value for a header name. Some headers have
@ -719,15 +704,11 @@ abstract class HttpHeaders {
void removeAll(String name);
/**
* 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.
* Enumerates the headers, applying the function [f] to each
* header. The header name passed in [:name:] will be all lower
* case.
*/
void forEach(void action(String name, List<String> values));
void forEach(void f(String name, List<String> values));
/**
* Disables folding for the header named [name] when sending the HTTP

View file

@ -8,8 +8,6 @@ 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?
@ -42,10 +40,10 @@ class _HttpHeaders implements HttpHeaders {
}
}
List<String> operator [](String name) => _headers[_validateField(name)];
List<String> operator [](String name) => _headers[name.toLowerCase()];
String value(String name) {
name = _validateField(name);
name = name.toLowerCase();
List<String> values = _headers[name];
if (values == null) return null;
if (values.length > 1) {
@ -54,21 +52,13 @@ class _HttpHeaders implements HttpHeaders {
return values[0];
}
void add(String name, value, {bool preserveHeaderCase: false}) {
void add(String name, value) {
_checkMutable();
String lowercaseName = _validateField(name);
// TODO(zichangguo): Consider storing all previously cased name for each
// entity instead of replacing name.
if (preserveHeaderCase && name != lowercaseName) {
(_originalHeaderNames ??= {})[lowercaseName] = name;
} else {
_originalHeaderNames?.remove(lowercaseName);
}
_addAll(lowercaseName, value);
_addAll(_validateField(name), value);
}
void _addAll(String name, value) {
assert(name == _validateField(name));
if (value is Iterable) {
for (var v in value) {
_add(name, _validateValue(v));
@ -78,20 +68,14 @@ class _HttpHeaders implements HttpHeaders {
}
}
void set(String name, Object value, {bool preserveHeaderCase: false}) {
void set(String name, Object value) {
_checkMutable();
String lowercaseName = _validateField(name);
_headers.remove(lowercaseName);
_originalHeaderNames?.remove(lowercaseName);
if (lowercaseName == HttpHeaders.transferEncodingHeader) {
name = _validateField(name);
_headers.remove(name);
if (name == HttpHeaders.transferEncodingHeader) {
_chunkedTransferEncoding = false;
}
if (preserveHeaderCase && name != lowercaseName) {
(_originalHeaderNames ??= {})[lowercaseName] = name;
} else {
_originalHeaderNames?.remove(lowercaseName);
}
_addAll(lowercaseName, value);
_addAll(name, value);
}
void remove(String name, Object value) {
@ -104,10 +88,7 @@ class _HttpHeaders implements HttpHeaders {
if (index != -1) {
values.removeRange(index, index + 1);
}
if (values.length == 0) {
_headers.remove(name);
_originalHeaderNames?.remove(name);
}
if (values.length == 0) _headers.remove(name);
}
if (name == HttpHeaders.transferEncodingHeader && value == "chunked") {
_chunkedTransferEncoding = false;
@ -118,14 +99,10 @@ class _HttpHeaders implements HttpHeaders {
_checkMutable();
name = _validateField(name);
_headers.remove(name);
_originalHeaderNames?.remove(name);
}
void forEach(void action(String name, List<String> values)) {
_headers.forEach((String name, List<String> values) {
String originalName = _originalHeaderName(name);
action(originalName, values);
});
void forEach(void f(String name, List<String> values)) {
_headers.forEach(f);
}
void noFolding(String name) {
@ -521,15 +498,14 @@ class _HttpHeaders implements HttpHeaders {
String toString() {
StringBuffer sb = new StringBuffer();
_headers.forEach((String name, List<String> values) {
String originalName = _originalHeaderName(name);
sb..write(originalName)..write(": ");
sb..write(name)..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(originalName)..write(": ");
sb..write("\n")..write(name)..write(": ");
}
}
sb.write(values[i]);
@ -615,7 +591,7 @@ 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)}", field, i);
"Invalid HTTP header field name: ${json.encode(field)}");
}
}
return field.toLowerCase();
@ -626,16 +602,11 @@ class _HttpHeaders implements HttpHeaders {
for (var i = 0; i < value.length; i++) {
if (!_HttpParser._isValueChar(value.codeUnitAt(i))) {
throw new FormatException(
"Invalid HTTP header field value: ${json.encode(value)}", value, i);
"Invalid HTTP header field value: ${json.encode(value)}");
}
}
return value;
}
String _originalHeaderName(String name) {
return (_originalHeaderNames == null ? null : _originalHeaderNames[name]) ??
name;
}
}
class _HeaderValue implements HeaderValue {

View file

@ -683,31 +683,22 @@ 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 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.
* 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.
*/
void add(String name, Object value,
{@Since("2.8") bool preserveHeaderCase: false});
void add(String name, Object value);
/**
* Sets the header [name] to [value].
* Sets a header.
*
* Removes all existing values for the header named [name] and
* then [add]s [value] to it.
* Removes all existing values for [name], then adds [value] as
* if using [add].
*/
void set(String name, Object value,
{@Since("2.8") bool preserveHeaderCase: false});
void set(String name, Object value);
/**
* Removes a specific value for a header name.
@ -732,15 +723,11 @@ abstract class HttpHeaders {
void removeAll(String name);
/**
* Performs the [action] on each header.
* Enumerates the headers, applying the function [f] to 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.
* The header name passed in [name] will be all lower case.
*/
void forEach(void action(String name, List<String> values));
void forEach(void f(String name, List<String> values));
/**
* Disables folding for the header named [name] when sending the HTTP header.

View file

@ -6,8 +6,6 @@ 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?
@ -40,10 +38,10 @@ class _HttpHeaders implements HttpHeaders {
}
}
List<String>? operator [](String name) => _headers[_validateField(name)];
List<String>? operator [](String name) => _headers[name.toLowerCase()];
String? value(String name) {
name = _validateField(name);
name = name.toLowerCase();
List<String>? values = _headers[name];
if (values == null) return null;
assert(values.isNotEmpty);
@ -53,21 +51,13 @@ class _HttpHeaders implements HttpHeaders {
return values[0];
}
void add(String name, value, {bool preserveHeaderCase: false}) {
void add(String name, value) {
_checkMutable();
String lowercaseName = _validateField(name);
// TODO(zichangguo): Consider storing all previously cased name for each
// entity instead of replacing name.
if (preserveHeaderCase && name != lowercaseName) {
(_originalHeaderNames ??= {})[lowercaseName] = name;
} else {
_originalHeaderNames?.remove(lowercaseName);
}
_addAll(lowercaseName, value);
_addAll(_validateField(name), value);
}
void _addAll(String name, value) {
assert(name == _validateField(name));
if (value is Iterable) {
for (var v in value) {
_add(name, _validateValue(v));
@ -77,50 +67,39 @@ class _HttpHeaders implements HttpHeaders {
}
}
void set(String name, Object value, {bool preserveHeaderCase: false}) {
void set(String name, Object value) {
_checkMutable();
String lowercaseName = _validateField(name);
_headers.remove(lowercaseName);
_originalHeaderNames?.remove(lowercaseName);
if (lowercaseName == HttpHeaders.transferEncodingHeader) {
name = _validateField(name);
_headers.remove(name);
if (name == HttpHeaders.transferEncodingHeader) {
_chunkedTransferEncoding = false;
}
if (preserveHeaderCase && name != lowercaseName) {
(_originalHeaderNames ??= {})[lowercaseName] = name;
}
_addAll(lowercaseName, value);
_addAll(name, value);
}
void remove(String name, Object value) {
_checkMutable();
name = _validateField(name);
value = _validateValue(value);
List<String>? values = _headers[name];
if (values != null) {
values.remove(_valueToString(value));
if (values.length == 0) {
_headers.remove(name);
_originalHeaderNames?.remove(name);
}
}
if (name == HttpHeaders.transferEncodingHeader && value == "chunked") {
_chunkedTransferEncoding = false;
return;
}
List<String>? values = _headers[name];
if (values != null) {
values.remove(_valueToString(value));
if (values.length == 0) _headers.remove(name);
}
}
void removeAll(String name) {
_checkMutable();
name = _validateField(name);
_headers.remove(name);
_originalHeaderNames?.remove(name);
}
void forEach(void action(String name, List<String> values)) {
_headers.forEach((String name, List<String> values) {
String originalName = _originalHeaderName(name);
action(originalName, values);
});
void forEach(void f(String name, List<String> values)) {
_headers.forEach(f);
}
void noFolding(String name) {
@ -532,15 +511,14 @@ class _HttpHeaders implements HttpHeaders {
String toString() {
StringBuffer sb = new StringBuffer();
_headers.forEach((String name, List<String> values) {
String originalName = _originalHeaderName(name);
sb..write(originalName)..write(": ");
sb..write(name)..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(originalName)..write(": ");
sb..write("\n")..write(name)..write(": ");
}
}
sb.write(values[i]);
@ -626,7 +604,7 @@ 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)}", field, i);
"Invalid HTTP header field name: ${json.encode(field)}");
}
}
return field.toLowerCase();
@ -637,15 +615,11 @@ class _HttpHeaders implements HttpHeaders {
for (var i = 0; i < value.length; i++) {
if (!_HttpParser._isValueChar(value.codeUnitAt(i))) {
throw new FormatException(
"Invalid HTTP header field value: ${json.encode(value)}", value, i);
"Invalid HTTP header field value: ${json.encode(value)}");
}
}
return value;
}
String _originalHeaderName(String name) {
return _originalHeaderNames?.[name] ?? name;
}
}
class _HeaderValue implements HeaderValue {

View file

@ -580,76 +580,6 @@ void testFolding() {
Expect.isTrue(str.contains(': d'));
}
void testLowercaseAdd() {
_HttpHeaders headers = new _HttpHeaders("1.1");
headers.add('A', 'a');
Expect.equals(headers['a'][0], headers['A'][0]);
Expect.equals(headers['A'][0], 'a');
headers.add('Foo', 'Foo', preserveHeaderCase: true);
Expect.equals(headers['Foo'][0], 'Foo');
// Header field is Foo.
Expect.isTrue(headers.toString().contains('Foo:'));
headers.add('FOo', 'FOo', preserveHeaderCase: true);
// Header field changes to FOo.
Expect.isTrue(headers.toString().contains('FOo:'));
headers.add('FOO', 'FOO', preserveHeaderCase: false);
// Header field
Expect.isTrue(!headers.toString().contains('Foo:'));
Expect.isTrue(!headers.toString().contains('FOo:'));
Expect.isTrue(headers.toString().contains('FOO'));
}
void testLowercaseSet() {
_HttpHeaders headers = new _HttpHeaders("1.1");
headers.add('test', 'lower cases');
// 'Test' should override 'test' entity
headers.set('TEST', 'upper cases', preserveHeaderCase: true);
Expect.isTrue(headers.toString().contains('TEST: upper cases'));
Expect.equals(1, headers['test'].length);
Expect.equals(headers['test'][0], 'upper cases');
// Latest header will be stored.
headers.set('Test', 'mixed cases', preserveHeaderCase: true);
Expect.isTrue(headers.toString().contains('Test: mixed cases'));
Expect.equals(1, headers['test'].length);
Expect.equals(headers['test'][0], 'mixed cases');
}
void testForEach() {
_HttpHeaders headers = new _HttpHeaders("1.1");
headers.add('header1', 'value1');
headers.add('header2', 'value2');
headers.add('HEADER1', 'value3', preserveHeaderCase: true);
headers.add('HEADER3', 'value4', preserveHeaderCase: true);
bool myHeader1 = false;
bool myHeader2 = false;
bool myHeader3 = false;
int totalValues = 0;
headers.forEach((String name, List<String> values) {
totalValues += values.length;
if (name == "HEADER1") {
myHeader1 = true;
Expect.isTrue(values.indexOf("value 1") != -1);
Expect.isTrue(values.indexOf("value 3") != -1);
}
if (name == "header2") {
myHeader2 = true;
Expect.isTrue(values.indexOf("value 2") != -1);
}
if (name == "HEADER3") {
myHeader3 = true;
Expect.isTrue(values.indexOf("value 4") != -1);
}
});
Expect.isTrue(myHeader1);
Expect.isTrue(myHeader2);
Expect.isTrue(myHeader3);
Expect.equals(4, totalValues);
}
main() {
testMultiValue();
testDate();
@ -669,6 +599,4 @@ main() {
testInvalidFieldValue();
testClear();
testFolding();
testLowercaseAdd();
testLowercaseSet();
}