Delphi World - Base64 для непродвинутых
Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Base64 для непродвинутых

Автор: Александр Терехов

Вступление

Изначально для передачи электронной почты в Интернет использовался только текст (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 символа (за исключением самой последней строки - там уже сколько получится).

Для тех, кто не особо понял, о чем выше шла речь, рекомендую сделать следующее:

  1. Имеющейся почтовой программой сохранить в файл какое-нибудь письмо. Письмо, естественно, надо выбрать со вложенным файлом. При сохранении письма, тип файла следует выбрать "почтового формата" (e-mail message), например *.msg или *.eml
  2. Включить самый мощный текстовый редактор - Notepad ("Блокнот") и открыть сохраненный файл письма. Тем, у кого п.2 не получился с первого раза - тип файла при открытии его "Блокнотом" надо указать "Все файлы" (*.*)
  3. В "Блокноте" (а если файл большой, то в 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-ми битного байта. Затем делаем побитовый сдвиг влево.

Теперь, когда мы теоретически подковались можно посмотреть и исходный код, который находится в файле Base64Unit.pas

Примеры.

В первом примере (b64_Example1 5К) показана работа модуля base64-кодирования (декодирования). Три восьмибитных байта кодируются в 4-х символьный стринг и обратно. Понятно, что это всего лишь пример и при вводе цифр и букв следует соблюдать base64-алфавит.

Второй пример (b64_Example2 5К) - вполне работоспособная утилита, позволяющая кодировать любой файл в текстовый base64-формат. Кроме этого утилита позволяет base64-кодированный текст декодировать в двоичный файл. Например, Вы можете "вырвать" из тела e-mail сообщения base64-фрагмент и сохранить его в текстовом файле. А затем декодировать это файл в двоичный файл. Название файла и его расширение можно увидеть в MIME-секции письма - filename.

Конечно, при кодировании надо считывать по 58 байт, а не по 3, как это сделано в примере. Считывание по 58 байт значительно увеличит "скорострельность" утилиты. Но следует учитывать, что это всего лишь пример.

Пример очередного изобретения велосипеда :)

Проект Delphi World © Выпуск 2002 - 2017
Автор проекта: Эксклюзивные курсы программирования