Закодировать и декодировать строку
Внутри 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:
| Вход | Сообщение |
|---|---|
aGk | Invalid length, must be multiple of four (at character 4) |
a | Invalid 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 – -_.