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