Skip to content

Commit a6db069

Browse files
zichanggcommit-bot@chromium.org
authored andcommitted
[dart:io] Loosen the HTTP header size limit
The `HttpClient` and `HttpServer` clasess now have a 1 MiB limit for the total size of the HTTP headers when parsing a request or response, instead of the former 8 KiB limit for each header name and value. This limit cannot be configured at this time. Bug: flutter/flutter#56580 Change-Id: I5f094df32a93ec3e6645a0d69d8cf8263082775a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150500 Commit-Queue: Zichang Guo <zichangguo@google.com> Reviewed-by: Jonas Termansen <sortie@google.com>
1 parent d9b874c commit a6db069

File tree

4 files changed

+175
-6
lines changed

4 files changed

+175
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
to cancel outgoing HTTP requests and stop following IO operations.
99
* A validation check is added to `path` of class `Cookie`. Having characters
1010
ranging from 0x00 to 0x1f and 0x3b (";") will lead to a `FormatException`.
11+
* The `HttpClient` and `HttpServer` clasess now have a 1 MiB limit for the
12+
total size of the HTTP headers when parsing a request or response, instead
13+
of the former 8 KiB limit for each header name and value. This limit cannot
14+
be configured at this time.
1115

1216
#### `dart:typed_data`
1317

