Игорь Тетерин
Если взглянуть на мир со стороны, становится абсолютно не понятно, кто и
как управляет сайтами. Информации на этот счет крайне мало, хотя, думаю,
существует достаточное количество интересных решений. Более-менее развитые
движки сайтов предоставляют веб-интерфейс управления контентом, если, конечно,
это не считается принципиальным аппендиксом. Про то, что я буду описывать,
лично мне нигде пока слышать не доводилось.
Сразу же уточним, чего мы хотим добиться. Наша задача – сделать как можно
более удобный и доступный интерфейс для наполнения сайта контентом, то есть
переложить часть рутины на скрипты и сотрудников. Значит, нам предстоит
заняться автоматизацией этого процесса.
Иногда дело доходит до смешного. Есть у человека
свой проект, и даже есть свой движок. Но для каждого нового динамического
раздела он пишет и рисует формы, пишет обработчики, вставляет проверки.
Появляется новый раздел – берем шаблон предыдущего, корректируем, правим, редизайним,
проверяем и выкладываем. И еще раз проверяем.
Такой вот обьем работы ради того, чтобы добавить
один раздел. А кто-то ведь еще работает и делает это через dial-up. Любой, кто
занимался этим, поймет – эта работа для орков. Мы же – программисты.
У нас есть свой проект, и тратить кучу времени на
разработку каждого отдельно взятого раздела мы не хотим. Лучше мы помучаемся
один раз, зато потом жизнь наша станет сладкой и динамичной.
Новый принцип автоматизации.
Основы. Сравнения
Как-то давно, когда я писал ret 0.8 (ядро для интернет-сайтов), мой друг
заметил, что было бы весьма удобно, если бы сайтом можно было управлять через
электронную почту. Тогда мы не уделили этому вопросу должного внимания. Позднее
– только вспоминали про эту технологию. И вот, наконец, я решил испробовать это
на одном разделе своего сайта http://revda.biz.
Написал простой скрипт, который читает почту из
рассылки и публикует все, что попадет, на сайт. И тут я понял, что это очень
удобно! Люди, которые писали в рассылку, просто переписывались, при этом
страница сайта была в постоянном движении. Те, кто заходил на эту страницу,
знали и понимали – за новой информацией туда можно заходить каждый день. Да,
пока это больше похоже на обычный интернет-трэд, но все же!
Как все это выглядит? Наша задача – обрабатывать
письма, которые мы будем брать с заранее созданного почтового ящика. Этот же
ящик мы можем подписать на рассылки или нечто подобное, дело даже не только в
рассылках. Мы можем дать адрес ящика нужным нам людям для того, чтобы они
присылали туда новую информацию почтой, и не приучать их пользоваться для этого
каким-либо веб-интерфейсом. Кроме того, наш сайт может брать что-то из сети
Интернет, а результат пересылать на тот же почтовый ящик.
Грубо говоря, наш почтовый ящик стал
централизованным источником информации. Этого обычно не скажешь о веб-интерфейсе,
где приходится (по большей части) кого-то напрягать, заставлять или самому
работать ручками. Более того – мы можем автоматически обрабатывать не один ящик,
а несколько, закрепив за каждым свою тематику.
Этот подход также решает вопрос вашего
присутствия в Интернете. Так как мы работаем с почтовым ящиком, то для того,
чтобы обработать материал, ответить кому-то или сделать что-то еще, нам
достаточно просто скачать почту, поработать с ней и отослать ответы.
Пока все выглядит очень красиво. Однако, чтобы
создать нечто подобное в реальности, нам придется ввязаться в битву с одним, а
может, и двумя, самыми запутанными стандартами: MIME и HTML.
Победить в битве с HTML достаточно трудно – это
довольно противоречивый и очень динамичный стандарт. Тут придется не один раз
приложить и руки, и голову. В рамках нашей задачи мы коснемся его только
слегка.
Выбор платформы, языка и оценка
задачи
На чем будет работать наш обработчик почты? Ответ очевиден, если
предположить, что все должно работать и на Windows, и на UNIX-платформах. Мы не
станем ограничивать себя чем-то одним, поэтому после разработки обработчика
сможем использовать его и там, и там. А это весьма удобно.
На чем будем писать? Немного подумаем. Учитывая,
что PHP я не знаю и знать не хочу, остается один выбор – Perl. К тому же Perl,
с моей точки зрения, концептуально более правильный язык и больше подходит на
роль стандартного. Но не это важно, важны принципы и алгоритм, а реализовывать
можно хоть на Ruby.
Ну и, наконец, перед нами стоит задача написать
обработчик почты. То есть нам потребуется автоматизировать прием, отправку,
разбор писем. Плюс к этому возникает проблема безопасности и защищенности
такого решения.
Что же нам потребуется? Один почтовый ящик, хостинг
(хотя можно и локально) с поддержкой Perl и несколькими дополнительными
модулями с CPAN. Ну и остальное «огнестрельное оружие», вроде putty, far, TheBat
и т. д.
Переходим к практике
Рассмотрим очень простой пример. Первое, что нам потребуется – подключение
нужных модулей:
use Net::POP3;use MIME::Parser;
use MIME::Entity;
use MIME::Head;
use MIME::Body;
use MIME::Words qw(:all);
use MIME::QuotedPrint;
use MIME::Base64;
Определим основные объекты и переменные:
my $parser = MIME::Parser->new;
$parser->output_to_core(1);
$parser->tmp_to_core(1);
$mail_server='127.0.0.1';
$username='login';
$password='password';
Теперь получим список писем:
$pop = Net::POP3->new($mail_server)
or die "Can't open
coonection to $mail_server :$!\n";
$pop->login($username, $password)
or die "Can't Authenticate:
$!\n";
$messages = $pop->list
or die "Can't get
listof undeleted messages: $!\n";
Начинаем обрабатывать каждое письмо:
foreach $msgid (keys %$messages)
{
$message = $pop->get($msgid);
unless (defined $message)
{
warn "Couldn't
fetch $msgid from server: $!\n";
next;
}
Следующий метод изначально был взят у Рэндола
Шварца. Если вы поищете в Сети, то найдете нечто вроде Perl-Column, мне
встречался даже перевод, правда, неполный.
Далее следует примерно такой алгоритм: если в
письме есть часть типа text/plain, то берется именно эта часть, а все остальное
игнорируется.
Таким образом, если мы встречаем письмо, где есть
HTML-часть и текст, то в качестве входящих данных берется именно текст. Если
текстовой части нет – письмо игнорируется.
$pop->delete ( $msgid
);
@message = @$message;
$ent = $parser->parse_data
( \@message );
$bodyCoding = $ent->head->mime_attr(
'Content-type.charset' );
$origType = $ent->head->get(
'Content-Transfer-Encoding',0 );
if ( $ent->effective_type
eq 'text/plain' )
{
# письмо - только
текст
$bodyCoding = $ent->head->mime_attr
(
'Content-type.charset');
$origType = $ent->head->get(
'Content-Transfer-Encoding',0
);
$body = $ent->body_as_string;
}
elsif (
$ent->effective_type eq 'multipart/alternative'
and $ent->parts(0)->effective_type
eq 'text/plain' )
{
# письмо, где первая
часть мультипар - текст
$bodyCoding = $ent->parts(0)->head->mime_attr(
'Content-type.charset' );
$origType = $ent->parts(0)->head->get(
'Content-Transfer-Encoding',0
);
$body = $ent->parts(0)->body_as_string;
}
elsif (
$ent->effective_type eq 'multipart/alternative'
and $ent->parts(1)->ffective_type
eq 'text/plain' )
{
# письмо, где вторая
часть мультипарт - текст
$bodyCoding = $ent->parts(1)->head->mime_attr(
'Content-type.charset');
$origType = $ent->parts(1)->head->get(
'Content-Transfer-Encoding',0
);
$body = $ent->parts(1)->body_as_string;
}
else {next}
chomp $origType;
Чем универсален этот код, так это тем, что в нём
происходят все стандартные MIME-декодировки – Base64 и Quoted-Printable – и
перекодирование из ISO и KOI в Windows-1251.
# Если закодировано,
декодируем его, во имя счастья
if (lc($origType) eq
'quoted-printable')
{ $body = MIME::QuotedPrint::decode($body);
}
if (lc($origType) eq
'base64')
{ $body = MIME::Base64::decode($body);
}
$bodyCoding = lc($bodyCoding);
# Перекодировка кирилицы
у тела, если надо.
if ($bodyCoding ne
'')
{
if ($bodyCoding eq
'koi8-r') {$bodyCoding = 'koi'}
if ($bodyCoding eq
'koi8r') {$bodyCoding = 'koi'}
if ($bodyCoding eq
'iso8859-5') {$bodyCoding = 'iso'}
if ($bodyCoding eq
'koi8-u') {$bodyCoding = 'koi'}
if ($bodyCoding eq 'koi'
|| $bodyCoding eq 'iso')
{ $body = encoder($body,
$bodyCoding, 'win') }
}
$subj = join( "",
map {xcode( ${$_}[1],
${$_}[0])}
decode_mimewords(
$ent->head->get('Subject',0)
)
);
$date = $ent->head->get('Date',0);
}
Собственно, все. Переменная $subj содержит тему
письма, $body – тело письма, а $date – дату. Остальные параметры письма вы
сможете легко получить, используя уже подключенные в программе модули.
Теперь вы смело можете сохранить в базе данных
полученные результаты. Я, например, сохраняю их таким образом:
use collector;
($r) = Add2Revorum ( \$subj,
\$body, \$date );
где модуль collector.pm – часть моего движка сайта, которая создает
необходимую структуру и, используя ядро ret WebOS и модуль Storable, пишет её
базу (обычные плоские файлы).
О проблеме альтернативной СУБД я напишу в другой
статье. Те, кого это заинтересовало, могут обратиться за подробностями по интернет-адресу:
http://jkeks.far.ru/ret.
Подпрограммы или процедуры, ответственные за
перекодировку:
sub xcode {
# определяем кодировку и
вызываем перекодировщик, если нужно
my ($charset, $src) = @_;
my %charsets = (
'windows-1251' =>'win',
'iso8859-5' =>'iso',
'koi8-r' =>'koi',
'koi8r' =>'koi',
'koi8-u' =>'koi',
);
return $src unless ($charsets{lc($charset)});
return encoder($src, $charsets{lc($charset)},
'win');
}
Огромная благодарность российскому разработчику
библиотек pvd – Денису Познякову (Denis Poznyakov, pvdenis@usa.net) – за минимальный код
перекодировки русских символов. Перекодировка основывается на данных полей
письма, тут нет попытки создания какого-либо анализатора.
sub encoder {
my $enstring = shift; my $cfrom
= shift; my $cto = shift;
my %codefunk = (
win =>
"\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF",
koi =>
"\xE1\xE2\xF7\xE7\xE4\xE5\xF6\xFA\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF2\xF3\xF4\xF5\xE6\xE8\xE3\xFE\xFB\xFD\xFF\xF9\xF8\xFC\xE0\xF1\xC1\xC2\xD7\xC7\xC4\xC5\xD6\xDA\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD2\xD3\xD4\xD5\xC6\xC8\xC3\xDE\xDB\xDD\xDF\xD9\xD8\xDC\xC0\xD1",
iso =>
"\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF",
dos =>
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF",
koi_lc => "tr/\xB3\xE0-\xFF/\xA3\xC0-\xDF/",
koi_uc =>"tr/\xA3\xC0-\xDF/\xB3\xE0-\xFF/",
win_lc => "tr/\xA8\xC0-\xDF/\xB8\xE0-\xFF/",
win_uc =>"tr/\xB8\xE0-\xFF/\xA8\xC0-\xDF/",
alt_lc => "tr/\xF0\x80-\x9F/\xF1\xA0-\xAF\xE0-\xEF/",
alt_uc => alt_lc =>
"tr/\xF1\xA0-\xAF\xE0-\xEF/\xF0\x80-\x9F/",
iso_lc => "tr/\xA1\xB0-\xCF/\xF1\xD0-\xEF/",
iso_uc => "tr/\xF1\xD0-\xEF/\xA1\xB0-\xCF/",
dos_lc => "tr/\x80-\x9F/\xA0-\xAF\xE0-\xEF/",
dos_uc => "tr/\xA0-\xAF\xE0-\xEF/\x80-\x9F/",
mac_lc => "tr/\xDD\x80-\xDF/\xDE\xE0-\xFE\xDF/",
mac_uc => mac_lc =>
"tr/\xDE\xE0-\xFE\xDF/\xDD\x80-\xDF/"
);
if (!$enstring or !$cfrom or
!$cto) {return 0}
else {
if ($cfrom ne
"" and $cto ne "lc" and $cto ne "uc") {
$_ = $enstring;$cfrom =
$codefunk{$cfrom};$cto = $codefunk{$cto};
eval "tr/$cfrom/$cto/";
return $_;
}
elsif (($cfrom ne
"") and ($cto eq "lc" or $cto eq "uc")) {
$_ = $enstring; $cfrom
= $codefunk{"$cfrom\_$cto"};
eval $cfrom; return $_;
}
}
return $enstring;
}
Если вы не поняли, как это работает, поясню.
Данный пример скачивает с почтового ящика все письма, обрабатывает их и
передает системе управления базами данных либо куда-то еще. Этот код является
основой описываемой технологии. Как я и обещал, соблюдена полная кросс-платформенность
между Windows и UNUX-системами. Все используемые в скрипте библиотеки легко
доступны.
От редактора: в процессе редактирования статьи
было замечено, что тот же самый Outlook Express нечасто кодирует русские буквы
в заголовках сообщений. Все это, конечно, зависит от настроек, но мало кто из
пользователей Outlook занимается дотошной настройкой своего почтового клиента.
Поэтому процедура xcode при работе с такими
заголовками не диагностировала необходимость перекодирования. Немного подумав,
была придумана небольшая модификация. Это не панацея, а скромная попытка
перекрыть некоторые проблемы – угадать кодировку KOI8-R в заголовке. Для этого
внесем только одну коррективу в процедуру xcode – чуть усилим проверку:
# закомментируем строку,
возвращающую неизмененный
# заголовок и продолжим
проверку:
# return $src unless ($charsets{lc($charset)});
unless ($charsets{lc($charset)})
{
# если кодировка
неопределенна, считаем вхождение
# больших и маленьких
русских букв
$upper =
"ЁЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ";
$lower = "ёйцукенгшщзхъфывапролджэячсмитьбю";
$ucount = eval("\$src
=~ tr/$upper/$upper/;");
$lcount = eval("\$src
=~ tr/$lower/$lower/;");
# если больших букв
больше - скорее всего это KOI8
# и мы перекодируем это в
Windows
return encoder($src, 'koi',
'win') if ($ucount > $lcount);
# иначе, как и ранее,
возвращаем неизмененный заголовок
return $src;
}
Собственно, идея основана на том факте, что слово
«Новость» в кодировке KOI будет восприниматься как «оПЧПУФШ» в кодировке Windows.
Прием довольно спорный, но имеет право на существование. Абсолютно не применим
на больших объемах текста – поверьте на слово.
Тема безопасности,
аутентификация, вклинивания
Любое письмо, попавшее на ящик, может стать атакой или просто спамом. Как с
этим бороться?
Прежде всего вы можете организовать интернет-рассылку,
где сможете управлять регистрацией нужных вам подписчиков. Обычно в системах
рассылки уже встроены свои собственные механизмы борьбы со спамом.
Описанный выше сценарий также подходит к делу
принципиально. Принимаются только те сообщения, которые пришли в виде текста.
Кроме того, вырезаются баннеры, расположенные обычно после символов «\n— \n»
(это – стандарт TheBat). Также скрипт соблюдает логику: «можно только то, что
разрешено», а не «можно то, что не запрещено».
Если проблемно организовать рассылку, скорее
всего вам необходимо будет добавить проверку ключа (например, в теме письма
должен быть соответствующий идентификатор). Идентификатор отбрасывает почти все
проблемы с безопасностью, однако это не накладывает ограничения на входящий
трафик.
Но самый безопасный вариант – принимать только
текст, зашифрованный или подписанный при помощи программ семейства PGP. Это
также исключит возможность прочитывания писем при случайном доступе к ящику.
Немного рекламы
Ранее мы коснулись темы хранения данных. Важно понимать, что кроме простых
функций вроде публикации рассылки, со временем функциональность может обрасти
связями с различными разделами, а приходящие данные смогут иметь сложную
структуру. Плюс ко всему – не каждый может себе позволить профессиональный хостинг.
Ради этого всего появилось на свет ядро ret WebOS,
которое (как одно из направлений) предлагает альтернативу серверу SQL. Код
системы минималистичен и для своей работы требует лишь наличия модуля Storable
(который входит в стандартную поставку с Perl 5.8).
В свою очередь модуль blogs.pm предоставляет
возможность хранить структуры данных любой сложности и осуществлять к ним
доступ как к разделам сайта. Данные хранятся в плоских файлах. Интернет-адрес
проекта: http://jkeks.far.ru/ret. Добро
пожаловать!