Письма с вложениями (на начало урока 6)
Регистрационная метка
Выходим в Интернет (на оглавление книги)
На начало урока 6

На этой странице — конспект беседы ЯНЗ (Якова Наумовича Зайдельмана) с Куками по теме:

Как кодируются электронные письма?

ЯНЗ. Поговорим о кодировках в электронной почте.

Согласно стандарту 1982 года RFC822, в письмах допускались только символы из 7-битного набора ASCII, поэтому ни о каком разнообразии кодировок речи там, естественно, не было. Как мы уже обсуждали, этого было достаточно для пересылки сообщений только на английском языке.

Для других языков, даже использующих латинский алфавит, тексты кодировались с помощью различных вариантов расширенной ASCII. Возникло вполне естественное желание использовать расширенную ASCII и в электронной почте.

Но на пути этого желания оказалось два препятствия.

Первым препятствием стало согласование кодировок. Чтобы правильно прочитать письмо, получатель должен был знать, в какой кодировке оно написано, а никаких стандартных средств для этого не было. Впрочем, особых проблем это не вызывало, так как количество различных кодировок было сравнительно невелико.

Вася. У меня сложилось впечатление, что русских кодировок всегда было много. Или на заре электронной почты русских писем ещё не было?

ЯНЗ. Были, но в начальный период развития электронной почты проблема русских кодировок стояла не столь остро. Первые почтовые системы создавались на базе UNIX-систем, где в качестве стандартной русской кодировки использовалась КОИ-8. Она и стала первым стандартом для русских электронных писем. Предполагалось, что письма на русском языке пересылаются только в КОИ-8, и примерно до середины 1990-х годов так оно и было.

Петя. А как же пересылали письма пользователи не UNIX-систем?

ЯНЗ. Самой популярной почтовой программой, работающей не в UNIX, был тогда пакет UUPC для MS DOS. Ранние версии этого пакета всю входящую почту безоговорочно перекодировали из КОИ-8 в альтернативную кодировку (CP866), а всю исходящую — из CP866 в КОИ-8. Таким образом, пользователь читал и писал письма в кодировке, соответствующей его системе, а пересылались они в КОИ-8.

Петя. А если пользователю UUPC приходило письмо на французском или немецком языке?

ЯНЗ. Если это письмо использовало 8-битную кодировку (например, ISO8859-1), обработка его оказывалась сложной. У почтовой системы не было возможности отличить русский текст от какого-то другого, поэтому оно тоже перекодировалось по правилам преобразования КОИ-8 в CP866. Понятно, что диакритические символы при этом искажались, и для их восстановления пользователю приходилось самому выполнять обратное преобразование.

Вася. И это называется отсутствием особых проблем!

ЯНЗ. Я же не сказал, что проблем не было совсем. Их практически не было у тех, кто всегда получал письма в одной и той же кодировке, а таких пользователей — большинство.

Петя. Было сказано о двух препятствиях, а названо только одно.

ЯНЗ. Вторым препятствием стали почтовые серверы. Поскольку стандарт RFC822 утверждал, что письма состоят из 7-битных символов, многие серверы не пропускали в сообщениях 8-битные символы. Чаще всего старший бит просто отбрасывался.

(Между прочим, обрезание бита стало одной из причин, по которой именно КОИ-8 утвердилась в качестве стандарта для русской почты. Если помните, эта кодировка устроена так, что при отбрасывании старшего бита русские буквы превращаются в соответствующие им латинские. Получается довольно уродливо, но понять смысл текста всё-таки можно.)

Для решения указанных проблем был разработан стандарт MIME, дополняющий указанные в RFC822 основные правила пересылки писем. Отдельные элементы нынешнего MIME появлялись постепенно с конца 1980-х годов, сам термин MIME появился в сентябре 1992 года в RFC1341, а ныне действующая версия этого стандарта описана в RFC2045-RFC2049, принятых в ноябре 1996.

Вася. Напомните, пожалуйста, как расшифровывается аббревиатура MIME.

ЯНЗ. Multipurpose Internet Mail Extensions — многоцелевые расширения для почты в Интернет.

Стандарт MIME ввёл несколько новых полей, наиболее интересные из них — Content-Type и Content-Transfer-Encoding.

