Загрузить файл
  1. base64hub
  2. Base64 в коде
  3. Dart
Код

Base64 в Dart

В библиотеке dart:convert есть кодеки Base64 для обоих алфавитов и метод normalize, который исправляет дополнение. Результаты ниже получены на Dart 3.13.

Закодировать и декодировать строку

Внутри Dart строки хранятся в UTF-16, поэтому перед кодированием переведите их в байты UTF-8 через utf8.encode:

import 'dart:convert';

void main() {
  final enc = base64Encode(utf8.encode('héllo wörld'));
  print(enc);                            // aMOpbGxvIHfDtnJsZA==
  print(utf8.decode(base64Decode(enc))); // héllo wörld
}

base64Encode(bytes) – сокращение для base64.encode(bytes), а base64Decode(s) – для base64.decode(s). Если декодированные байты не в UTF-8, utf8.decode бросит FormatException: Invalid UTF-8 byte; передайте allowMalformed: true, чтобы вместо исключения получить символы замены.

Те же два вызова собирают заголовок HTTP Basic Auth, например для package:http:

final headers = {'Authorization': 'Basic ${base64Encode(utf8.encode('user:pass'))}'};
// Authorization: Basic dXNlcjpwYXNz

Генератор заголовка Basic Auth покажет такое же значение для любых учётных данных.

URL-safe Base64 и normalize()

base64Url – второй кодек, с алфавитом -_. Оба кодировщика всегда пишут дополнение, так что отрежьте его, если ваш формат обходится без него:

final d = utf8.encode('<<???>>');
print(base64Url.encode(d));                     // PDw_Pz8-Pg==
print(base64Url.encode(d).replaceAll('=', '')); // PDw_Pz8-Pg

А вот декодирование в Dart дружелюбнее, чем в большинстве языков. Оба декодера принимают оба алфавита, но требуют правильного дополнения. normalize исправляет дополнение и заодно приводит алфавит:

base64Url.normalize('PDw_Pz8-Pg');   // PDw_Pz8-Pg==
base64.normalize('PDw_Pz8-Pg');      // PDw/Pz8+Pg==

utf8.decode(base64Url.decode(base64Url.normalize('PDw_Pz8-Pg')));  // <<???>>
utf8.decode(base64Decode('PDw_Pz8-Pg=='));                         // <<???>>

На некорректном Base64 normalize тоже бросает FormatException, так что его можно использовать и как валидатор. Сегменты JWT – это Base64URL без дополнения; см. Base64 и Base64URL и статью про дополнение или вставьте токен в декодер JWT.

С normalize полезная нагрузка JWT читается в одну строку:

const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig';
final payload = utf8.decode(base64Url.decode(base64Url.normalize(jwt.split('.')[1])));
print(payload);                        // {"sub":"1234"}
print(jsonDecode(payload)['sub']);     // 1234

Этот код только читает claims и не проверяет подпись, поэтому никогда не принимайте на их основе решения об авторизации на клиенте.

Закодировать и декодировать файл

import 'dart:io';

final bytes = await File('hello.txt').readAsBytes();
print(base64Encode(bytes));                              // aGVsbG8K
await File('copy.txt').writeAsBytes(base64Decode('aGVsbG8K'));

Большие файлы потоком

base64.encoder и base64.decoder – это трансформеры потоков, поэтому файл можно конвертировать по частям:

// encode: bytes -> Base64 text -> ASCII bytes on disk
await File('big.bin')
    .openRead()
    .transform(base64.encoder)
    .transform(ascii.encoder)
    .pipe(File('big.b64').openWrite());

// decode: ASCII bytes -> String chunks -> bytes
await File('big.b64')
    .openRead()
    .transform(ascii.decoder)
    .transform(base64.decoder)
    .pipe(File('big.out').openWrite());

Трансформеры сохраняют остаток байтов между частями, так что размер частей не важен. Проверка на файле в 300 КБ: потоковое кодирование и декодирование дали те же байты, что и функции в памяти.

Flutter: картинки и data URI

Показать картинку из Base64:

Image.memory(base64Decode(b64))

Чтобы получить Base64 из ассета или выбранного файла, прочитайте байты и вызовите base64Encode. С префиксом data URI справится UriData:

UriData.parse('data:text/plain;base64,aGk=').contentAsBytes();  // [104, 105]
UriData.fromBytes(pngBytes, mimeType: 'image/png').toString();   // data:image/png;base64,...

Если передать data URI прямо в base64Decode, получите Invalid character (at character 5): виноват символ : после data. Чтобы проверить, что лежит в незнакомой строке, откройте Base64 в картинку: инструмент покажет превью прямо в браузере.

Проверка и сообщения FormatException

Все ошибки декодирования – это FormatException. toString() содержит сообщение, входную строку и значок ^ под проблемным местом; e.message и e.offset дают эти части по отдельности. С base64Decode:

ВходСообщение
aGkInvalid length, must be multiple of four (at character 4)
aInvalid length, must be multiple of four (at character 2)
aGk=!!Invalid padding character (at character 5)
aGk==Invalid padding character (at character 5)
aGl=Invalid encoding before padding (at character 4)
"aGVs\nbG8K"Invalid character (at character 5)

В двух местах, где многие языки снисходительны, Dart строг: он отвергает переводы строк (удаляйте их из входных данных MIME или PEM) и ненулевые биты дополнения (aGl=). Проверка корректности:

bool isBase64(String s) {
  try {
    base64Decode(s);
    return true;
  } on FormatException {
    return false;
  }
}

Для наглядной проверки строки, которая не декодируется, откройте валидатор Base64; причины разобраны в статье частые ошибки Base64.

Подводные камни

  • Кодирование codeUnits. base64Encode('é'.codeUnits) возвращает 6Q== (Latin-1, а не UTF-8), а '✓'.codeUnits бросает ArgumentError: Invalid argument (Not a byte value at index 0: 0x2713). Всегда проходите через utf8.encode.
  • Переносы строк из других систем. MIME из писем или вывод base64 в Linux содержат \n; сначала удалите пробельные символы через s.replaceAll(RegExp(r'\s'), '').
  • Ожидание URL-safe вывода от base64Encode. Он пишет + и /. Для всего, что попадает в URL, используйте base64Url.encode.
  • Нет переноса строк в стиле MIME. Кодеки никогда не вставляют переводы строк. Если нужно, разбейте результат на строки по 76 символов сами.

Вопросы и ответы

Как показать картинку из Base64 во Flutter?

Декодируйте строку в байты и используйте Image.memory(base64Decode(b64)). Если строка – data URI, сначала отрежьте всё до запятой включительно или воспользуйтесь UriData.parse(uri).contentAsBytes().

Почему base64Decode пишет «Invalid length, must be multiple of four»?

У входной строки нет дополнения, что типично для JWT и URL-safe токенов. Сначала вызовите base64.normalize(s) или base64Url.normalize(s): метод добавит недостающие =.

Принимает ли base64Decode в Dart URL-safe строки?

Да. Декодер понимает и +/, и -_, так что base64Decode('PDw_Pz8-Pg==') сработает. Различаются только кодировщики: base64 пишет +/, а base64Url – -_.

Читайте также