Працюємо з mime

Хто такий MIME і навіщо нам з ним працювати?

MIME - Multipurpose Internet Mail Extension - стандарт поштових повідомлень. Поштове повідомлення це не тільки текст і вкладення, які ви звикли бачити, саме по собі поштове повідомлення складається з MIME-заголовків. які містять всю необхідну інформацію про повідомленні, зокрема дані про відправника / одержувача, тему повідомлення, текст повідомлення, файли вкладень, інформацію про кодування і методах шифрування, а також повний шлях повідомлення. Якщо у вас встановлений поштовий клієнт The Bat !. то ви легко можете подивитися на нутрощі будь-якого поштового повідомлення, для цього виберіть будь-який лист і натисніть меню Спеціальне => Оригінальний текст листа. або клавішу F9 (див. рис. 1).

Специфікація MIME

Стандарт MIME докладно описаний в RFC-тисячі триста сорок одна і є розширюваною стандартом, нижче я описав основні поля MIME.

Тип multipart вказує на те, що повідомлення містить змішаний тип даних, тобто одне повідомлення може містити один або кілька з вище описаних типів даних. Тип multipart має 4 основних підтипи: mixed. alternative. digest. parallel.

Підтип mixed визначає повідомлення, що складається з декількох (multi) частин (part), розділених один від одного кордоном (boundary). Кордон визначається в параметрі boundary в поле Content-Type. Кордон являє собою набір ASCII-символів. Кожна частина повідомлення, крім основних даних, може містити свої заголовки, що визначають тип контенту і інформацію про вашого засобу перевірки правопису. Частини повідомлення відокремлені один від одного ім'ям кордону, причому ім'я кордону в тексті листа завжди починається з символів -. а остання межа також додатково закінчується символами -.

Щоб вам було зрозуміліше, про що йде мова, подивіться на наступний фрагмент MIME:

Content-Type: multipart / mixed; boundary = "moia granica"
--moia granica
Content-Type: text-plan

Hello! Is sample my boundary!

--moia granica
Content-Type: text / plan

А це наступна частина повідомлення!

Як бачите, це повідомлення має тип вмісту multipart з підтипом mixed. тут вказано ім'я кордону - moia granica. Повідомлення складається з двох частин, кожна частина має тип text з підтипом plan. Перша частина повідомлення містить текст: «Hello! Is sample my boundary! », А друга частина повідомлення містить текст:« А це наступна частина повідомлення! ». Скінчено, текстове повідомлення ніхто ділити на частини не буде, звичайно це робиться, якщо в повідомленні присутні будь-які вкладення (аттачі).

Підтип alternative ідентичний підтипу mixed. проте кожна частина повідомлення являє собою повідомлення оптимізоване під можливості поштового клієнта. Наприклад, повідомлення може складатися з декількох частин, одна частина буде містити текстової контент text / plan. інша - гіпертекст text / html. в даному випадку, якщо у клієнта поштова програма не підтримуватиме html, то відобразитися перша частина повідомлення (text / plan), в іншому випадку - друга (text / html).

Підтип parallel ідентичний підтипу mixed і призначений головним чином для відображення одночасно всіх частин повідомлення.

Це основні підтипи multipart. їх кількість, як я вже раніше говорив, може збільшуватися.

Тип message головним чином використовується у випадках, коли повідомлення не може бути передано повністю. Основними підтипами даного типу є: partial - вказує на те, що повідомлення розділене на частини, при цьому, в параметрах поля Content-Type вказується кількість частин (total), номер частини (number) і ідентифікатор (id); external-body - дозволяє посилатися на зовнішні джерела.

Слід також зазначити, що поле Content-Type може містити параметр charset. який містить інформацію про вашого засобу перевірки правопису, це можуть бути windows-1251, kio8-r і т.п. У разі якщо повідомлення містить вкладення, то Content-Type також може мати параметр name. в якому міститься ім'я файлу вкладення, наприклад:

Content-Type: application / x-zip-compressed; name = "MyFile.zip"

Як ви вже, напевно, помітили, всі параметри відокремлені один від одного крапкою з комою (;), при цьому, кожен параметр може бути написаний на окремому рядку, а також, значення параметрів може бути укладено в лапки, хоча це зовсім не обов'язково. Це може викликати деякі проблеми при написання MIME-рідера (MIME Reader), але тим не менш цю особливість потрібно врахувати.

Тип кодування повідомлення (Content-Type-Encoding)

Поле Content-Type-Encoding містить інформацію про використаний типі кодування повідомлення. Існує 6 основних типів кодування: Base64. Quoted-Printable. 7Bit. 8Bit. Binary. X-Token. Типи кодування 7Bit. 8Bit і Binary не вимагають ніякого перетворення, оскільки дані передаються по байтам.

Тип кодування Base64 - позиційна система числення з основою 64, де 64 - максимальна ступінь двійки, яка представляється з використанням ASCII-символів. Кодування Base64 використовує символи A-Z, a-z і 0-9, в MIME також використовуються символи «+», «/» і «=».


Тип кодування Quoted-Printable є порядок символів в шістнадцятковому вигляді, при цьому кодуються тільки символи ASCII-код яких перевищує 122, а решта символи залишаються як є. Перед закодованими символами ставить знак «=».


Тип кодування X-Token дозволяє користувачеві самому задавати правила кодування.

Поле Subject містить тему повідомлення.

Subject: Тема повідомлення

From: [Ім'я відправника <]email@отправителя.ru[> (Компанія)]

Для назви компанії також може використовуватися окреме поле Organization.

To: [email protected]
From: Немиро Олексій
CC: User
Organization: www.Kbyte.Ru

Слід також згадати про поле Received. яке містить інформацію про шляхи сполучення. Полів Received може бути кілька, їх кількість залежить від кількості серверів, через яке проходить повідомлення до кінцевого одержувача, кожен сервер залишає інформацію про себе в цьому полі.

Ще одне цікаве поле - X-Mailer. яке містить назву поштового клієнта, через який було відправлено повідомлення.

Дату відправлення повідомлення можна дізнатися в поле Date.

Поле X-Priority містить позначку про пріоритет листи, як правило, це числове значення, або комбінація числового і буквеного значення. Лист може мати такі пріоритети:

2 (High) - високий пріоритет
3 (Normal) - нормальний пріоритет
4 (Low) - низький пріоритет

При наявності вкладень (аттачей) в повідомленні також може бути присутнім поле Content-Disposition. яке містить опис вкладення (аттачем), зокрема ім'я файлу, наприклад:

Content-Disposition: attachment; filename = "MyFile.rar"

Кількість полів MIME також може збільшуватися, тут я розповів про основні полях, які найчастіше можна зустріти в повідомленнях.

Зверніть увагу, всі значення полів можуть бути також зашифровані, зазвичай для шифрування використовуються типи кодування Base64 і Quoted-Printable. а також, може бути вказано кодування тексту, наприклад: windows-1251. kio8-r. utf-8 і т.п. Якщо значення поля зашифровано, то воно записується в наступному форматі:

=? кодова сторінка. тип кодування. значення поля? =

Кодова сторінка - це, власне, і є windows-1251. kio8-r. utf-8 і т.п.
Тип кодування - представляє перший символ назви типу кодування, це може бути або B - Base64. або Q - Quoted-Printable.

Значення поля - це закодоване зазначеним типом кодування значення поля.

Тут слід зазначити, що в Quoted-Printable перетворюються лише українські символи, тобто символи з кодом більше 122, решта символи записуються як є, при цьому перед кожним закодованим символом ставиться знак «=». А в Base64 кодується весь текст.

читаємо MIME

І так, для початку спробуємо написати функції дешифрування тексту з Base64 і Quoted-Printable. Почнемо з простого і напишемо функцію дешифрування Quoted-Printable. Як я вже говорив, Quoted-Printable перетворює деякі символи в шістнадцятковий код і перед кожним перетвореним символом ставить знак «=», на цьому і будемо грунтуватися:

Private Function QPDecode (ByVal sText As String) As String
If sText.Length <= 0 Then Return ""
Dim sResult As String = ""
Dim chrCurrentChar As Char
Dim intTextLength As Integer = sText.Length
Dim i As Integer = 1

Do While i <= intTextLength
'Беремо символ
chrCurrentChar = Convert.ToChar (Mid (sText, i, 1))
'Якщо починається з =, то це шістнадцятковий код
If chrCurrentChar = "=" Then
Try
'Беремо два наступних символу
'І намагаємося перетворити в десяткове число,
'А потім в символ
sResult + = Chr (CInt ( "H0" Mid (sText, i + 1, 1) _
Mid (sText, i + 2, 1)))
i + = 3
Catch ex As Exception
'Це звичайний символ, залишаємо як є
sResult + = chrCurrentChar
i + = 1
End Try
Else
'Це звичайний символ, залишаємо як є
sResult + = chrCurrentChar
i + = 1
End If
Loop

'Повертаємо дешифрований текст
Return sResult
End Function

Як бачите, нічого складно в цьому немає, дана функція запросто перетворює текст Quoted-Printable в звичайний. Перейдемо до Base64. Текст, зашифрований в Base64. складається з послідовності великих і маленьких символів англійського алфавіту, цифр, а також символів «+», «/» і «=». Для дешифрування Base64 можна використовувати наступну функцію:

Private Function Base64Decode (ByVal sText As String) As String
Dim sResult As String = ""
Dim i As Integer

For i = 1 To sText.Length
If Not InStr (1, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 + / =", Mid (sText, i, 1)) = 0 Then
sResult + = Mid (sText, i, 1)
End If
Next i

If (sResult.Length Mod 4) <> 0 Then
sResult + = StrDup (4 - (sResult.Length Mod 4), "=")
End If

Dim encoding As System.Text.Encoding = System.Text.Encoding.GetEncoding (тисячу двісті п'ятьдесят-одна)

Try
'Перетворимо в 8-розрядний масив символів
sResult = encoding.GetString (Convert.FromBase64String (sResult))
Catch ex As Exception
'помилка
End Try

Return sResult
End Function

Ця функція призначена головним чином для декодування текстових даних, щоб отримати бінарні (двійкові) дані досить пропустити отриманий результат через функцію System.Text.Encoding.GetBytes. або повернути Convert.FromBase64String (sResult).

Для отримання значень полів MIME я використовую регулярні вирази:

Private Function GetHeaderBySource (ByVal sSource As String, ByVal sHeader As String) As String
Dim myRegex As New Regex ( "((?[A-zA-Z0-9 -] *): (?.*)) | ((?[A-zA-Z0-9 -] *): s (?.*)) ", RegexOptions.Multiline)
Dim myMatchCollection As MatchCollection = myRegex.Matches (sSource)
Dim sResult As String = ""
Dim iStrt As Integer, iLngth As Integer
For i As Integer = 0 To (myMatchCollection.Count - 1)
If myMatchCollection (i) .Groups ( "key"). Value.Trim.ToLower = sHeader.Trim.ToLower Then
sResult + = (myMatchCollection (i) .Groups ( "value"). Value.Trim) vbCrLf
'Дивимося, чи є ще що-небудь після цієї групи
If i iStrt = myMatchCollection (i) .Groups ( "value"). Index + myMatchCollection (i) .Groups ( "value"). Length + 1
iLngth = myMatchCollection (i + 1) .Groups ( "key"). Index - iStrt - 1
If iStrt sResult + = Replace (sSource.Substring (iStrt, iLngth) .Trim, vbCrLf Chr (9), vbCrLf)
End If
Else
'Отримуємо все до кінця заголовка
iStrt = myMatchCollection (i) .Groups ( "value"). Index + myMatchCollection (i) .Groups ( "value"). Length + 1
iLngth = sSource.Length - iStrt - 1
If iLngth> 0 Then
sResult + = Replace (sSource.Substring (iStrt, iLngth) .Trim, vbCrLf Chr (9), vbCrLf)
End If
End If
End If
Next
If sResult.EndsWith (vbCrLf) Then sResult = Mid (sResult, 1, sResult.Length - vbCrLf.Length)
If sResult.StartsWith (vbCrLf) Then sResult = Mid (sResult, vbCrLf.Length, sResult.Length - (vbCrLf.Length + 1))
Return sResult
End Function

Для отримання даних з поля, досить вказати текст MIME і ім'я поля, значення якого потрібно отримати, наприклад:

Даний приклад поверне значення поля From. Тут слід також врахувати, що дані можуть бути зашифровані, тому слід визначити тип кодування і дешифрувати дані. Як я вже говорив, якщо дані зашифровані, то вони будуть мати такий вигляд:

=? кодова сторінка. тип кодування. значення поля? =

Це стандартний формат і змінюватися він не може, тому для отримання необхідної інформації запросто можна використовувати регулярні вирази:

Використовуючи цей синтаксис можна отримати назву кодування (windows-1251. Kio8-r і т.п.) - група encode. тип кодування (Q або B) - група type. а також значення параметра - група text. Крім цього, після зашифрованих даних, також може міститися будь-який інший текст - група othertext. Тепер, отримавши ці дані можна запросто пропустити їх через одну з раніше написаних функцій дешифрування і радіти життю ;-) Хоча ні, рано радіти, після дешифрування, потрібно перетворити текст в потрібну кодову сторінку, для цього можна скористатися функцією System.Text.Encoding. GetEncoding.

Сам текст повідомлення, або аттачем, йде відразу після заголовків, при цьому слід врахувати, що кордони заголовків визначаються наявністю в кінці послідовності двох символів CRLF. Іншими словами, заголовки закінчуються відразу після пари символів CRLF.

InStr (sMIME, vbCrLf vbCrLf)

Все інше - це текст повідомлення, яке тіло вкладення (аттачем).

Там же є невеликий приклад використання його в проектах ASP .NET.

Безумовно, написати універсальний MIME-рідер за пару годин не просто, але для вузького використання, наприклад в своїх проектах, це цілком реально.