Поле Content-Type, в полном соответствии с его названием, указывает тип содержимого письма. В этом поле может быть указано, что письмо содержит, например, изображение или звуковой файл.

Вася. Помнится, раньше мы говорили, что в письмах могут быть только тексты.

ЯНЗ. Не совсем так. Я говорил, и не отказываюсь от этих слов, что любая информация в письмах передаётся в виде текста. Но изображение, даже закодированное в виде текста, содержательно остаётся изображением. И поле Content-Type показывает именно содержательную сущность письма.

Согласно стандарту RFC2045, в поле Content-Type обязательно указываются тип и подтип информации в письме. В качестве типа можно использовать слова text — текст, image — изображение, audio — звук, и некоторые другие. Этот список может расширяться, и отдельный стандарт (RFC2048) описывает правила регистрации новых типов.

Подтип задаёт дополнительную информацию о формате представления информации. Например, image/jpeg — изображение в формате JPEG, Здесь image — тип, jpeg — подтип.

Вася. Понятно. Подтип — это просто формат файла.

ЯНЗ. Стандарт этого не утверждает, но в большинстве случаев дело обстоит именно так.

Вася. А какие могут быть подтипы у текста?

ЯНЗ. Чаще всего используются два подтипа: plain и html. Формат text/plain означает самый обычный текст без каких-либо специальных элементов оформления, text/html — текст, оформленный по правилам языка HTML, который обычно используется для создания веб-страниц.

Петя. А где же указывается кодировка?

ЯНЗ. Как я уже сказал, тип и подтип в поле Content-Type указываются обязательно. Кроме того, это поле может содержать дополнительные параметры, имена и смысл которых определяются отдельно для каждого типа данных. Для типа text можно указывать параметр charset, который и задаёт собственно кодировку.

Давайте ещё раз посмотрим на пример письма с заголовками.

From yz100@yandex.ru Mon Jun 06 22:49:15 2005
Received: from mx18.yandex.ru ([213.180.200.18]:4363)
        by pier.botik.ru with esmtp (Exim 4.34)
        id 1DfMep-0005SR-G7
        for yz@pereslavl.ru; Mon, 06 Jun 2005 22:49:14 +0400
Received: from yz.pereslavl.ru ([193.232.174.148]:45323 "EHLO yz.pereslavl.ru"
        smtp-auth: "yz100" TLS-CIPHER: <none> TLS-PEER-CN1: <none>)
        by mail.yandex.ru with ESMTP id S3375477AbVFFSsy (ORCPT
        <rfc822;yz@pereslavl.ru>); Mon, 6 Jun 2005 22:48:54 +0400
Date:   Mon, 6 Jun 2005 22:48:49 +0400
From:   Yakov Zaidelman <yz100@yandex.ru>
X-Mailer: The Bat! (v2.12.03) Business
X-Priority: 3 (Normal)
Message-ID: <938714119.20050606224849@yandex.ru>
To:     yz@pereslavl.ru
Subject: Письмо для примера
MIME-Version: 1.0
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit
X-Botik-Recipient: yz@yz.pereslavl.ru

Это письмо написано для примера.
--
ЯНЗ
      
Видите, в заголовке присутствует строчка:
Content-Type: text/plain; charset=koi8-r

Теперь вы должны понимать не только общий смысл, но и каждое слово этой записи.

Петя. Попробую расшифровать.

Content-Type — имя поля. Это поле показывает, какого рода информация содержится в теле письма.

text — информация представлена в виде текста.

plain — обычный текст, без дополнительного оформления.

charset — указание кодировки текста.

koi8-r — конкретная кодировка, в данном случае — КОИ-8.

ЯНЗ. Всё правильно.

Вася. А что означает буква r в названии кодировки?

ЯНЗ. koi8-r — это русский вариант КОИ-8. Есть ещё украинский вариант koi8-u, в эту кодировку включены буквы украинского языка, которых нет в русском.

Теперь обратимся к полю Content-Transfer-Encoding. Это поле указывает способ передачи данных.

...
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit
X-Botik-Recipient: yz@yz.pereslavl.ru

Это письмо написано для примера.
--
ЯНЗ
      

