Line data Source code
1 : // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2 : // for details. All rights reserved. Use of this source code is governed by a
3 : // BSD-style license that can be found in the LICENSE file.
4 :
5 : import 'dart:convert';
6 : import 'dart:typed_data';
7 :
8 : import 'package:charcode/ascii.dart';
9 :
10 : /// The canonical instance of [ChunkedCodingEncoder].
11 : const chunkedCodingEncoder = const ChunkedCodingEncoder._();
12 :
13 : /// The chunk indicating that the chunked message has finished.
14 : final _doneChunk = new Uint8List.fromList([$0, $cr, $lf, $cr, $lf]);
15 :
16 : /// A converter that encodes byte arrays into chunks with size tags.
17 : class ChunkedCodingEncoder extends Converter<List<int>, List<int>> {
18 5 : const ChunkedCodingEncoder._();
19 :
20 : List<int> convert(List<int> bytes) =>
21 0 : _convert(bytes, 0, bytes.length, isLast: true);
22 :
23 : ByteConversionSink startChunkedConversion(Sink<List<int>> sink) =>
24 0 : new _Sink(sink);
25 : }
26 :
27 : /// A conversion sink for the chunked transfer encoding.
28 : class _Sink extends ByteConversionSinkBase {
29 : /// The underlying sink to which encoded byte arrays will be passed.
30 : final Sink<List<int>> _sink;
31 :
32 0 : _Sink(this._sink);
33 :
34 : void add(List<int> chunk) {
35 0 : _sink.add(_convert(chunk, 0, chunk.length));
36 : }
37 :
38 : void addSlice(List<int> chunk, int start, int end, bool isLast) {
39 0 : RangeError.checkValidRange(start, end, chunk.length);
40 0 : _sink.add(_convert(chunk, start, end, isLast: isLast));
41 0 : if (isLast) _sink.close();
42 : }
43 :
44 : void close() {
45 0 : _sink.add(_doneChunk);
46 0 : _sink.close();
47 : }
48 : }
49 :
50 : /// Returns a new list a chunked transfer encoding header followed by the slice
51 : /// of [bytes] from [start] to [end].
52 : ///
53 : /// If [isLast] is `true`, this adds the footer that indicates that the chunked
54 : /// message is complete.
55 : List<int> _convert(List<int> bytes, int start, int end, {bool isLast: false}) {
56 0 : if (end == start) return isLast ? _doneChunk : const [];
57 :
58 0 : var size = end - start;
59 0 : var sizeInHex = size.toRadixString(16);
60 0 : var footerSize = isLast ? _doneChunk.length : 0;
61 :
62 : // Add 4 for the CRLF sequences that follow the size header and the bytes.
63 0 : var list = new Uint8List(sizeInHex.length + 4 + size + footerSize);
64 0 : list.setRange(0, sizeInHex.length, sizeInHex.codeUnits);
65 :
66 0 : var cursor = sizeInHex.length;
67 0 : list[cursor++] = $cr;
68 0 : list[cursor++] = $lf;
69 0 : list.setRange(cursor, cursor + end - start, bytes, start);
70 0 : cursor += end - start;
71 0 : list[cursor++] = $cr;
72 0 : list[cursor++] = $lf;
73 :
74 : if (isLast) {
75 0 : list.setRange(list.length - footerSize, list.length, _doneChunk);
76 : }
77 : return list;
78 : }
|