|
| 1 | +import 'dart:async'; |
| 2 | +import 'dart:typed_data'; |
| 3 | + |
| 4 | +import '../bytes.dart'; |
| 5 | + |
| 6 | +import '../text.dart'; |
| 7 | + |
| 8 | +/// The Default Base64 encoding scheme — |
| 9 | +/// [RFC 4648 section 4](https://datatracker.ietf.org/doc/html/rfc4648#section-4) |
| 10 | +/// |
| 11 | +/// **alphabet**: A–Za–z0–9+/ |
| 12 | +/// **padding**: '='. |
| 13 | +class Base64 extends Text { |
| 14 | + /// Encodes [bytes] to Base64 text. |
| 15 | + Base64(FutureOr<Uint8List> bytes) : super(_Base64Impl(bytes)); |
| 16 | + |
| 17 | + /// Encodes the utf8-encoded bytes of [str] to Base64 text. |
| 18 | + Base64.utf8(String str) : this(BytesOf.utf8(str)); |
| 19 | + |
| 20 | + /// Encodes the bytes of [list] to Base64 text. |
| 21 | + Base64.list(List<int> list) : this(BytesOf.list(list)); |
| 22 | +} |
| 23 | + |
| 24 | +/// The Base 64 encoding with an URL and filename safe alphabet — |
| 25 | +/// [RFC 4648 section 5](https://datatracker.ietf.org/doc/html/rfc4648#section-5) |
| 26 | +/// |
| 27 | +/// **alphabet**: A–Za–z0–9-_ |
| 28 | +/// **padding**: '='. |
| 29 | +class Base64Url extends Text { |
| 30 | + /// Encodes [bytes] to Base64 text with URL and filename safe alphabet. |
| 31 | + Base64Url(FutureOr<Uint8List> bytes) : super(_Base64Impl.url(bytes)); |
| 32 | + |
| 33 | + /// Encodes the utf8-encoded bytes of [str] to Base64Url text. |
| 34 | + Base64Url.utf8(String str) : this(BytesOf.utf8(str)); |
| 35 | + |
| 36 | + /// Encodes the bytes of [list] to Base64Url text. |
| 37 | + Base64Url.list(List<int> list) : this(BytesOf.list(list)); |
| 38 | +} |
| 39 | + |
| 40 | +/// The actual implementation of Base64. |
| 41 | +class _Base64Impl extends Text { |
| 42 | + /// Default Base64. |
| 43 | + _Base64Impl(FutureOr<Uint8List> bytesToEncode) |
| 44 | + : this._alphabet(bytesToEncode, _base64Alphabet); |
| 45 | + |
| 46 | + /// Url Base64 |
| 47 | + _Base64Impl.url(FutureOr<Uint8List> bytesToEncode) |
| 48 | + : this._alphabet(bytesToEncode, _base64UrlAlphabet); |
| 49 | + |
| 50 | + /// Helper ctor. |
| 51 | + _Base64Impl._alphabet(FutureOr<Uint8List> bytesToEncode, String alphabet) |
| 52 | + : super( |
| 53 | + Future(() async { |
| 54 | + final bytes = await bytesToEncode; |
| 55 | + return _Base64Str( |
| 56 | + _Pad( |
| 57 | + _Base64Bytes(_Base64Indexes(bytes), alphabet), |
| 58 | + bytes, |
| 59 | + ), |
| 60 | + ).toString(); |
| 61 | + }), |
| 62 | + ); |
| 63 | + |
| 64 | + /// The base64 alphabet. |
| 65 | + static const String _base64Alphabet = |
| 66 | + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| 67 | + |
| 68 | + /// The base64url alphabet. |
| 69 | + static const String _base64UrlAlphabet = |
| 70 | + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; |
| 71 | +} |
| 72 | + |
| 73 | +/// Base64 bytes as [String]. |
| 74 | +class _Base64Str { |
| 75 | + /// Decodes the Base64 bytes to the corresponding string. |
| 76 | + const _Base64Str(this._toBase64); |
| 77 | + |
| 78 | + /// Something that retrieves Base64 bytes. |
| 79 | + final Uint8List Function() _toBase64; |
| 80 | + |
| 81 | + @override |
| 82 | + String toString() => String.fromCharCodes(_toBase64()); |
| 83 | +} |
| 84 | + |
| 85 | +/// Base64 with padding characters. |
| 86 | +class _Pad { |
| 87 | + /// Inserts padding characters '=', if needed. |
| 88 | + _Pad(this._toBase64, Uint8List unencoded) |
| 89 | + : _inputLength = unencoded.lengthInBytes; |
| 90 | + |
| 91 | + final int _inputLength; |
| 92 | + |
| 93 | + // The base64 array to be padded. |
| 94 | + final Uint8List Function() _toBase64; |
| 95 | + |
| 96 | + /// The '=' ASCII code. |
| 97 | + static const _pad = 0x3d; |
| 98 | + |
| 99 | + Uint8List call() { |
| 100 | + final base64 = _toBase64(); |
| 101 | + switch (_inputLength % 3) { |
| 102 | + case 0: |
| 103 | + break; // No padding chars in this case. |
| 104 | + case 1: // Two padding chars. |
| 105 | + base64.fillRange(base64.length - 2, base64.length, _pad); |
| 106 | + break; |
| 107 | + case 2: // One padding char. |
| 108 | + base64[base64.length - 1] = _pad; |
| 109 | + break; |
| 110 | + } |
| 111 | + return base64; |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +/// A list of sextets indexes as a base64 byte list. |
| 116 | +class _Base64Bytes { |
| 117 | + /// Transforms a list of sextets indexes into a list of base64 encoded bytes. |
| 118 | + const _Base64Bytes(this._toIndexes, this._alphabet); |
| 119 | + |
| 120 | + /// Something that retrieves a list of sextets indexes. |
| 121 | + final Uint8List Function() _toIndexes; |
| 122 | + final String _alphabet; |
| 123 | + |
| 124 | + Uint8List call() { |
| 125 | + final base64 = _toIndexes(); |
| 126 | + for (int i = 0; i < base64.lengthInBytes; ++i) { |
| 127 | + final index = base64[i]; |
| 128 | + base64[i] = _alphabet.codeUnitAt(index); |
| 129 | + } |
| 130 | + return base64; |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +/// Bytes as a list of base64 alphabet sextets indexes. |
| 135 | +class _Base64Indexes { |
| 136 | + /// Converts an unencoded list of bytes into a list of sextets indexes. |
| 137 | + const _Base64Indexes(this._unencoded); |
| 138 | + // The unencoded bytes. |
| 139 | + final Uint8List _unencoded; |
| 140 | + |
| 141 | + /// a bitmask for the 6 most-significant bits 11111100. |
| 142 | + static const _mask6Msb = 0xfc; |
| 143 | + |
| 144 | + /// a bitmask for the 4 most-significant bits 11110000. |
| 145 | + static const _mask4Msb = 0xf0; |
| 146 | + |
| 147 | + /// a bitmask for the 2 most-significant bits 11000000. |
| 148 | + static const _mask2Msb = 0xC0; |
| 149 | + |
| 150 | + /// a bitmask for the 6 least-significant bits 00111111. |
| 151 | + static const _mask6Lsb = 0x3f; |
| 152 | + |
| 153 | + /// a bitmask for the 4 least-significant bits 00001111. |
| 154 | + static const _mask4Lsb = 0x0f; |
| 155 | + |
| 156 | + /// a bitmask for the 2 least-significant bits 00000011. |
| 157 | + static const _mask2Lsb = 0x03; |
| 158 | + |
| 159 | + /// List of sextets indexes. |
| 160 | + Uint8List call() { |
| 161 | + final indexes = _NewListOfBytes(_unencoded).value; |
| 162 | + int sextetIndex = 0; |
| 163 | + for (int octetIndex = 0; |
| 164 | + octetIndex < _unencoded.lengthInBytes; |
| 165 | + ++octetIndex) { |
| 166 | + final octet = _unencoded[octetIndex]; |
| 167 | + switch (octetIndex % 3) { |
| 168 | + case 0: |
| 169 | + { |
| 170 | + // sets the sextet to the 6-msb of the octet. |
| 171 | + indexes[sextetIndex] = (octet & _mask6Msb) >> 2; |
| 172 | + // sets the 2-most-significant bits of the next sextet to the |
| 173 | + // 2-least-significant bits of the current octet (byte). |
| 174 | + indexes[sextetIndex + 1] = (octet & _mask2Lsb) << 4; // 00110000 |
| 175 | + ++sextetIndex; |
| 176 | + break; |
| 177 | + } |
| 178 | + case 1: |
| 179 | + { |
| 180 | + /// combines the partial value of the sextet (2-msb) with the 4-msb of the |
| 181 | + /// current octet. |
| 182 | + indexes[sextetIndex] = |
| 183 | + indexes[sextetIndex] | ((octet & _mask4Msb) >> 4); |
| 184 | + // sets the 4-msb of the next sextet to the 4-lsb of the current |
| 185 | + // octet (byte). |
| 186 | + indexes[sextetIndex + 1] = (octet & _mask4Lsb) << 2; // 00111100 |
| 187 | + ++sextetIndex; |
| 188 | + break; |
| 189 | + } |
| 190 | + case 2: |
| 191 | + { |
| 192 | + /// combines the partial value of the sextet (4-msb) with the 2-msb |
| 193 | + /// of the current octet. |
| 194 | + indexes[sextetIndex] = |
| 195 | + indexes[sextetIndex] | ((octet & _mask2Msb) >> 6); |
| 196 | + // sets the next sextet as the 6-lsb of the current octet — whole |
| 197 | + // sextet value. |
| 198 | + indexes[sextetIndex + 1] = octet & _mask6Lsb; |
| 199 | + sextetIndex += 2; |
| 200 | + break; |
| 201 | + } |
| 202 | + } |
| 203 | + } |
| 204 | + return indexes; |
| 205 | + } |
| 206 | +} |
| 207 | + |
| 208 | +/// List for base64 encoded bytes. |
| 209 | +class _NewListOfBytes { |
| 210 | + /// Makes a zero-initialized (0x00) list of bytes to hold base64 encoded |
| 211 | + /// bytes. |
| 212 | + const _NewListOfBytes(this._unencoded); |
| 213 | + |
| 214 | + final Uint8List _unencoded; |
| 215 | + |
| 216 | + /// Retrieves a zero-initialized list whose length is a multiple of four. |
| 217 | + Uint8List get value { |
| 218 | + final length = (_unencoded.lengthInBytes * 4 / 3.0).ceil(); |
| 219 | + final mod4 = length % 4; |
| 220 | + return mod4 == 0 ? Uint8List(length) : Uint8List(length + 4 - mod4); |
| 221 | + } |
| 222 | +} |
0 commit comments