Петя. Способ передачи данных — очень длинное словосочетание. Есть ли у него какой-нибудь короткий синоним?

ЯНЗ. Иногда английское слово encoding переводят на русский как кодировка, но это создаёт путаницу, так как термин кодировка в русском языке закрепился за термином, который по-английски называется charset — набор символов. Если вам очень хочется обойтись одним словом, я рекомендую слово кодирование. Оно, конечно, слишком похоже на кодировку, но заметить разницу всё-таки можно.

Стандарт RFC2045 описывает 4 основных способа кодирования и разрешает вводить дополнительные способы. Давайте по очереди рассмотрим все основные способы.

7bit — 7-битное кодирование. Предполагается, что содержимое письма содержит текст в кодировке US-ASCII или любую другую информацию, представленную в виде набора 7-битных символов. Есть некоторые ограничения на использование кодов, которые в ASCII считаются управляющими, но на передачу текстов это не влияет.

Кодирование 7bit не требует никаких дополнительных преобразований и может передаваться через любые промежуточные серверы.

8bit — 8-битное кодирование. Содержимое письма рассматривается как набор байтов и не подвергается никаким преобразованиям.

Вася. Получается, что это кодирование позволяет переслать любой двоичный файл?

ЯНЗ. Нет, не позволяет. 8-битное кодирование, как и 7-битное, накладывает некоторые ограничения на передачу управляющих кодов ASCII, а в двоичных файлах, в отличие от текстов, такие коды могут встречаться довольно часто.

Петя. А что происходит при попытке переслать письмо с 8-битным кодированием через сервер с 7-битным ограничением?

ЯНЗ. Зависит от сервера. В худшем случае восьмой бит будет отброшен, в лучшем — письмо будет преобразовано так, чтобы кодирование было 7-битным, но информация не терялась.

Вася. Разве такое возможно?

ЯНЗ. Возможно. Именно для этого и предназначены ещё два способа кодирования, описанные в RFC2045. Суть у этих способов одна и та же: без потери информации преобразовать произвольный набор кодов к такому виду, в котором будут использоваться только разрешённые 7-битные символы.

Речь идёт о base64 и quoted-printable

При кодировании по методу base64 исходный код разбивается на группы по 6 бит. Из каждых 3 байтов получается 4 такие группы. У группы из 6 бит может быть 26 = 64 различных значения. Каждому возможному значению поставлен в соответствие символ из стандартного набора ASCII: используются 52 латинских буквы (26 заглавных и 26 строчных), 10 цифр, а также знаки + и /. Таким образом, 3 произвольных байта кодируются 4 заведомо допустимыми символами.

Второй способ преобразования произвольного сообщения в 7-битный формат называется quoted-printable. В этом способе печатные (не управляющие) символы основного набора ASCII не изменяются, а управляющие символы и символы второй половины расширенной кодовой таблицы заменяются на знак равенства и двузначный шестнадцатеричный код заменяемого символа. Стандарт подробно описывает различные детали и особые случаи, но для общего представления достаточно того, что я сказал.

Петя. Давайте попробуем закодировать что-нибудь в base64 и quoted-printable, чтобы лучше понять, как работают эти системы.

ЯНЗ. Хорошо. Давайте преобразуем короткое сообщение “Привет!”.

Петя. Сначала надо заменить все символы сообщения их двоичными кодами, а коды зависят от кодировки. В какой кодировке мы будем писать сообщение?

ЯНЗ. Давайте считать, что наше сообщение записано в кодировке Windows-1251.

Петя. Смотрю в кодовую таблицу и выписываю коды нужных символов, заменяя их двоичными значениями. Вот что у меня получилось:

Символ П р и в е т !
16-ричный код CF F0 E8 E2 E5 F2 21
Двоичный код 11001111 11110000 11100100 11100010 11100101 11110010 00100011

ЯНЗ. Пока всё правильно.

Петя. Для кодирования по системе base64 разбиваю биты на группы по 6… Не делится! В исходном сообщении 7 символов, 56 бит, они не делятся на группы по 6! Что делать?

ЯНЗ. В стандарте такая ситуация предусмотрена. В этом случае нужно дополнить последнюю группу нулевыми битами до 6, а к полученному после кодирования тексту приписать знаки “=” — по одному на каждую пару добавленных нулевых битов.

