Skip to content

Commit

Permalink
feat: Base64 and Base64Url encoding schemes
Browse files Browse the repository at this point in the history
Closes #12
  • Loading branch information
rafamizes committed Oct 9, 2021
1 parent c083a36 commit ac6dace
Show file tree
Hide file tree
Showing 11 changed files with 367 additions and 24 deletions.
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ and this project adheres to [Dart Package Versioning](https://dart.dev/tools/pub

## [Unreleased]

### Added

- Base64 and Base64Url encoding schemes —
[12](https://github.com/dartoos-dev/dartoos/issues/12)

## [0.1.0] - 2021-10-05

### Added

- FutureWrap class: instead of returning a value, subclasses will
themselves be the value.
- FutureWrap class: instead of returning a value, subclasses are the value.
- Text abstract class
- Rand class for generating random string patterns —
[9](https://g]]]ithub.com/dartoos-dev/dartoos/issues/9).

## [0.0.1]
9 changes: 9 additions & 0 deletions lib/base64.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// >Base64 is a group of binary-to-text encoding schemes that represent binary
/// >data (more specifically, a sequence of 8-bit bytes) in an ASCII string
/// >format by translating the data into a radix-64 representation — Wikipedia.
///
/// Specification reference:
/// - [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)
library base64;

export 'src/base64/base64.dart';
11 changes: 7 additions & 4 deletions lib/dartoos.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/// Support for doing something awesome.
/// Collection of object-oriented utility classes for Dart/Flutter development.
///
/// More dartdocs go here.
///
/// Inspired by [Cactoos](https://github.com/yegor256/cactoos)
library dartoos;

export 'base64.dart';
export 'src/bytes.dart';
export 'src/future_wrap.dart';
export 'text.dart';
// @todo: #3 Export any libraries intended for clients of this package.
export 'src/rand.dart';
export 'src/text.dart';
222 changes: 222 additions & 0 deletions lib/src/base64/base64.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import 'dart:async';
import 'dart:typed_data';

import '../bytes.dart';

import '../text.dart';

/// The Default Base64 encoding scheme —
/// [RFC 4648 section 4](https://datatracker.ietf.org/doc/html/rfc4648#section-4)
///
/// **alphabet**: A–Za–z0–9+/
/// **padding**: '='.
class Base64 extends Text {
/// Encodes [bytes] to Base64 text.
Base64(FutureOr<Uint8List> bytes) : super(_Base64Impl(bytes));

/// Encodes the utf8-encoded bytes of [str] to Base64 text.
Base64.utf8(String str) : this(BytesOf.utf8(str));

/// Encodes the bytes of [list] to Base64 text.
Base64.list(List<int> list) : this(BytesOf.list(list));
}

/// The Base 64 encoding with an URL and filename safe alphabet —
/// [RFC 4648 section 5](https://datatracker.ietf.org/doc/html/rfc4648#section-5)
///
/// **alphabet**: A–Za–z0–9-_
/// **padding**: '='.
class Base64Url extends Text {
/// Encodes [bytes] to Base64 text with URL and filename safe alphabet.
Base64Url(FutureOr<Uint8List> bytes) : super(_Base64Impl.url(bytes));

/// Encodes the utf8-encoded bytes of [str] to Base64Url text.
Base64Url.utf8(String str) : this(BytesOf.utf8(str));

/// Encodes the bytes of [list] to Base64Url text.
Base64Url.list(List<int> list) : this(BytesOf.list(list));
}

/// The actual implementation of Base64.
class _Base64Impl extends Text {
/// Default Base64.
_Base64Impl(FutureOr<Uint8List> bytesToEncode)
: this._alphabet(bytesToEncode, _base64Alphabet);

/// Url Base64
_Base64Impl.url(FutureOr<Uint8List> bytesToEncode)
: this._alphabet(bytesToEncode, _base64UrlAlphabet);

/// Helper ctor.
_Base64Impl._alphabet(FutureOr<Uint8List> bytesToEncode, String alphabet)
: super(
Future(() async {
final bytes = await bytesToEncode;
return _Base64Str(
_Pad(
_Base64Bytes(_Base64Indexes(bytes), alphabet),
bytes,
),
).toString();
}),
);

/// The base64 alphabet.
static const String _base64Alphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/// The base64url alphabet.
static const String _base64UrlAlphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
}

/// Base64 bytes as [String].
class _Base64Str {
/// Decodes the Base64 bytes to the corresponding string.
const _Base64Str(this._toBase64);

/// Something that retrieves Base64 bytes.
final Uint8List Function() _toBase64;

@override
String toString() => String.fromCharCodes(_toBase64());
}

/// Base64 with padding characters.
class _Pad {
/// Inserts padding characters '=', if needed.
_Pad(this._toBase64, Uint8List unencoded)
: _inputLength = unencoded.lengthInBytes;

final int _inputLength;

// The base64 array to be padded.
final Uint8List Function() _toBase64;

/// The '=' ASCII code.
static const _pad = 0x3d;

Uint8List call() {
final base64 = _toBase64();
switch (_inputLength % 3) {
case 0:
break; // No padding chars in this case.
case 1: // Two padding chars.
base64.fillRange(base64.length - 2, base64.length, _pad);
break;
case 2: // One padding char.
base64[base64.length - 1] = _pad;
break;
}
return base64;
}
}

/// A list of sextets indexes as a base64 byte list.
class _Base64Bytes {
/// Transforms a list of sextets indexes into a list of base64 encoded bytes.
const _Base64Bytes(this._toIndexes, this._alphabet);

/// Something that retrieves a list of sextets indexes.
final Uint8List Function() _toIndexes;
final String _alphabet;

Uint8List call() {
final base64 = _toIndexes();
for (int i = 0; i < base64.lengthInBytes; ++i) {
final index = base64[i];
base64[i] = _alphabet.codeUnitAt(index);
}
return base64;
}
}

/// Bytes as a list of base64 alphabet sextets indexes.
class _Base64Indexes {
/// Converts an unencoded list of bytes into a list of sextets indexes.
const _Base64Indexes(this._unencoded);
// The unencoded bytes.
final Uint8List _unencoded;

/// a bitmask for the 6 most-significant bits 11111100.
static const _mask6Msb = 0xfc;

/// a bitmask for the 4 most-significant bits 11110000.
static const _mask4Msb = 0xf0;

/// a bitmask for the 2 most-significant bits 11000000.
static const _mask2Msb = 0xC0;

/// a bitmask for the 6 least-significant bits 00111111.
static const _mask6Lsb = 0x3f;

/// a bitmask for the 4 least-significant bits 00001111.
static const _mask4Lsb = 0x0f;

/// a bitmask for the 2 least-significant bits 00000011.
static const _mask2Lsb = 0x03;

/// List of sextets indexes.
Uint8List call() {
final indexes = _NewListOfBytes(_unencoded).value;
int sextetIndex = 0;
for (int octetIndex = 0;
octetIndex < _unencoded.lengthInBytes;
++octetIndex) {
final octet = _unencoded[octetIndex];
switch (octetIndex % 3) {
case 0:
{
// sets the sextet to the 6-msb of the octet.
indexes[sextetIndex] = (octet & _mask6Msb) >> 2;
// sets the 2-most-significant bits of the next sextet to the
// 2-least-significant bits of the current octet (byte).
indexes[sextetIndex + 1] = (octet & _mask2Lsb) << 4; // 00110000
++sextetIndex;
break;
}
case 1:
{
/// combines the partial value of the sextet (2-msb) with the 4-msb of the
/// current octet.
indexes[sextetIndex] =
indexes[sextetIndex] | ((octet & _mask4Msb) >> 4);
// sets the 4-msb of the next sextet to the 4-lsb of the current
// octet (byte).
indexes[sextetIndex + 1] = (octet & _mask4Lsb) << 2; // 00111100
++sextetIndex;
break;
}
case 2:
{
/// combines the partial value of the sextet (4-msb) with the 2-msb
/// of the current octet.
indexes[sextetIndex] =
indexes[sextetIndex] | ((octet & _mask2Msb) >> 6);
// sets the next sextet as the 6-lsb of the current octet — whole
// sextet value.
indexes[sextetIndex + 1] = octet & _mask6Lsb;
sextetIndex += 2;
break;
}
}
}
return indexes;
}
}

/// List for base64 encoded bytes.
class _NewListOfBytes {
/// Makes a zero-initialized (0x00) list of bytes to hold base64 encoded
/// bytes.
const _NewListOfBytes(this._unencoded);

final Uint8List _unencoded;

/// Retrieves a zero-initialized list whose length is a multiple of four.
Uint8List get value {
final length = (_unencoded.lengthInBytes * 4 / 3.0).ceil();
final mod4 = length % 4;
return mod4 == 0 ? Uint8List(length) : Uint8List(length + 4 - mod4);
}
}
27 changes: 27 additions & 0 deletions lib/src/bytes.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'future_wrap.dart';

/// Represents a source of raw bytes.
abstract class Bytes extends FutureWrap<Uint8List> {
/// Encapsulates a Future of bytes.
Bytes(FutureOr<Uint8List> bytes) : super(bytes);
}

/// An amount of bytes from several sources.
class BytesOf extends Bytes {
/// Raw bytes from [bytes].
BytesOf(FutureOr<Uint8List> bytes) : super(bytes);

/// List of integers as [Uint8List].
BytesOf.list(List<int> list) : this(Uint8List.fromList(list));

/// String as a list of UTF-8 bytes.
BytesOf.utf8(String str) : this.list(utf8.encode(str));

/// The file's content as bytes.
BytesOf.file(File file) : this(file.readAsBytes());
}
5 changes: 1 addition & 4 deletions lib/src/future_wrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import 'dart:async';
/// subclasses will themselves be the value.
abstract class FutureWrap<T> implements Future<T> {
/// Main constructor.
FutureWrap(this._origin);

/// Creates a Future<T> from value.
FutureWrap.value(T value) : this(Future.value(value));
FutureWrap(FutureOr<T> origin) : _origin = Future.value(origin);

/// The encapsulated original Future.
final Future<T> _origin;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/text/rand.dart → lib/src/rand.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:math';

import '../text.dart';
import 'text.dart';

/// Randomized text.
class Rand extends Text {
Expand Down
9 changes: 4 additions & 5 deletions lib/src/text.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'dart:async';

import 'future_wrap.dart';

/// Represents a Future<String> value.
/// Represents a source of text as [String].
abstract class Text extends FutureWrap<String> {
/// Constructs a Text from a Future<String>
Text(Future<String> text) : super(text);

/// Text from value.
Text.value(String value) : super.value(value);
Text(FutureOr<String> text) : super(text);
}
5 changes: 0 additions & 5 deletions lib/text.dart

This file was deleted.

3 comments on commit ac6dace

@0pdd
Copy link

@0pdd 0pdd commented on ac6dace Oct 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 1-f708a69d discovered in README.md and submitted as #14. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

@0pdd
Copy link

@0pdd 0pdd commented on ac6dace Oct 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 1-ae37e722 discovered in README.md and submitted as #15. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

@0pdd
Copy link

@0pdd 0pdd commented on ac6dace Oct 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 1-889d4acf discovered in README.md and submitted as #16. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

Please sign in to comment.