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

Base64 em Perl

O MIME::Base64 vem com o Perl há décadas e é tudo de que você precisa. Os padrões dele são pensados para e-mail, o que pega muita gente de surpresa. As saídas abaixo vêm do Perl 5.40 com MIME::Base64 3.16.

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 utf8 e uma camada de saída :encoding(UTF-8), imprimir bytes decodificados sem decode('UTF-8', ...) mostra mojibake como hé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.

Continue lendo