Как декодировать quoted-printable
- Нажмите Quoted-printable → Текст над инструментом, чтобы переключиться на декодирование.
- Вставьте закодированный текст в поле ввода, например часть тела письма, скопированную из его исходника.
- Прочитайте результат. Если буквы с диакритикой выглядят неправильно, выберите в поле Кодировка под результатом ту кодировку, что указана в заголовке
Content-Typeэтой части, напримерcharset=iso-8859-1. - Нажмите Скопировать результат или Скачать. Мягкие переносы склеиваются, а каждый
=XXснова становится своим байтом.
Чтобы закодировать текст, оставьте направление Текст → Quoted-printable и введите или вставьте текст. Кодировка под полем ввода определяет, в какие байты превратятся символы за пределами ASCII.
Как устроен quoted-printable
Quoted-printable (RFC 2045, раздел 6.7) по возможности оставляет текст читаемым и экранирует только то, без чего не обойтись:
- Печатаемые символы ASCII остаются как есть, кроме
=. - Любой другой байт записывается как
=и две hex-цифры в верхнем регистре. В UTF-8 это значит, что каждый символ вне ASCII превращается в две–четыре такие последовательности. - Сам
=становится=3D. - Пробел или табуляция в конце строки становятся
=20или=09. - Строка не длиннее 76 символов. Более длинные разбиваются мягким переносом:
=в конце строки, который декодер удаляет.
| Текст | Quoted-printable |
|---|---|
café | caf=C3=A9 |
Grüße aus München | Gr=C3=BC=C3=9Fe aus M=C3=BCnchen |
100% = €5 | 100% =3D =E2=82=AC5 |
<p style="color:red">Hi</p> | <p style=3D"color:red">Hi</p> |
Caf=E9 в кодировке ISO-8859-1 | Café |
Последняя строка показывает, почему важна кодировка: =E9 кодирует один байт, который в ISO-8859-1 и Windows-1252 означает é, а в UTF-8 сам по себе некорректен.
Текстовый и двоичный режим
По умолчанию кодировщик считает ввод текстом. Переносы строк остаются настоящими переносами, а если первая строка заканчивается на CRLF, то, как и в Python binascii.b2a_qp, одиночный LF дальше тоже превращается в CRLF. Для текста письма это правильно, но байты при этом меняются.
Включите Кодировать переносы строк (двоичный режим), когда важны точные байты. Тогда переносы записываются как =0D и =0A: Hello, CRLF, World превращается в Hello=0D=0AWorld, а декодирование возвращает ровно то, что было на входе. Для настоящих двоичных данных вроде картинок всё равно лучше подходит Base64.
Quoted-printable или Base64 в письмах
Почтовая программа выбирает Content-Transfer-Encoding для каждой MIME-части отдельно. Quoted-printable хорош для текста, где почти всё в ASCII: он остаётся читаемым и мало растёт. У Base64 постоянные накладные расходы около 33%, и он выигрывает, как только экранировать нужно большинство байтов:
| Текст | Байтов UTF-8 | Quoted-printable | Base64 |
|---|---|---|---|
Grüße aus München | 20 | 32 символа | 28 символов |
Привет, мир (русский) | 20 | 56 символов | 28 символов |
Даже немецкий текст с тремя буквами вне ASCII на 17 символов в quoted-printable уже длиннее, чем в Base64, а кириллица выходит вдвое больше Base64. Чтобы получить Base64 со строками по 76 символов, как требует почта, воспользуйтесь кодировщиком MIME Base64. Статья Base64 в электронной почте объясняет, как обе кодировки выглядят в письме, включая вложения и закодированные заголовки.
Как прочитать письмо целиком
Для беглого взгляда хватит скопировать одну часть тела из исходника письма. Для полного файла .eml есть декодер EML: он разбирает заголовки, декодирует части в quoted-printable и Base64 с их объявленными кодировками и выдаёт вложения для скачивания. Quoted-printable встречается и за пределами почты, например в файлах vCard 2.1 с ENCODING=QUOTED-PRINTABLE; вставьте сюда значение после двоеточия.
Quoted-printable в коде
import quopri
quopri.encodestring("café".encode("utf-8")) # b'caf=C3=A9'
quopri.decodestring(b"caf=C3=A9 =3D 5=E2=82=AC").decode("utf-8") # 'café = 5€'
Пакет email в Python сам применяет нужное декодирование: msg.get_payload(decode=True) возвращает декодированные байты части, какой бы ни была её кодировка передачи. Про Base64 читайте на странице Python.
Вопросы и ответы
Что означает =3D в письме?
Это закодированный знак равенства. В quoted-printable = начинает escape-последовательность, поэтому сам символ = приходится записывать как =3D, то есть как его байт в hex. Особенно часто это встречается в HTML-письмах: style=3D"color:red".
Почему строки в исходнике письма заканчиваются на =?
Это мягкий перенос строки. Строка quoted-printable не может быть длиннее 76 символов, поэтому длинные строки разбиваются, а каждый разрыв помечается = в конце. Декодер склеивает куски обратно в одну строку.
Что означает =20 в конце строки?
Закодированный пробел. Почтовые системы могут обрезать пробелы в конце строк, поэтому quoted-printable защищает завершающий пробел или табуляцию, записывая их как =20 или =09.
Как декодировать quoted-printable в Python?
quopri.decodestring(data) или binascii.a2b_qp(data) возвращают байты; дальше вызовите .decode("utf-8") или ту кодировку, которую указывает заголовок Content-Type.
Можно ли декодировать темы вида =?UTF-8?Q?...?=?
На этой странице нет. Это encoded words из RFC 2047: они используют родственную кодировку Q, где пробел записывается как _. Их в заголовках расшифровывает декодер EML, который заодно декодирует тела писем в quoted-printable.