| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2015, 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 library convert.hex.decoder; |
| 6 |
| 7 import 'dart:convert'; |
| 8 import 'dart:typed_data'; |
| 9 |
| 10 import '../utils.dart'; |
| 11 |
| 12 /// The canonical instance of [HexDecoder]. |
| 13 const hexDecoder = const HexDecoder._(); |
| 14 |
| 15 /// A converter that decodes hexadecimal strings into byte arrays. |
| 16 /// |
| 17 /// Because two hexadecimal digits correspond to a single byte, this will throw |
| 18 /// a [FormatException] if given an odd-length string. It will also throw a |
| 19 /// [FormatException] if given a string containing non-hexadecimal code units. |
| 20 class HexDecoder extends Converter<String, List<int>> { |
| 21 const HexDecoder._(); |
| 22 |
| 23 List<int> convert(String string) { |
| 24 if (!string.length.isEven) { |
| 25 throw new FormatException("Invalid input length, must be even.", |
| 26 string, string.length); |
| 27 } |
| 28 |
| 29 var bytes = new Uint8List(string.length ~/ 2); |
| 30 _decode(string.codeUnits, 0, string.length, bytes, 0); |
| 31 return bytes; |
| 32 } |
| 33 |
| 34 StringConversionSink startChunkedConversion(Sink<List<int>> sink) => |
| 35 new _HexDecoderSink(sink); |
| 36 } |
| 37 |
| 38 /// A conversion sink for chunked hexadecimal decoding. |
| 39 class _HexDecoderSink extends StringConversionSinkBase { |
| 40 /// The underlying sink to which decoded byte arrays will be passed. |
| 41 final Sink<List<int>> _sink; |
| 42 |
| 43 /// The trailing digit from the previous string. |
| 44 /// |
| 45 /// This will be non-`null` if the most recent string had an odd number of |
| 46 /// hexadecimal digits. Since it's the most significant digit, it's always a |
| 47 /// multiple of 16. |
| 48 int _lastDigit; |
| 49 |
| 50 _HexDecoderSink(this._sink); |
| 51 |
| 52 void addSlice(String string, int start, int end, bool isLast) { |
| 53 RangeError.checkValidRange(start, end, string.length); |
| 54 |
| 55 if (start == end) { |
| 56 if (isLast) _close(string, end); |
| 57 return; |
| 58 } |
| 59 |
| 60 var codeUnits = string.codeUnits; |
| 61 Uint8List bytes; |
| 62 int bytesStart; |
| 63 if (_lastDigit == null) { |
| 64 bytes = new Uint8List((end - start) ~/ 2); |
| 65 bytesStart = 0; |
| 66 } else { |
| 67 var hexPairs = (end - start - 1) ~/ 2; |
| 68 bytes = new Uint8List(1 + hexPairs); |
| 69 bytes[0] = _lastDigit + digitForCodeUnit(codeUnits, start); |
| 70 start++; |
| 71 bytesStart = 1; |
| 72 } |
| 73 |
| 74 _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart); |
| 75 |
| 76 _sink.add(bytes); |
| 77 if (isLast) _close(string, end); |
| 78 } |
| 79 |
| 80 ByteConversionSink asUtf8Sink(bool allowMalformed) => |
| 81 new _HexDecoderByteSink(_sink); |
| 82 |
| 83 void close() => _close(); |
| 84 |
| 85 /// Like [close], but includes [string] and [index] in the [FormatException] |
| 86 /// if one is thrown. |
| 87 void _close([String string, int index]) { |
| 88 if (_lastDigit != null) { |
| 89 throw new FormatException( |
| 90 "Input ended with incomplete encoded byte.", string, index); |
| 91 } |
| 92 |
| 93 _sink.close(); |
| 94 } |
| 95 } |
| 96 |
| 97 /// A conversion sink for chunked hexadecimal decoding from UTF-8 bytes. |
| 98 class _HexDecoderByteSink extends ByteConversionSinkBase { |
| 99 /// The underlying sink to which decoded byte arrays will be passed. |
| 100 final Sink<List<int>> _sink; |
| 101 |
| 102 /// The trailing digit from the previous string. |
| 103 /// |
| 104 /// This will be non-`null` if the most recent string had an odd number of |
| 105 /// hexadecimal digits. Since it's the most significant digit, it's always a |
| 106 /// multiple of 16. |
| 107 int _lastDigit; |
| 108 |
| 109 _HexDecoderByteSink(this._sink); |
| 110 |
| 111 void add(List<int> chunk) => addSlice(chunk, 0, chunk.length, false); |
| 112 |
| 113 void addSlice(List<int> chunk, int start, int end, bool isLast) { |
| 114 RangeError.checkValidRange(start, end, chunk.length); |
| 115 |
| 116 if (start == end) { |
| 117 if (isLast) _close(chunk, end); |
| 118 return; |
| 119 } |
| 120 |
| 121 Uint8List bytes; |
| 122 int bytesStart; |
| 123 if (_lastDigit == null) { |
| 124 bytes = new Uint8List((end - start) ~/ 2); |
| 125 bytesStart = 0; |
| 126 } else { |
| 127 var hexPairs = (end - start - 1) ~/ 2; |
| 128 bytes = new Uint8List(1 + hexPairs); |
| 129 bytes[0] = _lastDigit + digitForCodeUnit(chunk, start); |
| 130 start++; |
| 131 bytesStart = 1; |
| 132 } |
| 133 |
| 134 _lastDigit = _decode(chunk, start, end, bytes, bytesStart); |
| 135 |
| 136 _sink.add(bytes); |
| 137 if (isLast) _close(chunk, end); |
| 138 } |
| 139 |
| 140 void close() => _close(); |
| 141 |
| 142 /// Like [close], but includes [chunk] and [index] in the [FormatException] |
| 143 /// if one is thrown. |
| 144 void _close([List<int> chunk, int index]) { |
| 145 if (_lastDigit != null) { |
| 146 throw new FormatException( |
| 147 "Input ended with incomplete encoded byte.", chunk, index); |
| 148 } |
| 149 |
| 150 _sink.close(); |
| 151 } |
| 152 } |
| 153 |
| 154 /// Decodes [codeUnits] and writes the result into [destination]. |
| 155 /// |
| 156 /// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes |
| 157 /// the result into [destination] starting at [destinationStart]. |
| 158 /// |
| 159 /// If there's a leftover digit at the end of the decoding, this returns that |
| 160 /// digit. Otherwise it returns `null`. |
| 161 int _decode(List<int> codeUnits, int sourceStart, int sourceEnd, |
| 162 List<int> destination, int destinationStart) { |
| 163 var destinationIndex = destinationStart; |
| 164 for (var i = sourceStart; i < sourceEnd - 1; i += 2) { |
| 165 var firstDigit = digitForCodeUnit(codeUnits, i); |
| 166 var secondDigit = digitForCodeUnit(codeUnits, i + 1); |
| 167 destination[destinationIndex++] = 16 * firstDigit + secondDigit; |
| 168 } |
| 169 |
| 170 if ((sourceEnd - sourceStart).isEven) return null; |
| 171 return 16 * digitForCodeUnit(codeUnits, sourceEnd - 1); |
| 172 } |
| OLD | NEW |