Enviar um arquivo
  1. base64hub
  2. Base64 no código
  3. Dart
Código

Base64 em Dart

A biblioteca dart:convert do Dart inclui codecs Base64 para os dois alfabetos, mais um método normalize que corrige o preenchimento. As saídas abaixo vêm do Dart 3.13.

Codificar e decodificar uma string

Internamente, as strings do Dart são UTF-16, então converta para bytes UTF-8 com utf8.encode antes de codificar:

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) é um atalho para base64.encode(bytes), e base64Decode(s) para base64.decode(s). Se os bytes decodificados não forem UTF-8, o utf8.decode lança FormatException: Invalid UTF-8 byte; passe allowMalformed: true para receber caracteres de substituição no lugar.

As mesmas duas chamadas montam um header HTTP Basic Auth, por exemplo para o package:http:

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

O gerador de header Basic Auth mostra o mesmo valor para quaisquer credenciais.

Base64 URL-safe e normalize()

O base64Url é um segundo codec, com o alfabeto -_. Os dois codificadores sempre escrevem o preenchimento, então remova-o se o seu formato não o quiser:

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

Na decodificação, o Dart é mais amigável que a maioria das linguagens. Os dois decodificadores aceitam os dois alfabetos, mas exigem o preenchimento correto. O normalize resolve isso e ainda converte o alfabeto:

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=='));                         // <<???>>

O normalize também lança FormatException se a entrada não for Base64 válido, então serve como validador. Os segmentos de um JWT são Base64URL sem preenchimento; veja Base64 vs Base64URL e preenchimento explicado, ou cole um token no decodificador de JWT.

Com o normalize, ler o payload de um JWT leva uma linha:

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

Isso só lê as claims; não verifica a assinatura, então nunca confie nelas para autorização no cliente.

Codificar e decodificar um arquivo

import 'dart:io';

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

Streaming de arquivos grandes

O base64.encoder e o base64.decoder são transformadores de stream, então um arquivo pode ser convertido bloco a bloco:

// 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());

Os transformadores guardam os bytes que sobram entre um bloco e outro, então o tamanho dos blocos não importa. Nas duas direções, um arquivo de teste de 300 KB saiu idêntico byte a byte ao resultado das funções em memória.

Flutter: imagens e data URIs

Para mostrar uma imagem Base64:

Image.memory(base64Decode(b64))

Para gerar Base64 a partir de um asset ou de um arquivo escolhido pelo usuário, leia os bytes e chame base64Encode. Para data URIs, o UriData cuida do prefixo:

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

Passar um data URI direto para o base64Decode falha com Invalid character (at character 5), o : depois de data. Para ver o que uma string desconhecida contém, o Base64 para imagem mostra uma prévia no navegador.

Validação e mensagens de FormatException

Todos os erros de decodificação são FormatException. O toString() inclui a mensagem, a entrada e um acento circunflexo sob o problema; e.message e e.offset dão as partes separadamente. Com base64Decode:

EntradaMensagem
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)

O Dart é rígido em dois pontos em que muitas linguagens são tolerantes: rejeita quebras de linha (remova-as em entradas MIME ou PEM) e rejeita bits de preenchimento diferentes de zero (aGl=). Uma verificação de validade:

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

Se uma string falha e a mensagem não diz o suficiente, o validador de Base64 marca o problema; erros comuns de Base64 explica as causas.

Armadilhas

  • Codificar codeUnits. base64Encode('é'.codeUnits) retorna 6Q== (Latin-1, não UTF-8), e '✓'.codeUnits lança ArgumentError: Invalid argument (Not a byte value at index 0: 0x2713). Passe sempre pelo utf8.encode.
  • Quebras de linha vindas de outros sistemas. Saída MIME de e-mail ou do base64 do Linux contém \n; remova os espaços em branco antes com s.replaceAll(RegExp(r'\s'), '').
  • Achar que o base64Encode gera saída URL-safe. Ele escreve + e /. Use base64Url.encode para tudo o que vai numa URL.
  • Sem quebra de linha MIME. Os codecs nunca inserem quebras de linha. Se precisar, divida em linhas de 76 caracteres você mesmo.

Perguntas frequentes

Como mostrar uma imagem Base64 no Flutter?

Decodifique para bytes e use Image.memory(base64Decode(b64)). Se a string for um data URI, remova antes tudo até a vírgula ou use UriData.parse(uri).contentAsBytes().

Por que o base64Decode diz 'Invalid length, must be multiple of four'?

A entrada não tem preenchimento, o que é típico de JWTs e tokens URL-safe. Chame antes base64.normalize(s) ou base64Url.normalize(s), que acrescenta os = que faltam.

O base64Decode do Dart aceita entrada URL-safe?

Sim. O decodificador aceita tanto +/ quanto -_, então base64Decode('PDw_Pz8-Pg==') funciona. Só os codificadores diferem: base64 escreve +/, base64Url escreve -_.

Continue lendo