sdk/lib/_http/http_parser.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ class _HttpParser extends Stream<_HttpIncoming> {
236236
Uint8List? _buffer;
237237
int _index = -1;
238238

239+
// Whether a HTTP request is being parsed (as opposed to a response).
239240
final bool _requestParser;
240241
int _state = _State.START;
241242
int? _httpVersionIndex;
@@ -246,8 +247,8 @@ class _HttpParser extends Stream<_HttpIncoming> {
246247
final List<int> _uriOrReasonPhrase = [];
247248
final List<int> _headerField = [];
248249
final List<int> _headerValue = [];
249-
// The limit for method, uriOrReasonPhrase, header field and value
250-
int _headerSizeLimit = 8 * 1024;
250+
static const _headerTotalSizeLimit = 1024 * 1024;
251+
int _headersReceivedSize = 0;
251252

252253
int _httpVersion = _HttpVersion.UNDETERMINED;
253254
int _transferLength = -1;
@@ -946,6 +947,7 @@ class _HttpParser extends Stream<_HttpIncoming> {
946947
_messageType = _MessageType.UNDETERMINED;
947948
_headerField.clear();
948949
_headerValue.clear();
950+
_headersReceivedSize = 0;
949951
_method.clear();
950952
_uriOrReasonPhrase.clear();
951953

@@ -977,8 +979,7 @@ class _HttpParser extends Stream<_HttpIncoming> {
977979
}
978980

979981
static bool _isValueChar(int byte) {
980-
return (byte > 31 && byte < 128) ||
981-
(byte == _CharCode.HT);
982+
return (byte > 31 && byte < 128) || (byte == _CharCode.HT);
982983
}
983984

984985
static List<String> _tokenizeFieldValue(String headerValue) {
@@ -1036,7 +1037,8 @@ class _HttpParser extends Stream<_HttpIncoming> {
10361037
}
10371038

10381039
void _addWithValidation(List<int> list, int byte) {
1039-
if (list.length < _headerSizeLimit) {
1040+
_headersReceivedSize++;
1041+
if (_headersReceivedSize < _headerTotalSizeLimit) {
10401042
list.add(byte);
10411043
} else {
10421044
_reportSizeLimitError();
@@ -1074,7 +1076,8 @@ class _HttpParser extends Stream<_HttpIncoming> {
10741076
throw UnsupportedError("Unexpected state: $_state");
10751077
break;
10761078
}
1077-
throw HttpException("$method exceeds the $_headerSizeLimit size limit");
1079+
throw HttpException(
1080+
"$method exceeds the $_headerTotalSizeLimit size limit");
10781081
}
10791082

10801083
_HttpIncoming _createIncoming(int transferLength) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:expect/expect.dart';
8+
9+
// This test checks whether a big header is accepted.
10+
11+
Future<void> testClient(int limit) async {
12+
final server = await HttpServer.bind('127.0.0.1', 0);
13+
final str = 'a' * (1000);
14+
int size = 0;
15+
server.listen((request) async {
16+
for (int i = 0; i < 10000; i++) {
17+
request.response.headers.add('dummy', str);
18+
size += 1000;
19+
if (size > limit) {
20+
break;
21+
}
22+
}
23+
await request.response.close();
24+
server.close();
25+
});
26+
27+
final client = HttpClient();
28+
final request = await client.get('127.0.0.1', server.port, '/');
29+
await request.close();
30+
}
31+
32+
Future<void> client() async {
33+
int i = 64;
34+
try {
35+
for (; i < 101 * 1024 * 1024; i *= 100) {
36+
await testClient(i);
37+
}
38+
} on HttpException catch (e) {
39+
Expect.isTrue(e.toString().contains('size limit'));
40+
Expect.isTrue(i > 1024 * 1024);
41+
return;
42+
}
43+
Expect.fail('An exception is expected');
44+
}
45+
46+
Future<void> testServer(int limit, int port) async {
47+
final str = 'a' * (1000);
48+
final client = HttpClient();
49+
final request = await client.get('127.0.0.1', port, '/');
50+
for (int size = 0; size < limit; size += 1000) {
51+
request.headers.add('dummy', str);
52+
}
53+
await request.close();
54+
}
55+
56+
Future<void> server() async {
57+
final server = await HttpServer.bind('127.0.0.1', 0);
58+
int i = 64;
59+
try {
60+
server.listen((request) async {
61+
await request.response.close();
62+
});
63+
for (; i < 101 * 1024 * 1024; i *= 100) {
64+
print(i);
65+
await testServer(i, server.port);
66+
}
67+
} on SocketException catch (_) {
68+
// Server will close on error and writing to the socket will be blocked due
69+
// to broken pipe.
70+
Expect.isTrue(i > 1024 * 1024);
71+
server.close();
72+
return;
73+
}
74+
server.close();
75+
Expect.fail('An exception is expected');
76+
}
77+
78+
Future<void> main() async {
79+
await client();
80+
await server();
81+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:expect/expect.dart';
8+
9+
// This test checks whether a big header is accepted.
10+
11+
Future<void> testClient(int limit) async {
12+
final server = await HttpServer.bind('127.0.0.1', 0);
13+
final str = 'a' * (1000);
14+
int size = 0;
15+
server.listen((request) async {
16+
for (int i = 0; i < 10000; i++) {
17+
request.response.headers.add('dummy', str);
18+
size += 1000;
19+
if (size > limit) {
20+
break;
21+
}
22+
}
23+
await request.response.close();
24+
server.close();
25+
});
26+
27+
final client = HttpClient();
28+
final request = await client.get('127.0.0.1', server.port, '/');
29+
await request.close();
30+
}
31+
32+
Future<void> client() async {
33+
int i = 64;
34+
try {
35+
for (; i < 101 * 1024 * 1024; i *= 100) {
36+
await testClient(i);
37+
}
38+
} on HttpException catch (e) {
39+
Expect.isTrue(e.toString().contains('size limit'));
40+
Expect.isTrue(i > 1024 * 1024);
41+
return;
42+
}
43+
Expect.fail('An exception is expected');
44+
}
45+
46+
Future<void> testServer(int limit, int port) async {
47+
final str = 'a' * (1000);
48+
final client = HttpClient();
49+
final request = await client.get('127.0.0.1', port, '/');
50+
for (int size = 0; size < limit; size += 1000) {
51+
request.headers.add('dummy', str);
52+
}
53+
await request.close();
54+
}
55+
56+
Future<void> server() async {
57+
final server = await HttpServer.bind('127.0.0.1', 0);
58+
int i = 64;
59+
try {
60+
server.listen((request) async {
61+
await request.response.close();
62+
});
63+
for (; i < 101 * 1024 * 1024; i *= 100) {
64+
print(i);
65+
await testServer(i, server.port);
66+
}
67+
} on SocketException catch (_) {
68+
// Server will close on error and writing to the socket will be blocked due
69+
// to broken pipe.
70+
Expect.isTrue(i > 1024 * 1024);
71+
server.close();
72+
return;
73+
}
74+
server.close();
75+
Expect.fail('An exception is expected');
76+
}
77+
78+
Future<void> main() async {
79+
await client();
80+
await server();
81+
}

0 commit comments

Comments
 (0)