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

Base64 в Perl

MIME::Base64 входит в поставку Perl уже несколько десятилетий, и ничего больше не нужно. Его настройки по умолчанию рассчитаны на почту, и это многих удивляет. Результаты ниже получены на Perl 5.40 с MIME::Base64 3.16.

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

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

Здесь важны две детали. Perl различает символьные и байтовые строки, а encode_base64 нужны байты, поэтому текст сначала проходит через Encode::encode('UTF-8', ...). Строка с символами выше U+00FF падает с Wide character in subroutine entry, а строка только из символов Latin-1 вроде é молча кодируется как Latin-1, а не UTF-8.

Вторая деталь – аргумент '', о нём дальше.

Аргумент окончания строки

encode_base64($bytes, $eol) переносит вывод через 76 символов и добавляет $eol к каждой строке, включая последнюю. По умолчанию это "\n":

print encode_base64('hi');        # "aGk=\n"
print encode_base64('x' x 60);
# eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
# eHh4

Для тел писем в MIME это правильно, а почти для всего остального – нет: для HTTP-заголовков, JSON, URL и полей базы данных. Передайте '', чтобы получить одну строку, или "\r\n" для окончаний строк строго по RFC 2045.

Для HTTP-заголовка это выглядит так:

print "Authorization: Basic ", encode_base64('user:pass', ''), "\n";
# Authorization: Basic dXNlcjpwYXNz

Декодированию окончания строк безразличны: decode_base64("aGVs\r\nbG8K") возвращает "hello\n", так что тела MIME и с \n, и с \r\n декодируются напрямую.

URL-safe Base64

MIME::Base64 по запросу экспортирует encode_base64url и decode_base64url. Кодировщик отбрасывает дополнение, а декодер принимает строки без него:

use MIME::Base64 qw(encode_base64url decode_base64url);

print encode_base64url('<<???>>'), "\n";   # PDw_Pz8-Pg
print decode_base64url('PDw_Pz8-Pg'), "\n"; # <<???>>

Если нужен URL-safe вывод с дополнением, переведите стандартную форму через tr:

my $padded = encode_base64('<<???>>', '') =~ tr{+/}{-_}r;   # PDw_Pz8-Pg==

Флаг /r (Perl 5.14+) возвращает изменённую копию. Какие системы ждут какой вариант, объясняет статья Base64 и Base64URL.

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

Открывайте файлы в режиме :raw, чтобы никакой слой не преобразовывал переводы строк или кодировки:

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;

Большие файлы по частям

Читайте блоками, кратными 57 байтам. 57 байт кодируются ровно в 76 символов, поэтому каждая часть даёт целые строки MIME, а склеенный вывод совпадает с кодированием всего файла сразу:

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;

Результат декодировался обратно в исходный файл на 300 КБ через base64 -d. Для вывода в одну строку используйте encode_base64($buf, '') с любым размером блока, кратным 3. Модуль умеет и сообщать размеры без кодирования: MIME::Base64::encoded_base64_length('hello') возвращает 9 (вместе с переводом строки), а decoded_base64_length('aGVsbG8=') – 5. Формула приведена в статье о росте размера.

В командной строке хватает двух однострочников:

perl -MMIME::Base64 -0777 -ne 'print encode_base64($_, "")' < file.bin
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < file.b64 > file.bin

Проверка: decode_base64 никогда не жалуется

decode_base64 снисходителен намеренно. Он отбрасывает всё, что вне алфавита, останавливается на дополнении и возвращает то, что получилось:

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 "<<???>>"

Последний случай опасен: URL-safe строка, переданная стандартному декодеру, молча даёт неправильные байты. Для токенов используйте decode_base64url. Для недоверенных данных проверьте алфавит и длину, а затем убедитесь, что кодирование туда и обратно сходится:

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

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

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

  • Перевод строки в конце заголовка. "Authorization: Basic " . encode_base64("$user:$pass") заканчивается на \n, и запрос ломается. Передавайте ''.
  • Двойное кодирование. При use utf8 и выходном слое :encoding(UTF-8) печать декодированных байтов без decode('UTF-8', ...) даёт кракозябры вроде héllo.
  • Base64 как секрет. Его может раскодировать кто угодно. См. кодирование и шифрование.

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

Почему encode_base64 добавляет перевод строки?

Второй аргумент – окончание строки, и по умолчанию это "\n". Вывод переносится через 76 символов и всегда заканчивается этой строкой. Передайте '', чтобы получить результат в одну строку.

Что значит «Wide character in subroutine entry»?

Вы передали в encode_base64, который принимает только байты, символьную строку с кодовыми точками больше 255. Сначала закодируйте текст: encode_base64(encode('UTF-8', $text), '').

Проверяет ли decode_base64 входные данные?

Нет. Он игнорирует символы вне алфавита и возвращает всё, что сумел декодировать, так что decode_base64('aGk=!!') даёт hi. Если входным данным нельзя доверять, проверьте строку регулярным выражением перед декодированием.

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