Version 2.13.0-155.0.dev
Merge commit 'f5432d7164ca0deb1747faf910080551fd3fe280' into 'dev'
diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
index 0a48da5..906a9ee 100644
--- a/runtime/vm/timeline.cc
+++ b/runtime/vm/timeline.cc
@@ -225,26 +225,30 @@
void Timeline::Cleanup() {
ASSERT(recorder_lock_ != nullptr);
- MutexLocker ml(recorder_lock_);
- ASSERT(recorder_ != NULL);
+ TimelineEventRecorder* temp = recorder_;
+ {
+ MutexLocker ml(recorder_lock_);
+ ASSERT(recorder_ != NULL);
#ifndef PRODUCT
- if (FLAG_timeline_dir != NULL) {
- recorder_->WriteTo(FLAG_timeline_dir);
- }
+ if (FLAG_timeline_dir != NULL) {
+ recorder_->WriteTo(FLAG_timeline_dir);
+ }
#endif
-// Disable global streams.
+ // Disable global streams.
#define TIMELINE_STREAM_DISABLE(name, fuchsia_name) \
Timeline::stream_##name##_.set_enabled(false);
- TIMELINE_STREAM_LIST(TIMELINE_STREAM_DISABLE)
+ TIMELINE_STREAM_LIST(TIMELINE_STREAM_DISABLE)
#undef TIMELINE_STREAM_DISABLE
- delete recorder_;
- recorder_ = NULL;
- if (enabled_streams_ != NULL) {
- FreeEnabledByDefaultTimelineStreams(enabled_streams_);
- enabled_streams_ = NULL;
+ recorder_ = NULL;
+ if (enabled_streams_ != NULL) {
+ FreeEnabledByDefaultTimelineStreams(enabled_streams_);
+ enabled_streams_ = NULL;
+ }
}
+ // We need to release the recorder lock to finish cleanup.
+ delete temp;
}
TimelineEventRecorder* Timeline::recorder() {
@@ -258,23 +262,23 @@
}
void Timeline::ReclaimCachedBlocksFromThreadsLocked() {
+ ASSERT(recorder_lock_->IsOwnedByCurrentThread());
TimelineEventRecorder* recorder = Timeline::recorder();
- if (recorder == NULL) {
- return;
- }
-
// Iterate over threads.
OSThreadIterator it;
while (it.HasNext()) {
OSThread* thread = it.Next();
+ // TODO(johnmccutchan): Consider dropping the timeline_block_lock here
+ // if we can do it everywhere. This would simplify the lock ordering
+ // requirements.
+
MutexLocker ml(thread->timeline_block_lock());
// Grab block and clear it.
TimelineEventBlock* block = thread->timeline_block();
thread->set_timeline_block(NULL);
- // TODO(johnmccutchan): Consider dropping the timeline_block_lock here
- // if we can do it everywhere. This would simplify the lock ordering
- // requirements.
- recorder->FinishBlock(block);
+ if (recorder != nullptr) {
+ recorder->FinishBlock(block);
+ }
}
}
@@ -1361,15 +1365,9 @@
: head_(nullptr), tail_(nullptr), block_index_(0) {}
TimelineEventEndlessRecorder::~TimelineEventEndlessRecorder() {
- MutexLocker ml(&lock_);
- TimelineEventBlock* current = head_;
- head_ = tail_ = nullptr;
-
- while (current != nullptr) {
- TimelineEventBlock* next = current->next();
- delete current;
- current = next;
- }
+ // Ensures block state is cleared for each thread.
+ Timeline::ReclaimCachedBlocksFromThreads();
+ Clear();
}
#ifndef PRODUCT
@@ -1452,9 +1450,7 @@
#endif
void TimelineEventEndlessRecorder::Clear() {
- OSThread* thread = OSThread::Current();
- MutexLocker ml(thread->timeline_block_lock());
- MutexLocker ml2(&lock_);
+ MutexLocker ml(&lock_);
TimelineEventBlock* current = head_;
while (current != NULL) {
TimelineEventBlock* next = current->next();
@@ -1464,7 +1460,6 @@
head_ = NULL;
tail_ = NULL;
block_index_ = 0;
- thread->set_timeline_block(NULL);
}
TimelineEventBlock::TimelineEventBlock(intptr_t block_index)
diff --git a/runtime/vm/timeline_test.cc b/runtime/vm/timeline_test.cc
index 70d162e..ac1ce05 100644
--- a/runtime/vm/timeline_test.cc
+++ b/runtime/vm/timeline_test.cc
@@ -90,11 +90,6 @@
event->Complete();
}
- static void Clear(TimelineEventRecorder* recorder) {
- ASSERT(recorder != NULL);
- recorder->Clear();
- }
-
static void FinishBlock(TimelineEventBlock* block) { block->Finish(); }
};
@@ -458,7 +453,6 @@
EXPECT(!it.HasNext());
}
- TimelineTestHelper::Clear(recorder);
delete recorder;
}
@@ -495,7 +489,6 @@
const char* beta = strstr(js.ToCString(), "Beta");
EXPECT(alpha < beta);
- TimelineTestHelper::Clear(recorder);
delete recorder;
}
@@ -520,8 +513,8 @@
EXPECT_EQ(10, pauses.MaxInclusiveTime("a"));
EXPECT_EQ(10, pauses.MaxExclusiveTime("a"));
}
- TimelineTestHelper::Clear(recorder);
-
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeDuration(recorder, "a", 0, 10);
TimelineTestHelper::FakeDuration(recorder, "b", 0, 10);
@@ -539,7 +532,9 @@
EXPECT_EQ(10, pauses.MaxInclusiveTime("b"));
EXPECT_EQ(10, pauses.MaxExclusiveTime("b"));
}
- TimelineTestHelper::Clear(recorder);
+
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeDuration(recorder, "a", 0, 10);
@@ -558,7 +553,8 @@
EXPECT_EQ(7, pauses.MaxInclusiveTime("b"));
EXPECT_EQ(7, pauses.MaxExclusiveTime("b"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeDuration(recorder, "a", 0, 10);
@@ -586,7 +582,8 @@
EXPECT_EQ(1, pauses.MaxInclusiveTime("b"));
EXPECT_EQ(1, pauses.MaxExclusiveTime("b"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeDuration(recorder, "a", 0, 10);
@@ -616,7 +613,8 @@
EXPECT_EQ(5, pauses.MaxInclusiveTime("d"));
EXPECT_EQ(5, pauses.MaxExclusiveTime("d"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeDuration(recorder, "a", 0, 10);
@@ -651,7 +649,8 @@
EXPECT_EQ(2, pauses.MaxInclusiveTime("e"));
EXPECT_EQ(2, pauses.MaxExclusiveTime("e"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeDuration(recorder, "a", 0, 10);
@@ -667,8 +666,6 @@
EXPECT_EQ(10, pauses.MaxInclusiveTime("a"));
EXPECT_EQ(8, pauses.MaxExclusiveTime("a"));
}
- TimelineTestHelper::Clear(recorder);
-
delete recorder;
}
@@ -695,7 +692,8 @@
EXPECT_EQ(10, pauses.MaxInclusiveTime("a"));
EXPECT_EQ(10, pauses.MaxExclusiveTime("a"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeBegin(recorder, "a", 0);
@@ -717,7 +715,8 @@
EXPECT_EQ(10, pauses.MaxInclusiveTime("b"));
EXPECT_EQ(10, pauses.MaxExclusiveTime("b"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeBegin(recorder, "a", 0);
@@ -739,7 +738,8 @@
EXPECT_EQ(7, pauses.MaxInclusiveTime("b"));
EXPECT_EQ(7, pauses.MaxExclusiveTime("b"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeBegin(recorder, "a", 0);
@@ -772,7 +772,8 @@
EXPECT_EQ(1, pauses.MaxInclusiveTime("b"));
EXPECT_EQ(1, pauses.MaxExclusiveTime("b"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeBegin(recorder, "a", 0);
@@ -806,7 +807,8 @@
EXPECT_EQ(5, pauses.MaxInclusiveTime("d"));
EXPECT_EQ(5, pauses.MaxExclusiveTime("d"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeBegin(recorder, "a", 0);
@@ -846,7 +848,8 @@
EXPECT_EQ(2, pauses.MaxInclusiveTime("e"));
EXPECT_EQ(2, pauses.MaxExclusiveTime("e"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeBegin(recorder, "a", 0);
@@ -864,7 +867,8 @@
EXPECT_EQ(10, pauses.MaxInclusiveTime("a"));
EXPECT_EQ(8, pauses.MaxExclusiveTime("a"));
}
- TimelineTestHelper::Clear(recorder);
+ delete recorder;
+ recorder = new TimelineEventEndlessRecorder();
// Test case.
TimelineTestHelper::FakeBegin(recorder, "a", 0);
@@ -878,8 +882,6 @@
pauses.CalculatePauseTimesForThread(tid);
EXPECT(pauses.has_error());
}
- TimelineTestHelper::Clear(recorder);
-
delete recorder;
}
diff --git a/sdk/lib/_http/http_parser.dart b/sdk/lib/_http/http_parser.dart
index 93b6518..060291c 100644
--- a/sdk/lib/_http/http_parser.dart
+++ b/sdk/lib/_http/http_parser.dart
@@ -66,17 +66,17 @@
static const int HEADER_FIELD = 11;
static const int HEADER_VALUE_START = 12;
static const int HEADER_VALUE = 13;
- static const int HEADER_VALUE_FOLDING_OR_ENDING = 14;
+ static const int HEADER_VALUE_FOLD_OR_END_CR = 14;
static const int HEADER_VALUE_FOLD_OR_END = 15;
static const int HEADER_ENDING = 16;
static const int CHUNK_SIZE_STARTING_CR = 17;
- static const int CHUNK_SIZE_STARTING_LF = 18;
+ static const int CHUNK_SIZE_STARTING = 18;
static const int CHUNK_SIZE = 19;
static const int CHUNK_SIZE_EXTENSION = 20;
static const int CHUNK_SIZE_ENDING = 21;
static const int CHUNKED_BODY_DONE_CR = 22;
- static const int CHUNKED_BODY_DONE_LF = 23;
+ static const int CHUNKED_BODY_DONE = 23;
static const int BODY = 24;
static const int CLOSED = 25;
static const int UPGRADED = 26;
@@ -418,6 +418,10 @@
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
// Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
// message-header = field-name ":" [ field-value ]
+ //
+ // Per section 19.3 "Tolerant Applications" CRLF treats LF as a terminator
+ // and leading CR is ignored. Use of standalone CR is not allowed.
+
void _doParse() {
assert(!_parserCalled);
_parserCalled = true;
@@ -570,10 +574,9 @@
} else {
if (byte == _CharCode.CR) {
_state = _State.REQUEST_LINE_ENDING;
- } else {
- _expect(byte, _CharCode.LF);
- _messageType = _MessageType.REQUEST;
- _state = _State.HEADER_START;
+ } else if (byte == _CharCode.LF) {
+ _state = _State.REQUEST_LINE_ENDING;
+ _index = _index - 1; // Make the new state see the LF again.
}
}
break;
@@ -588,9 +591,12 @@
if (byte == _CharCode.SP) {
_state = _State.RESPONSE_LINE_REASON_PHRASE;
} else if (byte == _CharCode.CR) {
- // Some HTTP servers does not follow the spec. and send
- // \r\n right after the status code.
+ // Some HTTP servers do not follow the spec and send
+ // \r?\n right after the status code.
_state = _State.RESPONSE_LINE_ENDING;
+ } else if (byte == _CharCode.LF) {
+ _state = _State.RESPONSE_LINE_ENDING;
+ _index = _index - 1; // Make the new state see the LF again.
} else {
_statusCodeLength++;
if (byte < 0x30 || byte > 0x39) {
@@ -607,11 +613,10 @@
case _State.RESPONSE_LINE_REASON_PHRASE:
if (byte == _CharCode.CR) {
_state = _State.RESPONSE_LINE_ENDING;
+ } else if (byte == _CharCode.LF) {
+ _state = _State.RESPONSE_LINE_ENDING;
+ _index = _index - 1; // Make the new state see the LF again.
} else {
- if (byte == _CharCode.CR || byte == _CharCode.LF) {
- throw HttpException(
- "Invalid response, unexpected $byte in reason phrase");
- }
_addWithValidation(_uriOrReasonPhrase, byte);
}
break;
@@ -653,7 +658,7 @@
case _State.HEADER_VALUE_START:
if (byte == _CharCode.CR) {
- _state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
+ _state = _State.HEADER_VALUE_FOLD_OR_END_CR;
} else if (byte == _CharCode.LF) {
_state = _State.HEADER_VALUE_FOLD_OR_END;
} else if (byte != _CharCode.SP && byte != _CharCode.HT) {
@@ -665,7 +670,7 @@
case _State.HEADER_VALUE:
if (byte == _CharCode.CR) {
- _state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
+ _state = _State.HEADER_VALUE_FOLD_OR_END_CR;
} else if (byte == _CharCode.LF) {
_state = _State.HEADER_VALUE_FOLD_OR_END;
} else {
@@ -673,7 +678,7 @@
}
break;
- case _State.HEADER_VALUE_FOLDING_OR_ENDING:
+ case _State.HEADER_VALUE_FOLD_OR_END_CR:
_expect(byte, _CharCode.LF);
_state = _State.HEADER_VALUE_FOLD_OR_END;
break;
@@ -748,11 +753,16 @@
break;
case _State.CHUNK_SIZE_STARTING_CR:
+ if (byte == _CharCode.LF) {
+ _state = _State.CHUNK_SIZE_STARTING;
+ _index = _index - 1; // Make the new state see the LF again.
+ break;
+ }
_expect(byte, _CharCode.CR);
- _state = _State.CHUNK_SIZE_STARTING_LF;
+ _state = _State.CHUNK_SIZE_STARTING;
break;
- case _State.CHUNK_SIZE_STARTING_LF:
+ case _State.CHUNK_SIZE_STARTING:
_expect(byte, _CharCode.LF);
_state = _State.CHUNK_SIZE;
break;
@@ -760,6 +770,9 @@
case _State.CHUNK_SIZE:
if (byte == _CharCode.CR) {
_state = _State.CHUNK_SIZE_ENDING;
+ } else if (byte == _CharCode.LF) {
+ _state = _State.CHUNK_SIZE_ENDING;
+ _index = _index - 1; // Make the new state see the LF again.
} else if (byte == _CharCode.SEMI_COLON) {
_state = _State.CHUNK_SIZE_EXTENSION;
} else {
@@ -775,6 +788,9 @@
case _State.CHUNK_SIZE_EXTENSION:
if (byte == _CharCode.CR) {
_state = _State.CHUNK_SIZE_ENDING;
+ } else if (byte == _CharCode.LF) {
+ _state = _State.CHUNK_SIZE_ENDING;
+ _index = _index - 1; // Make the new state see the LF again.
}
break;
@@ -788,11 +804,15 @@
break;
case _State.CHUNKED_BODY_DONE_CR:
+ if (byte == _CharCode.LF) {
+ _state = _State.CHUNKED_BODY_DONE;
+ _index = _index - 1; // Make the new state see the LF again.
+ break;
+ }
_expect(byte, _CharCode.CR);
- _state = _State.CHUNKED_BODY_DONE_LF;
break;
- case _State.CHUNKED_BODY_DONE_LF:
+ case _State.CHUNKED_BODY_DONE:
_expect(byte, _CharCode.LF);
_reset();
_closeIncoming();
diff --git a/tests/standalone/io/http_100_continue.dart b/tests/standalone/io/http_100_continue_test.dart
similarity index 90%
rename from tests/standalone/io/http_100_continue.dart
rename to tests/standalone/io/http_100_continue_test.dart
index f0e18a5..61af598 100644
--- a/tests/standalone/io/http_100_continue.dart
+++ b/tests/standalone/io/http_100_continue_test.dart
@@ -70,4 +70,8 @@
test(ascii.encode(r1), 0);
test(ascii.encode(r2), 0);
test(ascii.encode(r3), 2);
+
+ test(ascii.encode(r1.replaceAll('\r\n', '\n')), 0);
+ test(ascii.encode(r2.replaceAll('\r\n', '\n')), 0);
+ test(ascii.encode(r3.replaceAll('\r\n', '\n')), 2);
}
diff --git a/tests/standalone/io/http_client_parser_crlfs_tolerant_test.dart b/tests/standalone/io/http_client_parser_crlfs_tolerant_test.dart
new file mode 100644
index 0000000..1d02c39
--- /dev/null
+++ b/tests/standalone/io/http_client_parser_crlfs_tolerant_test.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2021, 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.
+//
+// Tests that CR*LF sequence works as well as CRLF in http client parser.
+
+import "package:expect/expect.dart";
+import "package:async_helper/async_helper.dart";
+
+import "dart:async";
+import "dart:convert";
+import "dart:io";
+import "dart:isolate";
+
+Future testHttpClient(header) {
+ final completer = Completer();
+ ServerSocket.bind("127.0.0.1", 0).then((server) async {
+ server.listen((socket) async {
+ int port = server.port;
+ socket.write(header);
+ await socket.flush();
+ completer.future.catchError((_) {}).whenComplete(() {
+ socket.destroy();
+ });
+ });
+
+ await runZonedGuarded(() {
+ var client = new HttpClient();
+ client.userAgent = null;
+ client
+ .get("127.0.0.1", server.port, "/")
+ .then((request) => request.close())
+ .then((response) {
+ response.transform(utf8.decoder).listen((contents) {
+ completer.complete();
+ }, onDone: () {
+ client.close(force: true);
+ server.close();
+ });
+ });
+ }, (e, st) {
+ server.close();
+ completer.completeError(e, st);
+ });
+ });
+ return completer.future;
+}
+
+void main() async {
+ const good = <String>[
+ "HTTP/1.1 200 OK\n\nTest!",
+ "HTTP/1.1 200 OK\r\n\nTest!",
+ "HTTP/1.1 200 OK\n\r\nTest!",
+ "HTTP/1.1 200 OK\r\n\r\nTest!",
+ ];
+ asyncStart();
+ for (final header in good) {
+ await testHttpClient(header);
+ }
+ const bad = <String>[
+ "HTTP/1.1 200 OK\n\rTest!",
+ "HTTP/1.1 200 OK\r\r\n\nTest!",
+ "HTTP/1.1 200 OK\r\rTest!",
+ "HTTP/1.1 200 OK\rTest!",
+ ];
+ for (final header in bad) {
+ var caught;
+ try {
+ await testHttpClient(header);
+ } catch (e, st) {
+ caught = e;
+ }
+ Expect.isTrue(caught is HttpException);
+ }
+ asyncEnd();
+}
diff --git a/tests/standalone/io/http_parser_test.dart b/tests/standalone/io/http_parser_test.dart
index 022a9b9..c689a0c 100644
--- a/tests/standalone/io/http_parser_test.dart
+++ b/tests/standalone/io/http_parser_test.dart
@@ -26,14 +26,25 @@
part "../../../sdk/lib/_http/http_session.dart";
class HttpParserTest {
+ final String Function(String) transform;
+ HttpParserTest(this.transform);
+
static void runAllTests() {
- testParseRequest();
- testParseResponse();
- testParseInvalidRequest();
- testParseInvalidResponse();
+ final testCRLF = HttpParserTest((String s) => s);
+ testCRLF.testParseRequest();
+ testCRLF.testParseResponse();
+ testCRLF.testParseInvalidRequest();
+ testCRLF.testParseInvalidResponse();
+
+ // Ensure http parser is CR?LF tolerant.
+ final testLF = HttpParserTest((String s) => s.replaceAll('\r', ''));
+ testLF.testParseRequest();
+ testLF.testParseResponse();
+ testLF.testParseInvalidRequest();
+ testLF.testParseInvalidResponse();
}
- static void _testParseRequest(
+ void _testParseRequest(
String request, String expectedMethod, String expectedUri,
{int expectedTransferLength: 0,
int expectedBytesReceived: 0,
@@ -118,13 +129,14 @@
// Test parsing the request three times delivering the data in
// different chunks.
- List<int> requestData = new Uint8List.fromList(request.codeUnits);
+ List<int> requestData =
+ new Uint8List.fromList(transform(request).codeUnits);
testWrite(requestData);
testWrite(requestData, 10);
testWrite(requestData, 1);
}
- static void _testParseRequestLean(
+ void _testParseRequestLean(
String request, String expectedMethod, String expectedUri,
{int expectedTransferLength: 0,
int expectedBytesReceived: 0,
@@ -155,7 +167,7 @@
expectedVersion: expectedVersion);
}
- static void _testParseInvalidRequest(String request) {
+ void _testParseInvalidRequest(String request) {
_HttpParser httpParser;
bool errorCalled = false;
late StreamController<Uint8List> controller;
@@ -192,13 +204,14 @@
// Test parsing the request three times delivering the data in
// different chunks.
- List<int> requestData = new Uint8List.fromList(request.codeUnits);
+ List<int> requestData =
+ new Uint8List.fromList(transform(request).codeUnits);
testWrite(requestData);
testWrite(requestData, 10);
testWrite(requestData, 1);
}
- static void _testParseResponse(
+ void _testParseResponse(
String response, int expectedStatusCode, String expectedReasonPhrase,
{int expectedTransferLength: 0,
int expectedBytesReceived: 0,
@@ -286,13 +299,14 @@
// Test parsing the request three times delivering the data in
// different chunks.
- List<int> responseData = new Uint8List.fromList(response.codeUnits);
+ List<int> responseData =
+ new Uint8List.fromList(transform(response).codeUnits);
testWrite(responseData);
testWrite(responseData, 10);
testWrite(responseData, 1);
}
- static void _testParseInvalidResponse(String response, [bool close = false]) {
+ void _testParseInvalidResponse(String response, [bool close = false]) {
void testWrite(List<int> requestData, [int chunkSize = -1]) {
_HttpParser httpParser = new _HttpParser.responseParser();
StreamController<Uint8List> controller = new StreamController(sync: true);
@@ -329,13 +343,14 @@
// Test parsing the request three times delivering the data in
// different chunks.
- List<int> responseData = new Uint8List.fromList(response.codeUnits);
+ List<int> responseData =
+ new Uint8List.fromList(transform(response).codeUnits);
testWrite(responseData);
testWrite(responseData, 10);
testWrite(responseData, 1);
}
- static void testParseRequest() {
+ void testParseRequest() {
String request;
Map<String, String> headers;
var methods = [
@@ -552,7 +567,7 @@
expectedHeaders: headers, upgrade: true, unparsedLength: 7);
}
- static void testParseResponse() {
+ void testParseResponse() {
String response;
Map<String, String> headers;
response = "HTTP/1.1 100 Continue\r\nContent-Length: 0\r\n\r\n";
@@ -714,7 +729,7 @@
expectedHeaders: headers, upgrade: true, unparsedLength: 4);
}
- static void testParseInvalidRequest() {
+ void testParseInvalidRequest() {
String request;
request = "GET /\r\n\r\n";
_testParseInvalidRequest(request);
@@ -770,7 +785,7 @@
_testParseInvalidRequest(request);
}
- static void testParseInvalidResponse() {
+ void testParseInvalidResponse() {
String response;
response = "HTTP/1.1\r\nContent-Length: 0\r\n\r\n";
diff --git a/tests/standalone_2/io/http_100_continue.dart b/tests/standalone_2/io/http_100_continue_test.dart
similarity index 90%
rename from tests/standalone_2/io/http_100_continue.dart
rename to tests/standalone_2/io/http_100_continue_test.dart
index b0e4116..fb7abae 100644
--- a/tests/standalone_2/io/http_100_continue.dart
+++ b/tests/standalone_2/io/http_100_continue_test.dart
@@ -70,4 +70,8 @@
test(ascii.encode(r1), 0);
test(ascii.encode(r2), 0);
test(ascii.encode(r3), 2);
+
+ test(ascii.encode(r1.replaceAll('\r\n', '\n')), 0);
+ test(ascii.encode(r2.replaceAll('\r\n', '\n')), 0);
+ test(ascii.encode(r3.replaceAll('\r\n', '\n')), 2);
}
diff --git a/tests/standalone_2/io/http_client_parser_crlfs_tolerant_test.dart b/tests/standalone_2/io/http_client_parser_crlfs_tolerant_test.dart
new file mode 100644
index 0000000..1d02c39
--- /dev/null
+++ b/tests/standalone_2/io/http_client_parser_crlfs_tolerant_test.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2021, 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.
+//
+// Tests that CR*LF sequence works as well as CRLF in http client parser.
+
+import "package:expect/expect.dart";
+import "package:async_helper/async_helper.dart";
+
+import "dart:async";
+import "dart:convert";
+import "dart:io";
+import "dart:isolate";
+
+Future testHttpClient(header) {
+ final completer = Completer();
+ ServerSocket.bind("127.0.0.1", 0).then((server) async {
+ server.listen((socket) async {
+ int port = server.port;
+ socket.write(header);
+ await socket.flush();
+ completer.future.catchError((_) {}).whenComplete(() {
+ socket.destroy();
+ });
+ });
+
+ await runZonedGuarded(() {
+ var client = new HttpClient();
+ client.userAgent = null;
+ client
+ .get("127.0.0.1", server.port, "/")
+ .then((request) => request.close())
+ .then((response) {
+ response.transform(utf8.decoder).listen((contents) {
+ completer.complete();
+ }, onDone: () {
+ client.close(force: true);
+ server.close();
+ });
+ });
+ }, (e, st) {
+ server.close();
+ completer.completeError(e, st);
+ });
+ });
+ return completer.future;
+}
+
+void main() async {
+ const good = <String>[
+ "HTTP/1.1 200 OK\n\nTest!",
+ "HTTP/1.1 200 OK\r\n\nTest!",
+ "HTTP/1.1 200 OK\n\r\nTest!",
+ "HTTP/1.1 200 OK\r\n\r\nTest!",
+ ];
+ asyncStart();
+ for (final header in good) {
+ await testHttpClient(header);
+ }
+ const bad = <String>[
+ "HTTP/1.1 200 OK\n\rTest!",
+ "HTTP/1.1 200 OK\r\r\n\nTest!",
+ "HTTP/1.1 200 OK\r\rTest!",
+ "HTTP/1.1 200 OK\rTest!",
+ ];
+ for (final header in bad) {
+ var caught;
+ try {
+ await testHttpClient(header);
+ } catch (e, st) {
+ caught = e;
+ }
+ Expect.isTrue(caught is HttpException);
+ }
+ asyncEnd();
+}
diff --git a/tools/VERSION b/tools/VERSION
index 5ed7e50..5b08c91 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 13
PATCH 0
-PRERELEASE 154
+PRERELEASE 155
PRERELEASE_PATCH 0
\ No newline at end of file