Next Previous Contents

Unix Review Column 37

Randal Schwartz

Апрель 2001

[предполагаемый заголовок: MIME -- это ужасная вещь]

Стандарт многоцелевых расширений почты Internet (Multipurpose Internet Mail Extensions, MIME) существует около десятилетия, но стал популярен только недавно. Это произошло скорее всего из-за появления более скоростных каналов электронной почты, а также из-за выросшей популярности Web и мощности настольных систем, которые могут делать более красивые вещи, чем просто плоский текст (или мы должны говорить text/plain)?

По моему мнению MIME является и благословением и проклятием. Это прекрасно, что я могу послать другу прикрепленный файл в формате PDF или JPEG, и знать что мне не нужно пытаться догадаться есть ли у него декодер uue или командный процессор для распаковки архива командного процессора. Он плох с той точки зрения, что большое количество почты, для которой хватало простого текста, сейчас посылается как сообщения в формате HTML или очень часто популярном формате ``multipart/alternative'' (много частей в разных форматах).

Почему это плохо? Хорошо, во первых, я не думаю, что Tim Berners-Lee или другие люди вовлеченные в создание HTML представляли его как среду для почтовых сообщений. HTML является набором гиперссылок и структурированного текста, читаемого в интерактивной среде. Электронная почта -- это простое сообщение, обычно диалоговое и в общем без нужды в разметки и ссылках.

Так наиболее часто сообщения в формате HTML используются ``рассылателями объявлений'', или более часто называемых ``спаммерами''. Это отличный способ для запихивание в наши почтовые ящики бессодержательной рекламы о бумаге для факса или о путешествии в центральную америку, с достаточно подвижными вещами, на которые мы вероятно будем отвечать. Это очень сильно раздражает.

Другой более серьезной проблемы почты в формате HTML является то, что она является переносчиком вирусов на языке Javascript. Hесчетное число раз я читал о людях пострадавших от кода встроенного в сообщения в формате HTML. Так что это является серьезной опасностью для организаций.

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

Hесомненно, что мое мнение не разделяется создателями некоторых так называемых почтовых программ, таких как Outlook Express или Netscape Communicator. Без настройки, каждое сообщение посылается с MIME-типом multipart/alternative, включая и текстовую и HTML версию документа. Теоретически, если у вас есть почтовая программа понимающая MIME, то вы получите такое сообщение как прекрасно отформатированный текст в формате HTML. Если это не так, то вы получите невнятную вторую часть сообщения. И они называют это общением.

Конечно, вы можете отключить это свойство. Вероятно. Hо читайте дальше, и вы увидите где это делается.

Теперь вот в чем проблема. У меня есть список рассылки небольшого объема организованные для класса, который я веду... ничего сложного... просто программа пересылки организованная на базе procmail. Я начал наблюдать большое количество сообщений в формате HTML, aи раздражался когда некоторые из ответов также цитировали часть разметки MIME, делая невозможным нормальное чтение сообщений.

Так что я поместил фильтр в почтовую программу, для того, чтобы отбрасывать обратно сообщения включающие boundary или html в почтовом заголовке content-type... чтобы удостоверится, что никто не будет посылать ничего кроме простого текста. Да, сразу после вставки данного фильтра, самые плохие пользователи не могли пользоваться моим списком рассылки, до тех пор пока они не разбирались как отключать посыл сообщений в формате HTML, и все стало гораздо лучше выглядеть.

В недавно организованной группе пользователей имелось некоторое количество людей установивших Outlook на Windows 2000 (не Outlook Express). Даже после того как я опросил своих друзей лучше понимающих программное обеспечение производства Redmond, они все равно не смогли определить как отключить посыл сообщений в формате HTML.

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

Затем я подумал, ``все что мне нужно -- маленький фильтр на Perl, который будет распознавать так называемую электронную почту и просто отбрасывать часть с HTML!''. И вот что я решил написать.

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

Сначала, давайте напишем немного кода для получения мозгодробильных сообщений в стандартном вводе, записывая чистые части почтовых сообщений в стандартный вывод (не изменяя их, если они не имеют правильного формата); Мы начнем с трех строк, которые начинают практически каждую программу, которую я пишу:


  #!/usr/bin/perl -w
  use strict;
  $|++;

Эти строки разрешают выдачу предупреждений, включают ограничения компилятора (запрещает наличие символьных ссылок, необъявленных переменных или неизвестных ключевых слов) и отключает буферизацию для стандартного вывода. Затем мы получаем строку ``envelope-from'' со стандартного ввода:


  my $envelope = <STDIN>;

Эта строка выглядит примерно так:


  From merlyn  Wed Jan 24 11:37:17 2001

и сообщает следующей почтовой программе о том, откуда пришло данное сообщение. В действительности данная строка не соответствует формату заголовка определенного RFC822, поскольку это ``мета-заголовок'', и поэтому не должен разбираться вместе с остальной информацией MIME. Мы запоминаем его и выдадим обратно, когда все будет готово.

Затем мы подключаем два модуля из поставки MIME::Tools:


  use MIME::Parser;
  use MIME::Entity;

И затем для чтения ввода мы создаем объект MIME::Parser:


  my $parser = MIME::Parser->new;
  $parser->output_to_core(1);
  $parser->tmp_to_core(1);

Здесь создается парсер MIME, который хранит все внутри, включая временные файлы. Конечно это может не работать, если кто-нибудь пошлет мне двухсот мегабайтный видеофайл, но я могу отследить это на предыдущем шаге.

Теперь нам надо читать стандартный ввод:


  my $ent = $parser->parse(\*STDIN);

Объект $parser считывает сообщение из стандартного ввода в память. Если здесь происходит сбой (неправильный ввод, неправильный формат), то парсер прекратит работу. Мы будем вызывать программу таким образом, что если она прекратит работу, то оригинальное сообщение все равно сохранится, так что смерть процесса не является проблемой.

Теперь обратимся к основной части. Я могу использовать доступные для сообщения методы (объект MIME::Entity) для определения его структуры. Одним из методов я просто превращаю остаток программы в:


  $ent->dump_skeleton(\*STDERR); exit 1;

Это заставляет программу показать структуру сообщений, так что я могу определить как выглядит сообщение с частью в формате HTML, сравнивая с другими сообщениями. После запуска этой программы для нескольких простых сообщений, я удалил эту строку и вставил следующий код:


  if ($ent->effective_type eq "multipart/alternative"
      and $ent->parts == 2
      and $ent->parts(0)->effective_type eq "text/plain"
      and $ent->parts(1)->effective_type eq "text/html") {

Здесь делается несколько действий. Давайте разберемся не спеша. Сначала я смотрю, имеет ли структура верхнего уровня тип multipart/alternative. Документ соответствующий стандарту MIME имеет иерархическую структуру (вложения, могут содержать вложения и так далее), так что мы сначала смотрим на корень сообщения. Если тип совпадает, то мы также убеждаемся, что имеется два варианта, первый из них является простым текстом, а второй является вариантом в формате HTML. Если все это так, то вероятно мы столкнулись с бедствием, которое я хочу устранить. (Существует небольшой шанс, что части сообщения сильно различаются, но если это так, то они неправильно обозначены как multipart/alternative вместо использования более правильного типа multipart/mixed).

Так что на следующем шаге нам необходимо выделить текстовую часть и затем представить эту часть как полное сообщение. Существуют более легкие методы выполнения этой операции, но вот что я сделал. Сначала, создаем новый объект из тела старого текста:


    my $newent = MIME::Entity->build(Data =>
                                     $ent->parts(0)->body_as_string .
                                     "\n\n[[HTML alternate version deleted]]\n");

Заметьте, что я добавил небольшую приписку к концу сообщения, чтобы пользователи знали что случилось. Я мог бы вставить его в заголовок почтового сообщения, но я хотел чтобы это замечание увидели.

Далее мы отбрасываем все части за исключением одной:


    $ent->parts([$newent]);     

И затем мы переделываем эту часть из составного документа в простое сообщение (где MIME даже не упоминается и не имеется маркеров границ):


    $ent->make_singlepart;

И в заключение, некоторые заголовки рассинхронизованы, так что теперь необходимо убрать их:


    $ent->sync_headers(Length => 'COMPUTE', Nonstandard => 'ERASE');
  }

И вот. Если это удовлетворяет критериям безобразных сообщений, то мы имеем сообщение в переменной $ent; в противном случае у нас имеется оригинальное сообщение. Теперь надо произвести его выдачу. Сначала мы выдаем обертку:


  print $envelope;

а затем само сообщение:


  $ent->print;

Следующим шагом является добавление скрипта в правило procmail для списка рассылки. До правил, которые выполняют саму рассылку я добавил следующее правило:


  :0 fw
  * ^Content-type:.*boundary
  | $HOME/lib/Strip-HTML-fork

где $HOME/lib/Strip-HTML-fork содержит саму программу. Если фильтр может выполнять свою работу, то следующее правило procmail начинается с:


  :0
  * ^Content-type:.*(html|boundary)
  {
    .. bouncing logic not shown ..
  }

больше нет правил и сообщение отправляется! Удача.

Хорошо, я надеюсь, что убедил вас, что MIME является ужасной вещью, но однажды поработав мы можем бороться с ним правильно. До скорых встреч, наслаждайтесь!


Next Previous Contents