Вступление
Изначально для передачи электронной почты в Интернет использовался только
текст (RFC822). Затем, с развитием компьютерных девайсов, потребовалась
возможность передачи нетекстовой информации: аудио, видео, графических файлов,
файлов приложений и т.д. Однако почтовые сервера как понимали только текст, так
и остались понимать только его. Поэтому появилась необходимость каким-то образом
преобразовать двоичный файл в текстовый. Вообще-то способ такого преобразования
уже имел место - это UUE кодирование. Но появился еще один - base64. Этот способ
используется в спецификации MIME (RFC2045-2049).
Что такое MIME? Если говорить вкратце, то это стандарт описания заголовков
e-mail сообщений. Используя этот стандарт, в одном письме можно отправить сразу
несколько различных вложений. Например, можно положить в аттачмент письма
архивированный файл, оформить сообщение как просто текст, а также поместить
HTML-страницу. И все это отправить получателю. Почтовая программа-получатель,
понимающая MIME, совершенно свободно из файла электронной почты (который на
самом деле является "обычным" текстовым файлом) извлечет архив, покажет
сообщение и обработает тэги HTML. Некоторые почтовики, например Outlook Express,
на радость вирмейкерам без спроса пользователя еще и запустят вложенные в
HTML-страницу скрипты.
Идеология base64
Как известно, байт состоит из восьми битов :)
В один байт можно вложить 256 цифр, от 0 до 255. Однако, если вместо восьми
байт использовать только шесть, то объем вложенной информации уменьшается до 64
цифр, от 0 до 63. Теперь главное: любую цифру 6-ти битового байта можно
представить в виде печатного символа. 64 символа это не так много, us-ascii
символов вполне хватит. Ниже представлен 64-х символьный base64 "алфавит".
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
где код символа A - 0, а код символа / - 63. Вроде, понятно. Что дальше?
А далее берутся три последовательных байта по восемь бит (всего 24 бита), и
побитно делятся на четыре 6-ти битных байта (всего 24 бита). Немного странно
звучит: "шестибитный байт". На самом деле бит восемь, однако используются только
6 младших бит, два старших бита игнорируются. Схематично такое "деление три к
четырем" можно представить себе так:
В приведенном примере три числа 103, 193 и 58 были закодированы в base64
формат. В результате мы получили 4-х символьный стринг Z8E6. Т.о. на практике
увидели идеологию перевода двоичной информации в текст по принципу 3 к 4.
Основываясь на этом принципе, мы можем закодировать любую двоичную информацию
в текст, причем не очень сильно увеличивая ее объем (на 30%). Затем наша
информация через почтовый сервер попадет к нужному адресату, почтовик которого
декодирует текст в двоичный файл.
Все довольны, все смеются.
"Но..", скажите Вы, - "что делать, если у нас нет трех байтов? Есть только
один или два!" В этом случае в конец четырех символьного стринга добавляется
символ = (равно). Если не хватает (до трех) одного байта, то добавляется один
символ "равно":
Z8E=
если не хватает двух байт, то добавляются два символа "равно":
Z8==
Что радует: с символами "равно" надо разбираться только один раз - при чтении
конца файла. На этом вроде бы все... Нет, еще не все. Формат base64 имеет
ограничение - общая длина строки, состоящая из 4-х символьных стрингов
составляет 72 символа (за исключением самой последней строки - там уже сколько
получится).
Для тех, кто не особо понял, о чем выше шла речь, рекомендую сделать
следующее:
- Имеющейся почтовой программой сохранить в файл какое-нибудь письмо. Письмо,
естественно, надо выбрать со вложенным файлом. При сохранении письма, тип файла
следует выбрать "почтового формата" (e-mail message), например *.msg или *.eml
- Включить самый мощный текстовый редактор - Notepad ("Блокнот") и открыть
сохраненный файл письма. Тем, у кого п.2 не получился с первого раза - тип файла
при открытии его "Блокнотом" надо указать "Все файлы" (*.*)
- В "Блокноте" (а если файл большой, то в WordPad'е) откроется примерно
нижеследующее:
Date: Sat, 12 Oct 2002 10:09:59 +0000
From: "Mr.Dark"
X-Mailer: The Bat! (v1.47 Halloween Edition)
Reply-To: "Mr.Dark"
Organization: Dark Company
X-Priority: 3 (Normal)
Message-ID: <5614244603.20021012100959@online.ru>
To: =?koi8-r?B?5czFzsEg5snMydDQz9fB?=
Subject: Base64
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="73121211AFBA8E3"
73121211AFBA8E3
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit
Hello еМЕОБ,
Best regards,
Mr.Dark mailto:dark@online.ru
Delphi-РТПЗТБННЙТПЧБОЙЕ http://www.inta.portal.ru/dark/
73121211AFBA8E3
Content-Type: image/jpeg; name="ex1.jpg"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="ex1.jpg"
/9j/4AAQSkZJRgABAQEBLAEsAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP
ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e
Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCADFApsDASIA
AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
============================вырезано==================================
FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQA
UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/Z
73121211AFBA8E3
Полный текст можно посмотреть в файле EMail.txt
Content, boundary и все остальное, что идет после строки Mime-Version: 1.0 -
это стандарт MIME. Буквы, написанные в koi8-r, читаются в виде абракадабры. И
это правильно, т.к. кодировка настоящего документа установлена в windows-1251.
Если в Вашем броузере установить кодировку koi8-r (в Internet Explorer 5.0 в
главном меню выбрать "Вид"-"Вид кодировки"-"Кириллица (КОИ8-Р)"), то эти буквы
можно будет прочитать. Остальной текст, правда, разобрать вряд ли удастся :) В
конце текста письма находится очень большой блок еще одной абракадабры. В
приведенном тексте он немного порезан. Так вот этот блок и есть двоичный файл
закодированный base64 в текст. Чуть выше "большого блока" видно, что для
кодировки применялся base64, видны также названия файла. То, что выше - реальное
название файла и его тип: 'ex1.jpg', тип файла image/jpeg, которое пониже -
название этого файла в окне аттача письма. Теперь, надеюсь, все понятно. Да,
еще. Немного отвлекаясь от base64, скажу, что чтение заголовков писем -
наиувлекательнейшее занятие. Например, с помощью одной из моих программ
(IPScaner, лежит на моем сайте в разделе "Программы"), по заголовку письма можно
очень даже конкретно определить географическое место отправки e-mail. Однажды
вычислил даже номер кабинета в одном из московских институтов, откуда ко мне
пришло письмо.
Алгоритм base64 кодирования и декодирования.
Наверное существует самый оптимальный и быстрый алгоритм кодирования и
декодирования base64. Но... Почему-то хочется в очередной раз самому изобрести
велосипед... Нисколько не претендуя на оптимальность, скорострельность и
оригинальность... Итак, рассматривая идеологию base64, первое, что приходит в
голову - это устроить небольшой битовый конвейер. Т.е. (для случая кодирования)
взять 8-ой (старший) бит исходного байта и поместить его в начало 6-ти битового
байта (по поводу термина "6-ти битовый байт" см. выше). Затем на место 8-го бита
исходного байта поместить 7-й бит, а в 6-ти битовом байте первый бит (младший)
переместить на место 2-го бита. После такого перемещения освобождается первый
(младший) бит 6-ти битного числа. В него и поместим старший (бывший седьмой) бит
исходного байта. Затем еще раз передвинем биты в обоих байтах. И т.д. Примерно
так:
Рассмотрим шаг 1.
Как известно стандартные операнды Паскаля AND и OR могут выступать в двух
ипостасях: битовая арифметика и логика. В первом шаге используем операнд AND как
битовую ипостась :)
Для проверки установки старшего бита исходного 8-ми битного байта наложим на
него так называемую "маску". Т.е. применим к нему побитовую операцию AND с
числом 128 (10000000).
Как видно из приведенной схемы, проверить установку старшего бита совсем
несложно. Если результатом операции получается число 128, значит бит установлен,
а если в результате получаем 0, значит старший бит не установлен. Далее в
зависимости от полученного результата применим к 6-ти битовому байту битовую
операцию OR с числом 1, которая в любом случае применения устанавливает первый
(младший) байт в 1.
Здесь необходимо учитывать то, что перед применением побитовой операции OR с
числом 1 младший бит 6-ти битового байта всегда 0. В случае для первых двух
шагов потому, что мы сами обнуляем его при инициализации, для последующих шагов,
потому что применяем операцию SHR.
Итак, посмотрим как выглядит реализация первого шага в Паскале:
if (Source and 128) = 128 then Destination := Destination or 1;
|
где Source - 8-ми битный байт источник, а Destination - 6-ти битный байт
приемник.
Шаг 2.
Сместим первый бит 6-ти битного байта на вторую позицию, одновременно обнуляя
его первый бит.
Destination:=Destination SHR 1;
Сместим седьмой байт 8-ми битного байта на 8-е(старшее) место.
Source:=Source SHR 1;
Операция X SHR Y, смещает в байте X биты на Y-мест влево и автоматически
устанавливает Y-младшие байты в 0, следовательно значение числа изменяется.
Поэтому в реальном коде мы будем работать не с самим байтом-источником, а с его
временной копией.
Дальнейшие шаги.
Повторяем шаги 1 и 2 шесть раз для каждого бита 6-ти битного байта.
Одновременно необходимо следить за тем, что если мы обработали восемь бит
байта-источника, следует перейти к новому байту. И последнее - если обработаны
все три байта-источники, следует выйти из функции кодирования.
Теперь о декодировании.
Декодирование по сути ничем не отличается от кодирования. Делаем тоже самое,
только источник и приемник меняются местами. А битовый конвейер должен
продолжать работать в ту же сторону - справа - на лево: проверяем установку
первого бита 6-ти битного байта (маску в этом случае надо накладывать с числом
32 (0010000) - не восемь битов, а шесть) и в зависимости от результата
устанавливаем (или не устанавливаем) младший байт 8-ми битного байта. Затем
делаем побитовый сдвиг влево.