Skip to content

Commit ac6dace

Browse files
committed
feat: Base64 and Base64Url encoding schemes
Closes #12
1 parent c083a36 commit ac6dace

File tree

11 files changed

+367
-24
lines changed

11 files changed

+367
-24
lines changed

CHANGELOG.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ and this project adheres to [Dart Package Versioning](https://dart.dev/tools/pub
99

1010
## [Unreleased]
1111

12+
### Added
13+
14+
- Base64 and Base64Url encoding schemes —
15+
[12](https://github.com/dartoos-dev/dartoos/issues/12)
16+
1217
## [0.1.0] - 2021-10-05
1318

1419
### Added
1520

16-
- FutureWrap class: instead of returning a value, subclasses will
17-
themselves be the value.
21+
- FutureWrap class: instead of returning a value, subclasses are the value.
1822
- Text abstract class
1923
- Rand class for generating random string patterns —
2024
[9](https://g]]]ithub.com/dartoos-dev/dartoos/issues/9).
21-
22-
## [0.0.1]

lib/base64.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// >Base64 is a group of binary-to-text encoding schemes that represent binary
2+
/// >data (more specifically, a sequence of 8-bit bytes) in an ASCII string
3+
/// >format by translating the data into a radix-64 representation — Wikipedia.
4+
///
5+
/// Specification reference:
6+
/// - [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)
7+
library base64;
8+
9+
export 'src/base64/base64.dart';

lib/dartoos.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
/// Support for doing something awesome.
1+
/// Collection of object-oriented utility classes for Dart/Flutter development.
22
///
3-
/// More dartdocs go here.
3+
///
4+
/// Inspired by [Cactoos](https://github.com/yegor256/cactoos)
45
library dartoos;
56

7+
export 'base64.dart';
8+
export 'src/bytes.dart';
69
export 'src/future_wrap.dart';
7-
export 'text.dart';
8-
// @todo: #3 Export any libraries intended for clients of this package.
10+
export 'src/rand.dart';
11+
export 'src/text.dart';

lib/src/base64/base64.dart

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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+
}

lib/src/bytes.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
import 'dart:io';
4+
import 'dart:typed_data';
5+
6+
import 'future_wrap.dart';
7+
8+
/// Represents a source of raw bytes.
9+
abstract class Bytes extends FutureWrap<Uint8List> {
10+
/// Encapsulates a Future of bytes.
11+
Bytes(FutureOr<Uint8List> bytes) : super(bytes);
12+
}
13+
14+
/// An amount of bytes from several sources.
15+
class BytesOf extends Bytes {
16+
/// Raw bytes from [bytes].
17+
BytesOf(FutureOr<Uint8List> bytes) : super(bytes);
18+
19+
/// List of integers as [Uint8List].
20+
BytesOf.list(List<int> list) : this(Uint8List.fromList(list));
21+
22+
/// String as a list of UTF-8 bytes.
23+
BytesOf.utf8(String str) : this.list(utf8.encode(str));
24+
25+
/// The file's content as bytes.
26+
BytesOf.file(File file) : this(file.readAsBytes());
27+
}

lib/src/future_wrap.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ import 'dart:async';
66
/// subclasses will themselves be the value.
77
abstract class FutureWrap<T> implements Future<T> {
88
/// Main constructor.
9-
FutureWrap(this._origin);
10-
11-
/// Creates a Future<T> from value.
12-
FutureWrap.value(T value) : this(Future.value(value));
9+
FutureWrap(FutureOr<T> origin) : _origin = Future.value(origin);
1310

1411
/// The encapsulated original Future.
1512
final Future<T> _origin;

lib/src/text/rand.dart renamed to lib/src/rand.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'dart:math';
22

3-
import '../text.dart';
3+
import 'text.dart';
44

55
/// Randomized text.
66
class Rand extends Text {

lib/src/text.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
import 'dart:async';
2+
13
import 'future_wrap.dart';
24

3-
/// Represents a Future<String> value.
5+
/// Represents a source of text as [String].
46
abstract class Text extends FutureWrap<String> {
57
/// Constructs a Text from a Future<String>
6-
Text(Future<String> text) : super(text);
7-
8-
/// Text from value.
9-
Text.value(String value) : super.value(value);
8+
Text(FutureOr<String> text) : super(text);
109
}

lib/text.dart

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)