Петя. Ясно. Значит, получаются такие группы по 6 бит:

110011 111111 000011 100100 111000 101110 010111 110010 001000 110000
      

Преобразуем эти двоичные числа в десятичные:

51 63 3 36 56 46 23 50 8 48
      

Теперь в таблице кодирования (стандартная ASCII) находим соответствующие символы, а в конце добавляем два знака равенства, потому что мы дописали 4 бита. Получается такой код:

z/Dk4uXyIw==
      

ЯНЗ. Всё сделано совершенно верно.

Вася. Теперь моя очередь. Я буду кодировать это сообщение в quoted-printable.

Все русские буквы относятся ко второй половине кодовой таблицы, значит, их надо заменять шестнадцатеричными кодами. Восклицательный знак — печатный символ ASCII, его кодировать не надо. Вот что у меня получилось:

=CF=F0=E8=E2=E5=F2!
      

ЯНЗ. И здесь всё сделано правильно.

Вася. По-моему, преобразование в quoted-printable проще и удобнее. Да и восстанавливать закодированный текст легче: никакой возни с битами, просто декодируешь символ за символом. А если попадаются печатные символы ASCII, то и декодировать нечего! Не понимаю, зачем нужно сложное кодирование base64, если существует более простой вариант.

ЯНЗ. В реальной жизни кодирование не выполняется вручную. Для всех преобразований написаны программы, и компьютеру, который их выполняет, манипулировать с битами ничуть не сложнее, чем кодировать символы по одному.

Зато код base64 — более экономный. Смотрите: исходный размер нашего сообщения — 7 байтов. После кодирования размер увеличился. Это естественно: для передачи одного и того же объёма информации 7-битных символов всегда потребуется больше, чем 8-битных. Но в base64 нам потребовалось 12 символов, а в quoted-printable — 19.

Петя. Если кодирование quoted-printable настолько неэкономно, зачем оно тогда нужно?

ЯНЗ. Давайте посчитаем. Кодирование base64 всегда превращает 3 байта исходного сообщения в 4 байта закодированного. Таким образом, объём увеличивается на одну треть.

Петя. Всего на одну треть! А quoted-printable заменяет каждый символ на три, объём увеличивается в три раза!

ЯНЗ. Ты забываешь, что quoted-printable заменяет не все символы. При кодировании русских сообщений объём действительно вырастает почти втрое. Но если количество символов, которые нужно кодировать невелико, например, в текстах на западноевропейских языках, увеличение объёма может оказаться незначительным.

Для полноты картины я должен добавить, что все описанные правила применяются только к телу письма. В заголовках применяются немного другие правила преобразования, описанные в отдельном стандарте — RFC2047.

Вася. Так ведь в заголовках всё по-английски! Зачем там что-то дополнительно кодировать?

ЯНЗ. Не всё. В полях Subject (тема), From (отправитель), To (получатель) и некоторых других могут встречаться произвольные символы. Стандарт предлагает записывать такие поля специальным образом, указывая в одной строке и кодировку и метод кодирования.

Не буду рассказывать об этих правилах подробно, ограничусь двумя примерами. В письме с темой “Привет!” указание темы может выглядеть, например, так:

Subect: =?windows-1251?B?z/Dk4uXyIw==?=
      
Или вот так:
Subect: =?windows-1251?Q?=CF=F0=E8=E2=E5=F2!?=
      

Вася. Я помню, что в письме, которые мы рассматривали, когда разбирали общую структуру заголовков, ничего подобного не было, тема указывалась как нормальный русский текст.

ЯНЗ. Формально это нарушение стандарта: 8-битные символы в заголовках не разрешены. Но на практике они часто удобнее, поэтому я обычно отключаю MIME-кодирование заголовков. Правда, некоторые серверы и даже клиенты (почтовые программы) некорректно работают с такими заголовками. Конечно же, в этом случае от 8-битных заголовков надо отказаться и действовать по стандарту.

На начало урока 6

Азы информатики RU 2000/2006 © А.А.Дуванов

Вверх Оглавление книги Урок 6. Письма с вложениями Письмо автору Об авторах