| // Copyright (c) 2013, 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:async"; | 
 | import "dart:isolate"; | 
 | import "dart:math"; | 
 | import "dart:typed_data"; | 
 | // ignore: IMPORT_INTERNAL_LIBRARY | 
 | import "dart:_http" show TestingClass$_HttpHeaders, TestingClass$_HttpParser; | 
 |  | 
 | import "package:expect/expect.dart"; | 
 |  | 
 | typedef _HttpHeaders = TestingClass$_HttpHeaders; | 
 | typedef _HttpParser = TestingClass$_HttpParser; | 
 |  | 
 | class HttpParserTest { | 
 |   final String Function(String) transform; | 
 |   HttpParserTest(this.transform); | 
 |  | 
 |   static void runAllTests() { | 
 |     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(); | 
 |   } | 
 |  | 
 |   void _testParseRequest( | 
 |     String request, | 
 |     String expectedMethod, | 
 |     String expectedUri, { | 
 |     int expectedTransferLength = 0, | 
 |     int expectedBytesReceived = 0, | 
 |     Map<String, String?>? expectedHeaders = null, | 
 |     bool chunked = false, | 
 |     bool upgrade = false, | 
 |     int unparsedLength = 0, | 
 |     bool connectionClose = false, | 
 |     String expectedVersion = "1.1", | 
 |   }) { | 
 |     late StreamController<Uint8List> controller; | 
 |     void reset() { | 
 |       _HttpParser httpParser = new _HttpParser.requestParser(); | 
 |       controller = new StreamController(sync: true); | 
 |       var port1 = new ReceivePort(); | 
 |       var port2 = new ReceivePort(); | 
 |  | 
 |       String? method = null; | 
 |       Uri? uri = null; | 
 |       _HttpHeaders? headers = null; | 
 |       int bytesReceived = 0; | 
 |       int unparsedBytesReceived = 0; | 
 |       bool upgraded = false; | 
 |  | 
 |       httpParser.listenToStream(controller.stream); | 
 |       var subscription = httpParser.listen((incoming) { | 
 |         method = incoming.method; | 
 |         uri = incoming.uri; | 
 |         headers = incoming.headers; | 
 |         upgraded = incoming.upgraded; | 
 |         Expect.equals(upgrade, upgraded); | 
 |  | 
 |         if (!chunked) { | 
 |           Expect.equals(expectedTransferLength, incoming.transferLength); | 
 |         } else { | 
 |           Expect.equals(-1, incoming.transferLength); | 
 |         } | 
 |         if (expectedHeaders != null) { | 
 |           expectedHeaders.forEach( | 
 |             (String name, String? value) => | 
 |                 Expect.equals(value, headers?[name]?[0]), | 
 |           ); | 
 |         } | 
 |         incoming.listen( | 
 |           (List<int> data) { | 
 |             Expect.isFalse(upgraded); | 
 |             bytesReceived += data.length; | 
 |           }, | 
 |           onDone: () { | 
 |             port2.close(); | 
 |             Expect.equals(expectedMethod, method); | 
 |             Expect.stringEquals(expectedUri, uri.toString()); | 
 |             Expect.equals(expectedVersion, headers!.protocolVersion); | 
 |             if (upgrade) { | 
 |               Expect.equals(0, bytesReceived); | 
 |               // port1 is closed by the listener on the detached data. | 
 |             } else { | 
 |               Expect.equals(expectedBytesReceived, bytesReceived); | 
 |             } | 
 |           }, | 
 |         ); | 
 |  | 
 |         if (upgraded) { | 
 |           port1.close(); | 
 |           httpParser.detachIncoming().listen( | 
 |             (List<int> data) { | 
 |               unparsedBytesReceived += data.length; | 
 |             }, | 
 |             onDone: () { | 
 |               Expect.equals(unparsedLength, unparsedBytesReceived); | 
 |               port2.close(); | 
 |             }, | 
 |           ); | 
 |         } | 
 |  | 
 |         incoming.dataDone.then((_) { | 
 |           port1.close(); | 
 |         }); | 
 |       }); | 
 |     } | 
 |  | 
 |     void testWrite(List<int> requestData, [int chunkSize = -1]) { | 
 |       if (chunkSize == -1) chunkSize = requestData.length; | 
 |       reset(); | 
 |       for (int pos = 0; pos < requestData.length; pos += chunkSize) { | 
 |         int end = min(requestData.length, pos + chunkSize); | 
 |         controller.add(requestData.sublist(pos, end) as Uint8List); | 
 |       } | 
 |       controller.close(); | 
 |     } | 
 |  | 
 |     // Test parsing the request three times delivering the data in | 
 |     // different chunks. | 
 |     List<int> requestData = new Uint8List.fromList( | 
 |       transform(request).codeUnits, | 
 |     ); | 
 |     testWrite(requestData); | 
 |     testWrite(requestData, 10); | 
 |     testWrite(requestData, 1); | 
 |   } | 
 |  | 
 |   void _testParseRequestLean( | 
 |     String request, | 
 |     String expectedMethod, | 
 |     String expectedUri, { | 
 |     int expectedTransferLength = 0, | 
 |     int expectedBytesReceived = 0, | 
 |     Map<String, String>? expectedHeaders = null, | 
 |     bool chunked = false, | 
 |     bool upgrade = false, | 
 |     int unparsedLength = 0, | 
 |     bool connectionClose = false, | 
 |     String expectedVersion = "1.1", | 
 |   }) { | 
 |     _testParseRequest( | 
 |       request, | 
 |       expectedMethod, | 
 |       expectedUri, | 
 |       expectedTransferLength: expectedTransferLength, | 
 |       expectedBytesReceived: expectedBytesReceived, | 
 |       expectedHeaders: expectedHeaders, | 
 |       chunked: chunked, | 
 |       upgrade: upgrade, | 
 |       unparsedLength: unparsedLength, | 
 |       connectionClose: connectionClose, | 
 |       expectedVersion: expectedVersion, | 
 |     ); | 
 |     // Same test but with only \n instead of \r\n terminating each header line. | 
 |     _testParseRequest( | 
 |       request.replaceAll('\r', ''), | 
 |       expectedMethod, | 
 |       expectedUri, | 
 |       expectedTransferLength: expectedTransferLength, | 
 |       expectedBytesReceived: expectedBytesReceived, | 
 |       expectedHeaders: expectedHeaders, | 
 |       chunked: chunked, | 
 |       upgrade: upgrade, | 
 |       unparsedLength: unparsedLength, | 
 |       connectionClose: connectionClose, | 
 |       expectedVersion: expectedVersion, | 
 |     ); | 
 |   } | 
 |  | 
 |   void _testParseInvalidRequest(String request) { | 
 |     _HttpParser httpParser; | 
 |     bool errorCalled = false; | 
 |     late StreamController<Uint8List> controller; | 
 |  | 
 |     void reset() { | 
 |       httpParser = new _HttpParser.requestParser(); | 
 |       controller = new StreamController(sync: true); | 
 |       var port = new ReceivePort(); | 
 |       httpParser.listenToStream(controller.stream); | 
 |       var subscription = httpParser.listen((incoming) { | 
 |         Expect.fail("Expected request"); | 
 |       }); | 
 |       subscription.onError((e) { | 
 |         errorCalled = true; | 
 |       }); | 
 |       subscription.onDone(() { | 
 |         port.close(); | 
 |         Expect.isTrue(errorCalled); | 
 |       }); | 
 |       errorCalled = false; | 
 |     } | 
 |  | 
 |     void testWrite(List<int> requestData, [int chunkSize = -1]) { | 
 |       if (chunkSize == -1) chunkSize = requestData.length; | 
 |       reset(); | 
 |       for ( | 
 |         int pos = 0; | 
 |         pos < requestData.length && !errorCalled; | 
 |         pos += chunkSize | 
 |       ) { | 
 |         int end = min(requestData.length, pos + chunkSize); | 
 |         controller.add(requestData.sublist(pos, end) as Uint8List); | 
 |       } | 
 |       controller.close(); | 
 |     } | 
 |  | 
 |     // Test parsing the request three times delivering the data in | 
 |     // different chunks. | 
 |     List<int> requestData = new Uint8List.fromList( | 
 |       transform(request).codeUnits, | 
 |     ); | 
 |     testWrite(requestData); | 
 |     testWrite(requestData, 10); | 
 |     testWrite(requestData, 1); | 
 |   } | 
 |  | 
 |   void _testParseResponse( | 
 |     String response, | 
 |     int expectedStatusCode, | 
 |     String expectedReasonPhrase, { | 
 |     int expectedTransferLength = 0, | 
 |     int expectedBytesReceived = 0, | 
 |     Map<String, String>? expectedHeaders = null, | 
 |     bool chunked = false, | 
 |     bool close = false, | 
 |     String? responseToMethod = null, | 
 |     bool connectionClose = false, | 
 |     bool upgrade = false, | 
 |     int unparsedLength = 0, | 
 |     String expectedVersion = "1.1", | 
 |   }) { | 
 |     late StreamController<Uint8List> controller; | 
 |     bool upgraded; | 
 |  | 
 |     void reset() { | 
 |       _HttpParser httpParser; | 
 |       bool headersCompleteCalled = false; | 
 |       bool dataEndCalled = false; | 
 |       bool? dataEndClose = null; | 
 |       int statusCode = -1; | 
 |       String? reasonPhrase = null; | 
 |       _HttpHeaders? headers = null; | 
 |       int bytesReceived = 0; | 
 |  | 
 |       httpParser = new _HttpParser.responseParser(); | 
 |       controller = new StreamController(sync: true); | 
 |       var port = new ReceivePort(); | 
 |       httpParser.listenToStream(controller.stream); | 
 |       int doneCallCount = 0; | 
 |       // Called when done parsing entire message and done parsing body. | 
 |       // Only executed when both are done. | 
 |       void whenDone() { | 
 |         doneCallCount++; | 
 |         if (doneCallCount < 2) return; | 
 |         Expect.equals(expectedVersion, headers!.protocolVersion); | 
 |         Expect.equals(expectedStatusCode, statusCode); | 
 |         Expect.equals(expectedReasonPhrase, reasonPhrase); | 
 |         Expect.isTrue(headersCompleteCalled); | 
 |         Expect.equals(expectedBytesReceived, bytesReceived); | 
 |         if (!upgrade) { | 
 |           Expect.isTrue(dataEndCalled); | 
 |           if (close) Expect.isTrue(dataEndClose); | 
 |           Expect.equals(dataEndClose, connectionClose); | 
 |         } | 
 |       } | 
 |  | 
 |       var subscription = httpParser.listen((incoming) { | 
 |         port.close(); | 
 |         statusCode = incoming.statusCode!; | 
 |         reasonPhrase = incoming.reasonPhrase; | 
 |         headers = incoming.headers; | 
 |         Expect.isFalse(headersCompleteCalled); | 
 |         if (!chunked && !close) { | 
 |           Expect.equals(expectedTransferLength, incoming.transferLength); | 
 |         } else { | 
 |           Expect.equals(-1, incoming.transferLength); | 
 |         } | 
 |         if (expectedHeaders != null) { | 
 |           expectedHeaders.forEach((String name, String value) { | 
 |             Expect.equals(value, headers![name]![0]); | 
 |           }); | 
 |         } | 
 |         Expect.equals(upgrade, httpParser.upgrade); | 
 |         headersCompleteCalled = true; | 
 |         incoming.listen( | 
 |           (List<int> data) { | 
 |             Expect.isTrue(headersCompleteCalled); | 
 |             bytesReceived += data.length; | 
 |           }, | 
 |           onDone: () { | 
 |             dataEndCalled = true; | 
 |             dataEndClose = close; | 
 |             whenDone(); | 
 |           }, | 
 |         ); | 
 |       }, onDone: whenDone); | 
 |     } | 
 |  | 
 |     void testWrite(List<int> requestData, [int chunkSize = -1]) { | 
 |       if (chunkSize == -1) chunkSize = requestData.length; | 
 |       reset(); | 
 |       for (int pos = 0; pos < requestData.length; pos += chunkSize) { | 
 |         int end = min(requestData.length, pos + chunkSize); | 
 |         controller.add(requestData.sublist(pos, end) as Uint8List); | 
 |       } | 
 |       if (close) controller.close(); | 
 |     } | 
 |  | 
 |     // Test parsing the request three times delivering the data in | 
 |     // different chunks. | 
 |     List<int> responseData = new Uint8List.fromList( | 
 |       transform(response).codeUnits, | 
 |     ); | 
 |     testWrite(responseData); | 
 |     testWrite(responseData, 10); | 
 |     testWrite(responseData, 1); | 
 |   } | 
 |  | 
 |   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); | 
 |       bool errorCalled = false; | 
 |  | 
 |       if (chunkSize == -1) chunkSize = requestData.length; | 
 |  | 
 |       var port = new ReceivePort(); | 
 |       httpParser.listenToStream(controller.stream); | 
 |       var subscription = httpParser.listen((incoming) { | 
 |         incoming.listen( | 
 |           (data) {}, | 
 |           onError: (e) { | 
 |             Expect.isFalse(errorCalled); | 
 |             errorCalled = true; | 
 |           }, | 
 |         ); | 
 |       }); | 
 |       subscription.onError((e) { | 
 |         Expect.isFalse(errorCalled); | 
 |         errorCalled = true; | 
 |       }); | 
 |       subscription.onDone(() { | 
 |         port.close(); | 
 |         Expect.isTrue(errorCalled); | 
 |       }); | 
 |  | 
 |       errorCalled = false; | 
 |       for ( | 
 |         int pos = 0; | 
 |         pos < requestData.length && !errorCalled; | 
 |         pos += chunkSize | 
 |       ) { | 
 |         int end = min(requestData.length, pos + chunkSize); | 
 |         controller.add(requestData.sublist(pos, end) as Uint8List); | 
 |       } | 
 |       controller.close(); | 
 |     } | 
 |  | 
 |     // Test parsing the request three times delivering the data in | 
 |     // different chunks. | 
 |     List<int> responseData = new Uint8List.fromList( | 
 |       transform(response).codeUnits, | 
 |     ); | 
 |     testWrite(responseData); | 
 |     testWrite(responseData, 10); | 
 |     testWrite(responseData, 1); | 
 |   } | 
 |  | 
 |   void testParseRequest() { | 
 |     String request; | 
 |     Map<String, String> headers; | 
 |     var methods = [ | 
 |       // RFC 2616 methods. | 
 |       "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", | 
 |       // WebDAV methods from RFC 4918. | 
 |       "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", | 
 |       // WebDAV methods from RFC 5323. | 
 |       "SEARCH", | 
 |       // Methods with HTTP prefix. | 
 |       "H", "HT", "HTT", "HTTP", "HX", "HTX", "HTTX", "HTTPX", | 
 |     ]; | 
 |     methods = ['GET']; | 
 |     methods.forEach((method) { | 
 |       request = "$method / HTTP/1.1\r\n\r\n"; | 
 |       _testParseRequestLean(request, method, "/"); | 
 |       request = "$method /index.html HTTP/1.1\r\n\r\n"; | 
 |       _testParseRequestLean(request, method, "/index.html"); | 
 |     }); | 
 |     request = "GET / HTTP/1.0\r\n\r\n"; | 
 |     _testParseRequestLean( | 
 |       request, | 
 |       "GET", | 
 |       "/", | 
 |       expectedVersion: "1.0", | 
 |       connectionClose: true, | 
 |     ); | 
 |  | 
 |     request = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"; | 
 |     _testParseRequestLean(request, "GET", "/", expectedVersion: "1.0"); | 
 |  | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | AAA: AAA\r | 
 | \r | 
 | """; | 
 |     _testParseRequestLean(request, "POST", "/test"); | 
 |  | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | \r | 
 | """; | 
 |     _testParseRequestLean(request, "POST", "/test"); | 
 |  | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Header-A: AAA  aaa\r | 
 | X-Header-B: bbb  BBB\r | 
 | \r | 
 | """; | 
 |     headers = new Map(); | 
 |     headers["header-a"] = "AAA  aaa"; | 
 |     headers["x-header-b"] = "bbb  BBB"; | 
 |     _testParseRequestLean(request, "POST", "/test", expectedHeaders: headers); | 
 |  | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Header-A:   \t AAA  aaa \t \r | 
 | X-Header-B:   \t bbb  BBB  \t \r | 
 | \r | 
 | """; | 
 |     headers = new Map(); | 
 |     headers["header-a"] = "AAA  aaa"; | 
 |     headers["x-header-b"] = "bbb  BBB"; | 
 |     _testParseRequestLean(request, "POST", "/test", expectedHeaders: headers); | 
 |  | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Empty-Header-1:\r | 
 | Empty-Header-2:\r | 
 |         \r | 
 | \r | 
 | """; | 
 |     headers = new Map(); | 
 |     headers["empty-header-1"] = ""; | 
 |     headers["empty-header-2"] = ""; | 
 |     _testParseRequestLean(request, "POST", "/test", expectedHeaders: headers); | 
 |  | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Empty-Header-1:\t  \t \r | 
 | Empty-Header-2:\t  \t \r | 
 |         \r | 
 | \r | 
 | """; | 
 |     headers = new Map(); | 
 |     headers["empty-header-1"] = ""; | 
 |     headers["empty-header-2"] = ""; | 
 |     _testParseRequestLean(request, "POST", "/test", expectedHeaders: headers); | 
 |  | 
 |     // Test folded headers. | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Header-A: h\r | 
 |  ell\r | 
 |  o\r | 
 | X-Header-B: w\r | 
 |  o\r | 
 |  r\r | 
 |  l\r | 
 |  d\r | 
 | \r | 
 | """; | 
 |  | 
 |     headers = new Map(); | 
 |     headers["header-a"] = "h ell o"; | 
 |     headers["x-header-b"] = "w o r l d"; | 
 |     _testParseRequestLean(request, "POST", "/test", expectedHeaders: headers); | 
 |  | 
 |     // Test folded headers with leading and trailing whitespace. | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Header-A: \t  h \t \r | 
 |   \t ell \t  \t \r | 
 | \to  \t \r | 
 | X-Header-B:w\r | 
 | \t\to\t\t\r | 
 | \t\tr\t\t\r | 
 | \tl \r | 
 | \td \t\r | 
 | \r | 
 | """; | 
 |  | 
 |     headers = new Map(); | 
 |     headers["header-a"] = "h \t  ell \t  \t  o"; | 
 |     headers["x-header-b"] = "w o\t\t r\t\t l  d"; | 
 |     _testParseRequestLean(request, "POST", "/test", expectedHeaders: headers); | 
 |  | 
 |     // _testParseRequestLean encodes the request as ISO-8859-1. Test that the | 
 |     // HTTP parser decodes header values as ISO-8859-1. | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | latin1:   blåbærgrød\r | 
 | \r | 
 | """; | 
 |  | 
 |     headers = new Map(); | 
 |     headers["latin1"] = "blåbærgrød"; | 
 |     _testParseRequestLean(request, "POST", "/test", expectedHeaders: headers); | 
 |  | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Content-Length: 10\r | 
 | \r | 
 | 0123456789"""; | 
 |     _testParseRequestLean( | 
 |       request, | 
 |       "POST", | 
 |       "/test", | 
 |       expectedTransferLength: 10, | 
 |       expectedBytesReceived: 10, | 
 |     ); | 
 |  | 
 |     // Test connection close header. | 
 |     request = "GET /test HTTP/1.1\r\nConnection: close\r\n\r\n"; | 
 |     _testParseRequest(request, "GET", "/test", connectionClose: true); | 
 |  | 
 |     // Test chunked encoding. | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Transfer-Encoding: chunked\r | 
 | \r | 
 | 5\r | 
 | 01234\r | 
 | 5\r | 
 | 56789\r | 
 | 0\r\n\r\n"""; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "POST", | 
 |       "/test", | 
 |       expectedTransferLength: -1, | 
 |       expectedBytesReceived: 10, | 
 |       chunked: true, | 
 |     ); | 
 |  | 
 |     // Test LWS around chunked encoding header value. | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Transfer-Encoding:   \t   chunked  \t \r | 
 | \r | 
 | 5\r | 
 | 01234\r | 
 | 5\r | 
 | 56789\r | 
 | 0\r\n\r\n"""; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "POST", | 
 |       "/test", | 
 |       expectedTransferLength: -1, | 
 |       expectedBytesReceived: 10, | 
 |       chunked: true, | 
 |     ); | 
 |  | 
 |     // Test upper and lower case hex digits in chunked encoding. | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Transfer-Encoding: chunked\r | 
 | \r | 
 | 1E\r | 
 | 012345678901234567890123456789\r | 
 | 1e\r | 
 | 012345678901234567890123456789\r | 
 | 0\r\n\r\n"""; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "POST", | 
 |       "/test", | 
 |       expectedTransferLength: -1, | 
 |       expectedBytesReceived: 60, | 
 |       chunked: true, | 
 |     ); | 
 |  | 
 |     // Test chunk extensions in chunked encoding. | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Transfer-Encoding: chunked\r | 
 | \r | 
 | 1E;xxx\r | 
 | 012345678901234567890123456789\r | 
 | 1E;yyy=zzz\r | 
 | 012345678901234567890123456789\r | 
 | 0\r\n\r\n"""; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "POST", | 
 |       "/test", | 
 |       expectedTransferLength: -1, | 
 |       expectedBytesReceived: 60, | 
 |       chunked: true, | 
 |     ); | 
 |  | 
 |     // Content-Length and "Transfer-Encoding: chunked" are specified. | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Content-Length: 10\r | 
 | Transfer-Encoding: chunked\r | 
 | \r | 
 | 5\r | 
 | 01234\r | 
 | 5\r | 
 | 56789\r | 
 | 0\r\n\r\n"""; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "POST", | 
 |       "/test", | 
 |       expectedTransferLength: -1, | 
 |       expectedBytesReceived: 10, | 
 |       chunked: true, | 
 |       expectedHeaders: {'content-length': null, 'transfer-encoding': 'chunked'}, | 
 |     ); | 
 |  | 
 |     request = """ | 
 | POST /test HTTP/1.1\r | 
 | Transfer-Encoding: chunked\r | 
 | Content-Length: 10\r | 
 | \r | 
 | 5\r | 
 | 01234\r | 
 | 5\r | 
 | 56789\r | 
 | 0\r\n\r\n"""; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "POST", | 
 |       "/test", | 
 |       expectedTransferLength: -1, | 
 |       expectedBytesReceived: 10, | 
 |       chunked: true, | 
 |       expectedHeaders: {'content-length': null, 'transfer-encoding': 'chunked'}, | 
 |     ); | 
 |  | 
 |     // Test HTTP upgrade. | 
 |     request = """ | 
 | GET /irc HTTP/1.1\r | 
 | Upgrade: irc/1.2\r | 
 | Connection: Upgrade\r | 
 | \r\n\x01\x01\x01\x01\x01\x02\x02\x02\x02\xFF"""; | 
 |     headers = new Map(); | 
 |     headers["upgrade"] = "irc/1.2"; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "GET", | 
 |       "/irc", | 
 |       expectedHeaders: headers, | 
 |       upgrade: true, | 
 |       unparsedLength: 10, | 
 |     ); | 
 |  | 
 |     // Test HTTP upgrade with protocol data. | 
 |     request = """ | 
 | GET /irc HTTP/1.1\r | 
 | Upgrade: irc/1.2\r | 
 | Connection: Upgrade\r | 
 | \r\n"""; | 
 |     headers = new Map(); | 
 |     headers["upgrade"] = "irc/1.2"; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "GET", | 
 |       "/irc", | 
 |       expectedHeaders: headers, | 
 |       upgrade: true, | 
 |     ); | 
 |  | 
 |     // Test websocket upgrade. | 
 |     request = """ | 
 | GET /chat HTTP/1.1\r | 
 | Host: server.example.com\r | 
 | Upgrade: websocket\r | 
 | Connection: Upgrade\r | 
 | Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r | 
 | Origin: http://example.com\r | 
 | Sec-WebSocket-Version: 13\r | 
 | \r\n"""; | 
 |     headers = new Map(); | 
 |     headers["host"] = "server.example.com"; | 
 |     headers["upgrade"] = "websocket"; | 
 |     headers["sec-websocket-key"] = "dGhlIHNhbXBsZSBub25jZQ=="; | 
 |     headers["origin"] = "http://example.com"; | 
 |     headers["sec-websocket-version"] = "13"; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "GET", | 
 |       "/chat", | 
 |       expectedHeaders: headers, | 
 |       upgrade: true, | 
 |     ); | 
 |  | 
 |     // Test websocket upgrade with protocol data. NOTE: When using the | 
 |     // WebSocket protocol this should never happen as the client | 
 |     // should not send protocol data before processing the request | 
 |     // part of the opening handshake. However the HTTP parser should | 
 |     // still handle this. | 
 |     request = """ | 
 | GET /chat HTTP/1.1\r | 
 | Host: server.example.com\r | 
 | Upgrade: websocket\r | 
 | Connection: Upgrade\r | 
 | Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r | 
 | Origin: http://example.com\r | 
 | Sec-WebSocket-Version: 13\r | 
 | \r\n0123456"""; | 
 |     headers = new Map(); | 
 |     headers["host"] = "server.example.com"; | 
 |     headers["upgrade"] = "websocket"; | 
 |     headers["sec-websocket-key"] = "dGhlIHNhbXBsZSBub25jZQ=="; | 
 |     headers["origin"] = "http://example.com"; | 
 |     headers["sec-websocket-version"] = "13"; | 
 |     _testParseRequest( | 
 |       request, | 
 |       "GET", | 
 |       "/chat", | 
 |       expectedHeaders: headers, | 
 |       upgrade: true, | 
 |       unparsedLength: 7, | 
 |     ); | 
 |   } | 
 |  | 
 |   void testParseResponse() { | 
 |     String response; | 
 |     Map<String, String> headers; | 
 |     response = "HTTP/1.1 100 Continue\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseResponse(response, 100, "Continue"); | 
 |  | 
 |     response = "HTTP/1.1 100 Continue\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseResponse(response, 100, "Continue"); | 
 |  | 
 |     response = "HTTP/1.1 100 Continue\r\nContent-Length: 10\r\n\r\n"; | 
 |     _testParseResponse( | 
 |       response, | 
 |       100, | 
 |       "Continue", | 
 |       expectedTransferLength: 10, | 
 |       expectedBytesReceived: 0, | 
 |     ); | 
 |  | 
 |     response = "HTTP/1.1 100 Continue\r\nContent-Length: \t  10 \t \r\n\r\n"; | 
 |     _testParseResponse( | 
 |       response, | 
 |       100, | 
 |       "Continue", | 
 |       expectedTransferLength: 10, | 
 |       expectedBytesReceived: 0, | 
 |     ); | 
 |  | 
 |     response = | 
 |         "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n" | 
 |         "Connection: Close\r\n\r\n"; | 
 |     _testParseResponse(response, 200, "OK", connectionClose: true); | 
 |  | 
 |     response = "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseResponse( | 
 |       response, | 
 |       200, | 
 |       "OK", | 
 |       expectedVersion: "1.0", | 
 |       connectionClose: true, | 
 |     ); | 
 |  | 
 |     response = | 
 |         "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n" | 
 |         "Connection: Keep-Alive\r\n\r\n"; | 
 |     _testParseResponse(response, 200, "OK", expectedVersion: "1.0"); | 
 |  | 
 |     response = "HTTP/1.1 204 No Content\r\nContent-Length: 11\r\n\r\n"; | 
 |     _testParseResponse( | 
 |       response, | 
 |       204, | 
 |       "No Content", | 
 |       expectedTransferLength: 11, | 
 |       expectedBytesReceived: 0, | 
 |     ); | 
 |  | 
 |     response = "HTTP/1.1 304 Not Modified\r\nContent-Length: 12\r\n\r\n"; | 
 |     _testParseResponse( | 
 |       response, | 
 |       304, | 
 |       "Not Modified", | 
 |       expectedTransferLength: 12, | 
 |       expectedBytesReceived: 0, | 
 |     ); | 
 |  | 
 |     response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseResponse(response, 200, "OK"); | 
 |  | 
 |     response = "HTTP/1.1 404 Not found\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseResponse(response, 404, "Not found"); | 
 |  | 
 |     response = "HTTP/1.1 500 Server error\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseResponse(response, 500, "Server error"); | 
 |  | 
 |     // Test response to HEAD request. | 
 |     response = """ | 
 | HTTP/1.1 200 OK\r | 
 | Content-Length: 20\r | 
 | Content-Type: text/html\r | 
 | \r\n"""; | 
 |     headers = new Map(); | 
 |     headers["content-length"] = "20"; | 
 |     headers["content-type"] = "text/html"; | 
 |     _testParseResponse( | 
 |       response, | 
 |       200, | 
 |       "OK", | 
 |       responseToMethod: "HEAD", | 
 |       expectedTransferLength: 20, | 
 |       expectedBytesReceived: 0, | 
 |       expectedHeaders: headers, | 
 |     ); | 
 |  | 
 |     // _testParseRequestLean encodes the request as ISO-8859-1. Test that the | 
 |     // HTTP parser decodes header values as ISO-8859-1. | 
 |     response = """ | 
 | HTTP/1.1 200 OK\r | 
 | Content-Length: 0\r | 
 | test-latin1: blåbærgrød\r | 
 | \r\n"""; | 
 |     headers = new Map(); | 
 |     headers["content-length"] = "0"; | 
 |     headers["test-latin1"] = "blåbærgrød"; | 
 |     _testParseResponse(response, 200, "OK", expectedHeaders: headers); | 
 |  | 
 |     // Test content. | 
 |     response = """ | 
 | HTTP/1.1 200 OK\r | 
 | Content-Length: 20\r | 
 | \r | 
 | 01234567890123456789"""; | 
 |     _testParseResponse( | 
 |       response, | 
 |       200, | 
 |       "OK", | 
 |       expectedTransferLength: 20, | 
 |       expectedBytesReceived: 20, | 
 |     ); | 
 |  | 
 |     // Test upper and lower case hex digits in chunked encoding. | 
 |     response = """ | 
 | HTTP/1.1 200 OK\r | 
 | Transfer-Encoding: chunked\r | 
 | \r | 
 | 1A\r | 
 | 01234567890123456789012345\r | 
 | 1f\r | 
 | 0123456789012345678901234567890\r | 
 | 0\r\n\r\n"""; | 
 |     _testParseResponse( | 
 |       response, | 
 |       200, | 
 |       "OK", | 
 |       expectedTransferLength: -1, | 
 |       expectedBytesReceived: 57, | 
 |       chunked: true, | 
 |     ); | 
 |  | 
 |     // Test connection close header. | 
 |     response = """ | 
 | HTTP/1.1 200 OK\r | 
 | Content-Length: 0\r | 
 | Connection: close\r | 
 | \r\n"""; | 
 |     _testParseResponse(response, 200, "OK", connectionClose: true); | 
 |  | 
 |     // Test HTTP response without any transfer length indications | 
 |     // where closing the connections indicates end of body. | 
 |     response = """ | 
 | HTTP/1.1 200 OK\r | 
 | \r | 
 | 01234567890123456789012345 | 
 | 0123456789012345678901234567890 | 
 | """; | 
 |     _testParseResponse( | 
 |       response, | 
 |       200, | 
 |       "OK", | 
 |       expectedTransferLength: -1, | 
 |       expectedBytesReceived: 59, | 
 |       close: true, | 
 |       connectionClose: true, | 
 |     ); | 
 |  | 
 |     // Test HTTP upgrade. | 
 |     response = """ | 
 | HTTP/1.1 101 Switching Protocols\r | 
 | Upgrade: irc/1.2\r | 
 | Connection: Upgrade\r | 
 | \r\n"""; | 
 |     headers = new Map(); | 
 |     headers["upgrade"] = "irc/1.2"; | 
 |     _testParseResponse( | 
 |       response, | 
 |       101, | 
 |       "Switching Protocols", | 
 |       expectedHeaders: headers, | 
 |       upgrade: true, | 
 |     ); | 
 |  | 
 |     // Test HTTP upgrade with protocol data. | 
 |     response = """ | 
 | HTTP/1.1 101 Switching Protocols\r | 
 | Upgrade: irc/1.2\r | 
 | Connection: Upgrade\r | 
 | \r\n\x00\x10\x20\x30\x40\x50\x60\x70\x80\x90\xA0\xB0\xC0\xD0\xE0\xF0"""; | 
 |     headers = new Map(); | 
 |     headers["upgrade"] = "irc/1.2"; | 
 |     _testParseResponse( | 
 |       response, | 
 |       101, | 
 |       "Switching Protocols", | 
 |       expectedHeaders: headers, | 
 |       upgrade: true, | 
 |       unparsedLength: 16, | 
 |     ); | 
 |  | 
 |     // Test websocket upgrade. | 
 |     response = """ | 
 | HTTP/1.1 101 Switching Protocols\r | 
 | Upgrade: websocket\r | 
 | Connection: Upgrade\r | 
 | Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r | 
 | \r\n"""; | 
 |     headers = new Map(); | 
 |     headers["upgrade"] = "websocket"; | 
 |     headers["sec-websocket-accept"] = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="; | 
 |     _testParseResponse( | 
 |       response, | 
 |       101, | 
 |       "Switching Protocols", | 
 |       expectedHeaders: headers, | 
 |       upgrade: true, | 
 |     ); | 
 |  | 
 |     // Test websocket upgrade with protocol data. | 
 |     response = """ | 
 | HTTP/1.1 101 Switching Protocols\r | 
 | Upgrade: websocket\r | 
 | Connection: Upgrade\r | 
 | Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r | 
 | \r\nABCD"""; | 
 |     headers = new Map(); | 
 |     headers["upgrade"] = "websocket"; | 
 |     headers["sec-websocket-accept"] = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="; | 
 |     _testParseResponse( | 
 |       response, | 
 |       101, | 
 |       "Switching Protocols", | 
 |       expectedHeaders: headers, | 
 |       upgrade: true, | 
 |       unparsedLength: 4, | 
 |     ); | 
 |   } | 
 |  | 
 |   void testParseInvalidRequest() { | 
 |     String request; | 
 |     request = "GET /\r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |  | 
 |     request = "GET / \r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |  | 
 |     request = "/ HTTP/1.1\r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |  | 
 |     request = "GET HTTP/1.1\r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |  | 
 |     request = " / HTTP/1.1\r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |  | 
 |     request = "@ / HTTP/1.1\r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |  | 
 |     request = "GET / TTP/1.1\r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |  | 
 |     request = "GET / HTTP/1.\r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |  | 
 |     request = "GET / HTTP/1.1\r\nKeep-Alive: False\r\nbadheader\r\n\r\n"; | 
 |     _testParseInvalidRequest(request); | 
 |   } | 
 |  | 
 |   void testParseInvalidResponse() { | 
 |     String response; | 
 |  | 
 |     response = "HTTP/1.1\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 \r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 200\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 200 \r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 OK\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 20A OK\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "200 OK\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1. 200 OK\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 200 O\rK\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 000 OK\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 999 Server Error\r\nContent-Length: 0\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 200 OK\r\nContent-Length: x\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 200 OK\r\nbadheader\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 200 OK\r\nbadvalue: \x00\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = "HTTP/1.1 200 OK\r\nbadvalue: bad\x00value\r\n\r\n"; | 
 |     _testParseInvalidResponse(response); | 
 |  | 
 |     response = """ | 
 | HTTP/1.1 200 OK\r | 
 | Transfer-Encoding: chunked\r | 
 | \r | 
 | 1A\r | 
 | 01234567890123456789012345\r | 
 | 1g\r | 
 | 0123456789012345678901234567890\r | 
 | 0\r\n\r\n"""; | 
 |     _testParseInvalidResponse(response); | 
 |   } | 
 | } | 
 |  | 
 | void main() { | 
 |   HttpParserTest.runAllTests(); | 
 | } |