mirror of
https://github.com/dart-lang/sdk
synced 2024-10-02 23:24:42 +00:00
Reland [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: 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>
This commit is contained in:
parent
88a711d398
commit
d39cdf03f8
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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]);
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
@ -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.
|
||||
|
|
|
@ -6,6 +6,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?
|
||||
|
@ -38,10 +40,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;
|
||||
assert(values.isNotEmpty);
|
||||
|
@ -51,13 +53,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));
|
||||
|
@ -67,27 +75,34 @@ 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;
|
||||
}
|
||||
_addAll(lowercaseName, value);
|
||||
}
|
||||
|
||||
void remove(String name, Object value) {
|
||||
_checkMutable();
|
||||
name = _validateField(name);
|
||||
value = _validateValue(value);
|
||||
if (name == HttpHeaders.transferEncodingHeader && value == "chunked") {
|
||||
_chunkedTransferEncoding = false;
|
||||
}
|
||||
List<String>? values = _headers[name];
|
||||
if (values != null) {
|
||||
values.remove(_valueToString(value));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,10 +110,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) {
|
||||
|
@ -449,7 +468,7 @@ class _HttpHeaders implements HttpHeaders {
|
|||
} else if (value is String) {
|
||||
return value; // TODO(39784): no _validateValue?
|
||||
} else {
|
||||
return _validateValue(value.toString());
|
||||
return _validateValue(value.toString()) as String;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,14 +529,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]);
|
||||
|
@ -603,22 +623,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) {
|
||||
// TODO: Update syntax to_originalHeaderNames?[name].
|
||||
return _originalHeaderNames?.[name] ?? name;
|
||||
}
|
||||
}
|
||||
|
||||
class _HeaderValue implements HeaderValue {
|
||||
|
|
|
@ -580,6 +580,76 @@ 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', 'value 1');
|
||||
headers.add('header2', 'value 2');
|
||||
headers.add('HEADER1', 'value 3', preserveHeaderCase: true);
|
||||
headers.add('HEADER3', 'value 4', 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();
|
||||
|
@ -599,4 +669,7 @@ main() {
|
|||
testInvalidFieldValue();
|
||||
testClear();
|
||||
testFolding();
|
||||
testLowercaseAdd();
|
||||
testLowercaseSet();
|
||||
testForEach();
|
||||
}
|
||||
|
|
|
@ -580,6 +580,76 @@ 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', 'value 1');
|
||||
headers.add('header2', 'value 2');
|
||||
headers.add('HEADER1', 'value 3', preserveHeaderCase: true);
|
||||
headers.add('HEADER3', 'value 4', 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();
|
||||
|
@ -599,4 +669,7 @@ main() {
|
|||
testInvalidFieldValue();
|
||||
testClear();
|
||||
testFolding();
|
||||
testLowercaseAdd();
|
||||
testLowercaseSet();
|
||||
testForEach();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue