From a6db069971796cf793976c07875ff447ee4a15da Mon Sep 17 00:00:00 2001 From: Zichang Guo Date: Tue, 1 Sep 2020 22:34:21 +0000 Subject: [PATCH] [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: https://github.com/flutter/flutter/issues/56580 Change-Id: I5f094df32a93ec3e6645a0d69d8cf8263082775a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150500 Commit-Queue: Zichang Guo Reviewed-by: Jonas Termansen --- CHANGELOG.md | 4 + sdk/lib/_http/http_parser.dart | 15 ++-- tests/standalone/io/http_big_header_test.dart | 81 +++++++++++++++++++ .../standalone_2/io/http_big_header_test.dart | 81 +++++++++++++++++++ 4 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 tests/standalone/io/http_big_header_test.dart create mode 100644 tests/standalone_2/io/http_big_header_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 817279d1fc9..011e3ca6426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ to cancel outgoing HTTP requests and stop following IO operations. * A validation check is added to `path` of class `Cookie`. Having characters ranging from 0x00 to 0x1f and 0x3b (";") will lead to a `FormatException`. +* 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. #### `dart:typed_data` diff --git a/sdk/lib/_http/http_parser.dart b/sdk/lib/_http/http_parser.dart index cb13b247880..23e4a2b844f 100644 --- a/sdk/lib/_http/http_parser.dart +++ b/sdk/lib/_http/http_parser.dart @@ -236,6 +236,7 @@ class _HttpParser extends Stream<_HttpIncoming> { Uint8List? _buffer; int _index = -1; + // Whether a HTTP request is being parsed (as opposed to a response). final bool _requestParser; int _state = _State.START; int? _httpVersionIndex; @@ -246,8 +247,8 @@ class _HttpParser extends Stream<_HttpIncoming> { final List _uriOrReasonPhrase = []; final List _headerField = []; final List _headerValue = []; - // The limit for method, uriOrReasonPhrase, header field and value - int _headerSizeLimit = 8 * 1024; + static const _headerTotalSizeLimit = 1024 * 1024; + int _headersReceivedSize = 0; int _httpVersion = _HttpVersion.UNDETERMINED; int _transferLength = -1; @@ -946,6 +947,7 @@ class _HttpParser extends Stream<_HttpIncoming> { _messageType = _MessageType.UNDETERMINED; _headerField.clear(); _headerValue.clear(); + _headersReceivedSize = 0; _method.clear(); _uriOrReasonPhrase.clear(); @@ -977,8 +979,7 @@ class _HttpParser extends Stream<_HttpIncoming> { } static bool _isValueChar(int byte) { - return (byte > 31 && byte < 128) || - (byte == _CharCode.HT); + return (byte > 31 && byte < 128) || (byte == _CharCode.HT); } static List _tokenizeFieldValue(String headerValue) { @@ -1036,7 +1037,8 @@ class _HttpParser extends Stream<_HttpIncoming> { } void _addWithValidation(List list, int byte) { - if (list.length < _headerSizeLimit) { + _headersReceivedSize++; + if (_headersReceivedSize < _headerTotalSizeLimit) { list.add(byte); } else { _reportSizeLimitError(); @@ -1074,7 +1076,8 @@ class _HttpParser extends Stream<_HttpIncoming> { throw UnsupportedError("Unexpected state: $_state"); break; } - throw HttpException("$method exceeds the $_headerSizeLimit size limit"); + throw HttpException( + "$method exceeds the $_headerTotalSizeLimit size limit"); } _HttpIncoming _createIncoming(int transferLength) { diff --git a/tests/standalone/io/http_big_header_test.dart b/tests/standalone/io/http_big_header_test.dart new file mode 100644 index 00000000000..e5d25fbcb8f --- /dev/null +++ b/tests/standalone/io/http_big_header_test.dart @@ -0,0 +1,81 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:expect/expect.dart'; + +// This test checks whether a big header is accepted. + +Future testClient(int limit) async { + final server = await HttpServer.bind('127.0.0.1', 0); + final str = 'a' * (1000); + int size = 0; + server.listen((request) async { + for (int i = 0; i < 10000; i++) { + request.response.headers.add('dummy', str); + size += 1000; + if (size > limit) { + break; + } + } + await request.response.close(); + server.close(); + }); + + final client = HttpClient(); + final request = await client.get('127.0.0.1', server.port, '/'); + await request.close(); +} + +Future client() async { + int i = 64; + try { + for (; i < 101 * 1024 * 1024; i *= 100) { + await testClient(i); + } + } on HttpException catch (e) { + Expect.isTrue(e.toString().contains('size limit')); + Expect.isTrue(i > 1024 * 1024); + return; + } + Expect.fail('An exception is expected'); +} + +Future testServer(int limit, int port) async { + final str = 'a' * (1000); + final client = HttpClient(); + final request = await client.get('127.0.0.1', port, '/'); + for (int size = 0; size < limit; size += 1000) { + request.headers.add('dummy', str); + } + await request.close(); +} + +Future server() async { + final server = await HttpServer.bind('127.0.0.1', 0); + int i = 64; + try { + server.listen((request) async { + await request.response.close(); + }); + for (; i < 101 * 1024 * 1024; i *= 100) { + print(i); + await testServer(i, server.port); + } + } on SocketException catch (_) { + // Server will close on error and writing to the socket will be blocked due + // to broken pipe. + Expect.isTrue(i > 1024 * 1024); + server.close(); + return; + } + server.close(); + Expect.fail('An exception is expected'); +} + +Future main() async { + await client(); + await server(); +} diff --git a/tests/standalone_2/io/http_big_header_test.dart b/tests/standalone_2/io/http_big_header_test.dart new file mode 100644 index 00000000000..e5d25fbcb8f --- /dev/null +++ b/tests/standalone_2/io/http_big_header_test.dart @@ -0,0 +1,81 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:expect/expect.dart'; + +// This test checks whether a big header is accepted. + +Future testClient(int limit) async { + final server = await HttpServer.bind('127.0.0.1', 0); + final str = 'a' * (1000); + int size = 0; + server.listen((request) async { + for (int i = 0; i < 10000; i++) { + request.response.headers.add('dummy', str); + size += 1000; + if (size > limit) { + break; + } + } + await request.response.close(); + server.close(); + }); + + final client = HttpClient(); + final request = await client.get('127.0.0.1', server.port, '/'); + await request.close(); +} + +Future client() async { + int i = 64; + try { + for (; i < 101 * 1024 * 1024; i *= 100) { + await testClient(i); + } + } on HttpException catch (e) { + Expect.isTrue(e.toString().contains('size limit')); + Expect.isTrue(i > 1024 * 1024); + return; + } + Expect.fail('An exception is expected'); +} + +Future testServer(int limit, int port) async { + final str = 'a' * (1000); + final client = HttpClient(); + final request = await client.get('127.0.0.1', port, '/'); + for (int size = 0; size < limit; size += 1000) { + request.headers.add('dummy', str); + } + await request.close(); +} + +Future server() async { + final server = await HttpServer.bind('127.0.0.1', 0); + int i = 64; + try { + server.listen((request) async { + await request.response.close(); + }); + for (; i < 101 * 1024 * 1024; i *= 100) { + print(i); + await testServer(i, server.port); + } + } on SocketException catch (_) { + // Server will close on error and writing to the socket will be blocked due + // to broken pipe. + Expect.isTrue(i > 1024 * 1024); + server.close(); + return; + } + server.close(); + Expect.fail('An exception is expected'); +} + +Future main() async { + await client(); + await server(); +}