Codificar e decodificar uma string
use strict;
use warnings;
use utf8;
use MIME::Base64 qw(encode_base64 decode_base64);
use Encode qw(encode decode);
my $enc = encode_base64(encode('UTF-8', 'héllo wörld'), '');
print "$enc\n"; # aMOpbGxvIHfDtnJsZA==
print decode('UTF-8', decode_base64($enc)), "\n"; # héllo wörld
O Perl distingue strings de caracteres de strings de bytes, e o encode_base64 quer bytes, então o texto passa antes por Encode::encode('UTF-8', ...). Uma string com caracteres acima de U+00FF morre com Wide character in subroutine entry; uma com apenas caracteres Latin-1, como é, é codificada em silêncio como Latin-1, não UTF-8.
O argumento '' é explicado na próxima seção.
O argumento de final de linha
O encode_base64($bytes, $eol) quebra a saída a cada 76 caracteres e acrescenta $eol a cada linha, inclusive à última. O padrão é "\n":
print encode_base64('hi'); # "aGk=\n"
print encode_base64('x' x 60);
# eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
# eHh4
Isso é correto para corpos de e-mail MIME e errado para quase todo o resto: headers HTTP, JSON, URLs e campos de banco de dados. Passe '' para saída em uma linha, ou "\r\n" para finais de linha estritamente conforme a RFC 2045.
Para um header HTTP, fica assim:
print "Authorization: Basic ", encode_base64('user:pass', ''), "\n";
# Authorization: Basic dXNlcjpwYXNz
A decodificação não liga para finais de linha: decode_base64("aGVs\r\nbG8K") retorna "hello\n", então corpos MIME com \n ou \r\n decodificam diretamente.
Base64 URL-safe
O MIME::Base64 exporta encode_base64url e decode_base64url quando solicitados. O codificador remove o preenchimento, e o decodificador aceita entrada sem ele:
use MIME::Base64 qw(encode_base64url decode_base64url);
print encode_base64url('<<???>>'), "\n"; # PDw_Pz8-Pg
print decode_base64url('PDw_Pz8-Pg'), "\n"; # <<???>>
Para saída URL-safe que mantém o preenchimento, traduza a forma padrão com tr:
my $padded = encode_base64('<<???>>', '') =~ tr{+/}{-_}r; # PDw_Pz8-Pg==
A flag /r (Perl 5.14+) retorna a cópia modificada. Base64 vs Base64URL explica quais sistemas esperam cada variante.
Codificar e decodificar um arquivo
Abra os arquivos no modo :raw para que nenhuma camada traduza quebras de linha ou codificações:
open my $in, '<:raw', 'hello.txt' or die $!;
my $data = do { local $/; <$in> };
close $in;
print encode_base64($data, ''), "\n"; # aGVsbG8K
open my $out, '>:raw', 'copy.txt' or die $!;
print {$out} decode_base64('aGVsbG8K');
close $out;
Arquivos grandes em blocos
Leia em múltiplos de 57 bytes. 57 bytes viram exatamente 76 caracteres, então cada bloco produz linhas MIME completas, e a saída concatenada é idêntica à codificação do arquivo inteiro:
open my $in, '<:raw', 'big.bin' or die $!;
open my $out, '>:raw', 'big.b64' or die $!;
while (read($in, my $buf, 57 * 1024)) {
print {$out} encode_base64($buf);
}
close $in;
close $out;
O resultado decodificou de volta para o arquivo original de 300 KB com base64 -d. Para saída em uma linha, use encode_base64($buf, '') com qualquer tamanho de bloco divisível por 3. O módulo também informa tamanhos sem codificar: MIME::Base64::encoded_base64_length('hello') retorna 9 (incluindo a quebra de linha) e decoded_base64_length('aGVsbG8=') retorna 5. O guia de aumento de tamanho traz a fórmula.
Na linha de comando, vale conhecer duas linhas com o módulo:
perl -MMIME::Base64 -0777 -ne 'print encode_base64($_, "")' < file.bin
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < file.b64 > file.bin
Validação: o decode_base64 nunca reclama
O decode_base64 é tolerante por projeto. Ele descarta tudo o que está fora do alfabeto, para no preenchimento e retorna o que tiver:
decode_base64('aGk=!!'); # "hi"
decode_base64('aGk'); # "hi" (missing padding is fine)
decode_base64('aGl='); # "hi" (non-zero padding bits ignored)
decode_base64('a'); # "" (too short, no warning)
decode_base64('PDw_Pz8-Pg=='); # "<<\x0f\xcf\xc3\xe0", not "<<???>>"
O último é o caso perigoso: entrada URL-safe passada ao decodificador padrão gera bytes errados em silêncio. Use decode_base64url para tokens. Para entrada não confiável, confira o alfabeto e o comprimento e depois confirme a ida e volta:
sub is_base64 {
my ($s) = @_;
return 0 unless $s =~ m{\A[A-Za-z0-9+/]*={0,2}\z} && length($s) % 4 == 0;
return encode_base64(decode_base64($s), '') eq $s ? 1 : 0;
}
# is_base64('aGk=') = 1, 'aGk' = 0, 'aGl=' = 0, 'aGk=!!' = 0
O validador de Base64 faz as mesmas verificações no navegador e diz qual caractere está errado. Para strings quebradas tiradas de logs, experimente o Reparar Base64; erros comuns de Base64 explica o que costuma causá-las.
Armadilhas
- Quebra de linha no final de headers.
"Authorization: Basic " . encode_base64("$user:$pass")termina com\n, o que quebra a requisição. Passe''. - Codificação dupla. Com
use utf8e uma camada de saída:encoding(UTF-8), imprimir bytes decodificados semdecode('UTF-8', ...)mostra mojibake comohéllo. - Tratar Base64 como segredo. Qualquer pessoa consegue revertê-lo. Veja codificação vs criptografia.
Perguntas frequentes
Por que o encode_base64 acrescenta uma quebra de linha?
O segundo argumento é o final de linha, e o padrão é "\n". A saída é quebrada a cada 76 caracteres e sempre termina com essa string. Passe '' para obter uma única linha.
O que significa 'Wide character in subroutine entry'?
Você passou para o encode_base64 uma string de caracteres com code points acima de 255, e ele só aceita bytes. Codifique o texto antes: encode_base64(encode('UTF-8', $text), '').
O decode_base64 valida a entrada?
Não. Ele ignora os caracteres fora do alfabeto e retorna o que conseguir decodificar, então decode_base64('aGk=!!') dá hi. Confira a string com uma regex antes de decodificar se a entrada não for confiável.