| // Copyright (c) 2015, 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:math'; |
| |
| import 'byte_utils.dart'; |
| |
| /// This is a set of bytes with which a client connection begins in the normal |
| /// case. It can be used on a server to distinguish HTTP/1.1 and HTTP/2 clients. |
| const List<int> CONNECTION_PREFACE = [ |
| 0x50, |
| 0x52, |
| 0x49, |
| 0x20, |
| 0x2a, |
| 0x20, |
| 0x48, |
| 0x54, |
| 0x54, |
| 0x50, |
| 0x2f, |
| 0x32, |
| 0x2e, |
| 0x30, |
| 0x0d, |
| 0x0a, |
| 0x0d, |
| 0x0a, |
| 0x53, |
| 0x4d, |
| 0x0d, |
| 0x0a, |
| 0x0d, |
| 0x0a |
| ]; |
| |
| /// Reads the connection preface from [incoming]. |
| /// |
| /// The returned `Stream` will be a duplicate of `incoming` without the |
| /// connection preface. If an error occurs while reading the connection |
| /// preface, the returned stream will have only an error. |
| Stream<List<int>> readConnectionPreface(Stream<List<int>> incoming) { |
| final result = StreamController<List<int>>(); |
| late StreamSubscription subscription; |
| var connectionPrefaceRead = false; |
| var prefaceBuffer = <int>[]; |
| var terminated = false; |
| |
| void terminate(Object error) { |
| if (!terminated) { |
| result.addError(error); |
| result.close(); |
| subscription.cancel(); |
| } |
| terminated = true; |
| } |
| |
| bool compareConnectionPreface(List<int> data) { |
| for (var i = 0; i < CONNECTION_PREFACE.length; i++) { |
| if (data[i] != CONNECTION_PREFACE[i]) { |
| terminate('Connection preface does not match.'); |
| return false; |
| } |
| } |
| connectionPrefaceRead = true; |
| return true; |
| } |
| |
| void onData(List<int> data) { |
| if (connectionPrefaceRead) { |
| // Forward data after reading preface. |
| result.add(data); |
| } else { |
| if (prefaceBuffer.isEmpty && data.length > CONNECTION_PREFACE.length) { |
| if (!compareConnectionPreface(data)) return; |
| data = data.sublist(CONNECTION_PREFACE.length); |
| } else if (prefaceBuffer.length < CONNECTION_PREFACE.length) { |
| var remaining = CONNECTION_PREFACE.length - prefaceBuffer.length; |
| |
| var end = min(data.length, remaining); |
| var part1 = viewOrSublist(data, 0, end); |
| var part2 = viewOrSublist(data, end, data.length - end); |
| prefaceBuffer.addAll(part1); |
| |
| if (prefaceBuffer.length == CONNECTION_PREFACE.length) { |
| if (!compareConnectionPreface(prefaceBuffer)) return; |
| } |
| data = part2; |
| } |
| if (data.isNotEmpty) { |
| result.add(data); |
| } |
| } |
| } |
| |
| result.onListen = () { |
| subscription = |
| incoming.listen(onData, onError: result.addError, onDone: () { |
| if (!connectionPrefaceRead) { |
| terminate('EOS before connection preface could be read.'); |
| } else { |
| result.close(); |
| } |
| }); |
| result |
| ..onPause = subscription.pause |
| ..onResume = subscription.resume |
| ..onCancel = subscription.cancel; |
| }; |
| |
| return result.stream; |
| } |