С чего бы это вам посылать по e-mail энциклопедию?

Хорошо, не обязательно энциклопедию. Это может быть фильм. Или большая директория, которую вы упаковали в один 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.CZ,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-3P-#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


Команда переводчиков:
Владимир Меренков, Александр Михайлов, Иван Песин, Сергей Скороходов, Александр Саввин, Роман Шумихин, Александр Куприн, Андрей Киселев, Игорь Яровинский, Юрий Прушинский, Павел Соколов

Со всеми предложениями, идеями и комментариями обращайтесь к Александру Куприну (ru_classic at mail.ru). Убедительная просьба: указывайте сразу, не возражаете ли Вы против публикации Ваших отзывов в рассылке.