Как послать энциклопедию по
e-mail
Автор: Graham Jenkins
Перевод: Павел Соколов
Хорошо, не обязательно энциклопедию. Это может быть фильм. Или большая директория, которую вы упаковали в один tar-файл и заархивировали. И, конечно, вы могли бы переслать её по FTP. Или, возможно, не могли бы. Ваша машина может работать в корпоративной сети без доступа к внешним FTP серверам. Или на компьютере-пункте назначения отключен протокол FTP в целях безопасности. Другой вариант - закодировать передаваемый объект в строку из символов ASCII и послать его по почте. Вы можете воспользоваться утилитой uuencode для такого кодирования, либо вы можете использовать метод Base64, описанный в RFC 2045 "Многоцелевые расширения электронной почты (MIME), Часть первая (Multipurpose Internet Mail Extensions (MIME) Part One)".
[Прим. пер. - Отсюда совсем не понятно, зачем кодировать строку. Изначально при передаче почты существовали ограничения на передаваемые символы - они кодировались только 7 битами.]
Если бы вы физически посылали по почте энциклопедию, скорее всего, вы бы упаковали её в одну коробку. Это было бы хорошим решением, если бы все почтовые службы на пути следования могли принимать коробки такого веса и размера. Если же это не так, тогда бы вам пришлось распределить энциклопедию по нескольким посылкам приемлемого размера и веса.
Аналогично, когда мы посылаем энциклопедию по электронной почте, нам надо обеспечить, чтобы размер письма, в котором мы посылаем энциклопедию, не вышел за ограничения, с которыми оно может столкнуться в пути. Для этого придётся разбить письмо на несколько частей. Это можно сделать в соответствии с RFC 2046 "Многоцелевые расширения электронной почты (MIME), Часть вторая (Multipurpose Internet Mail Extensions (MIME) Part Two)".
Обобщая, если мы последуем рекомендациям RFC 2045 и RFC 2046, мы должны закодировать всю энциклопедию, используя Base64, а затем разбить результат на то количество частей, которое нам нужно. Тогда, посылаемые по почте части, будут выглядеть примерно так:
From [email protected] Tue Dec 31 13:14:34 2002 Content-Disposition: inline Content-Transfer-Encoding: 7bit Content-Type: message/partial; id="300870"; number="1" Subject: Graham's Encylopedia owF1Vb+P3EQUPhLRrBSFlHQjRYCQsthe/1q7CNrbREjocnvK3hHREM3ac7fWeWfM zPh2L38ASomEIro0SNBBg2hBSDTwR0BBEwqQaFJF8J499tob0EjW7rzv+96b772x ... szJb9DUMvKdRUIV+RY5Xu3UkRQqvJCzdzHtHoQL36Ke6elnYLgwH8MfxCU9ymq1Y -- From [email protected] Tue Dec 31 13:14:34 2002 Content-Disposition: inline Content-Transfer-Encoding: 7bit Content-Type: message/partial; id="300870"; number="9"; total="9" Subject: Graham's Encyclopedia dc45xuruv3m3e8z/OGRD6lxz13GC5m0XbXvcWlyFW4vxbSSK5KEoTOIIuxTFs2JK UnZKy1wTAV9TWr2dev7WrLbXkeOHUVQnjuyXEptwm3hBgfT43auvVh/v5mt+48pb n+09Hf7+5Nvyx5tf/fP4o+PJ398Xf958cW3v6ejzL17/9YPfPs4unv08efvr68O/ njz/Fw== --
Для получателя не всегда легко собрать части в правильном порядке, а затем отбросить заголовки и передать данные Base64 декодеру. Если получатель использует старую Unix-машину, у него может вообще не быть Base64 декодера. Если же он использует машину с ОС от Microsoft, существует вероятность того, что он не сможет правильно обработать части сообщения.
Так что альтернативный вариант - разбить энциклопедию на пронумерованные части, а затем отдельно обработать uuencode для пересылки. Большинство версий утилиты uudecode достаточно сообразительны, чтобы отбросить строки с заголовками. Это срабатывает даже с Microsoft Outlook.
Хитрость здесь - пронумеровать части таким образом, чтобы их легко можно было выбирать (например, используя cat) в правильной последовательности и передавать по конвейеру (например, для разархивации и развёртывания: unzip и untar), либо выводить в файл. Части теперь выглядят примерно так:
From [email protected] Tue Dec 31 13:49:07 2002 Subject: encyclo part 1/ size/sum 1024/16571 begin 644 001_encyclo M<F]O=#IX.C`Z,3I3=7!E<BU5<V5R.B\Z+W-B:6XO<V@*9&%E;6]N.G@Z,3HQ M.CHO.@IB:6XZ>#HR.C(Z.B]U<W(O8FEN.@IS>7,Z>#HS.C,Z.B\Z"F%D;3IX ... M8W)E<',Z+V)I;B]K<V@*=V-O8F%T8V@Z>#HU,#(X.#HQ.D%L97@@=&AE(%=A B;FME<CHO97AP;W)T+VAO;64O=V-O8F%T8V@Z+V)I;B]K<P`` ` end -- From [email protected] Tue Dec 31 13:49:07 2002 Subject: encyclo part 2/2 size/sum 945/12218 begin 644 002_encyclo M:`IC-S0S-#0P.G@Z-38T-C,Z-3`P-#I!;F1R97<@3'5O;F<Z+VAO;64O861M M;W!E<F%T;W(Z+V5X<&]R="]H;VUE+V]P8U]O<#HO8FEN+W-H"F,Y,34W.3DZ M>#HU,#(Y,#HQ.CHO:&]M92]A9&UI;B]C.3$U-SDY.B]U<W(O8FEN+V)A<V@* ` end --
Обратите внимание, что мы теперь используем символы только верхнего регистра, и что в сообщении появилось несколько скобок и других символов. Некоторые из них не могут быть эквивалентным образом переведены в другие варианты кодирования символов. Поэтому RFC 2045 рекомендует использовать Base64 вместо uuencode.
Вот вариант программы-упаковщика. Для простоты и рассмотрения наиболее общего случая, мы используем альтернативный вариант, описанный ранее. Такого рода программы существуют уже давно. Они обычно написаны на C, хотя существуют варианты и на языке Bourne-Shell. И обычно они создают временные файлы.
Можно написать элегантную реализацию схемы упаковки на Perl, не использующую никаких временных файлов. Конечная программа - и проста, и может быть перенесена на другую ОС. Поэтому это то, что мы и сделали. (Единственное на что хочу обратить ваше внимание -- это путь к интерпретатору Perl в заголовке скрипта. Как правило, это /usr/bin/perl , а не /usr/local/bin/perl Прим.ред.)
#!/usr/local/bin/perl -w # @(#) filemail.pl Разбивает входящий поток на части, а затем кодирует # каждую часть и посылает её по e-mail указанному получателю. # Vers. 2.05; Graham Jenkins, IBM GSA, Декабрь 2002. use strict; # Части кодируются и высылаются по схеме с двойным буфером. use File::Basename; # Используется Uuencode для сокращения зависимости от модулей. my $PSize = 700; # Размер части на входе по умолчанию. my ($Count,$Sum,$Size,$Total,$InpBuf,$InpLen,$OutBuf,$j); if ($#ARGV eq 2) { if ($ARGV[0] =~ m/^-\d+$/ ) { $PSize=0-$ARGV[0]; shift } } die "Использование: cat файл |".basename($0)." [-KbНаЧасть] получатель ИмяФайла\n". "Например: tar cf - .|".basename($0)." -64 smith\@popser.acme.com mydir.tar\n". "(Примечание: размер незакодированной части по умолчанию = $PSize","kb)\n" if ($#ARGV ne 1); open(INFILE,"-") || die "Не могу прочесть входные данные!\n"; $Count = 0; $Total = "";# Цикл пока не кончатся данные. do { $InpLen = read(INFILE, $InpBuf, 1024 * $PSize); $Total = $Count if $InpLen lt 1; do { $Size = length($OutBuf); print STDERR "$ARGV[1] part $Count/$Total => $ARGV[0] $Size bytes\n"; $Sum = unpack("%32C*", $OutBuf); foreach $j (1,2) {$Sum = ($Sum & 0xffff) + int($Sum/0x10000)} open(PIPE, "| Mail -s" . "'$ARGV[1] part $Count/$Total size/sum $Size/$Sum' $ARGV[0]"); $j = $Count ; while (length($j) < 3 ) { $j = "0" . $j } $j = dirname($ARGV[1])."/".$j if dirname($ARGV[1]) ne "."; print PIPE "begin 644 ",$j,"_", basename($ARGV[1]),"\n", pack("u",$OutBuf),"\`\nend\n"; close(PIPE) } if $Count gt 0; $Count++; $OutBuf = $InpBuf } until $InpLen lt 1;
Perl предоставляет инструкцию read, которая позволяет указать количество байт, которое она должна попробовать прочитать в указанную строку. Как можно видеть, мы просто продолжаем читать из стандартного ввода до тех пор, пока в $InpBuf не будет возвращена пустая строка. Каждый раз, когда мы получаем непустую строку, мы кодируем всё, что находится в $OutBuf, с помощью uuencode и передаём содержимое буфера в почтовую программу. Затем мы сохраняем содержимое $InpBuf в $OutBuf для последующей итерации.
Perl способен кодировать строку по uuencode, как это показано, с помощью инструкции pack с параметром u; никаких дополнительных модулей не требуется. В действительности, в этом нет необходимости, но в этом случае, как дополнительной возможностью, мы можем воспользоваться инструкцией unpack для расчёта контрольной суммы по каждой посылаемой части.
Вы можете заметить, что мы фактически открываем канал (pipe) в программу Mail, входящую в ОС Unix/Linux, для обработки исходящей почты. Для большей переносимости на другие ОС, мы могли бы установить и воспользоваться модулем Net::SMTP.
Программа может быть вызвана с опциональным параметром, регулирующим размер передаваемых частей, чтобы скорректировать установленный по умолчанию размер некодированной части - 700kb.
Некоторые из вас могут заметить, что такое разбиение сообщений аналогично тому, что мы делали в "Безопасная печать с PGP". (Перевод статьи вы найдёте здесь. Прим.ред.) Для тех, кому интересно: существуют обновлённые версии программы из этой статьи, их можно найти в "Репозиторий скриптов CPAN". Эти программы используют рекомендованный в RFC метод "закодировать по Base64, затем разбить".
Более ранняя статья, "Linux-клиент для Интернет-протокола печати Brother", включает в себя скрипт для shell, который использует метод "разбить, затем послать части"; он также использует кодирование Base64. (Перевод статьи вы найдёте здесь. Прим.ред.)
Graham - специалист по Unix, работает в IBM Global Services,
Австралия. Живёт в Мельбурне, создал и администрировал множество
проприетарных и открытых систем на нескольких платформах.
Copyright (c) 2003, Graham Jenkins. Copying license
http://www.linuxgazette.com/copying.html
Опубликовано в выпуске 86 Linux Gazette, январь
2003