Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

PHP, SQL, SSI-Дневник ламера, превращающегося в ГУРУ :-) Отправка писем с вложением файлов - 2


Информационный Канал Subscribe.Ru

Выпуск номер 11
Рассылка: Дневник ламера.
     
 

Тема: Отправка писем с вложением файлов, часть вторая.

Несколько ошибок допустил, прошу прощения. Так что это исправленный выпуск и операторы подсветил, чтобы удобнее было читать.

В предыдущем выпуске я приводил пример класса, который отправляет файлы на сервер. Подробно с этим я тогда не разобрался и точно не знал, как именно это работает, поэтому расскажу об этом сейчас. Тем более, что тот класс отправки работал немного некорректно и робот, которому я "скармливал" мои письма не принял их. Заглянув в заголовок "Header" письма и сравнив его с нормальным (который формирует к примеру The Bat), я увидел страшную кашу, которую и пришлось "пофиксить" ручками, переписав класс по новой.

Немного теории. Письмо представляет из себя просто файл. Один файл. Простая функция PHP по отправке мыла делает в обычном виде только отправку текстового сообщения, тоесть письмо состоит из ОДНОЙ части, из одного блока. Письма же с вложениями состоят из нескольких блоков, которые разбиваются-отделяются от других частей так называемым заголовком BOUNDARY.
Чтобы увидеть это, можно просто создать письмо, в любом почтовом мэйлере (я пользовался зебатом) и вложить в него как минимум ДВА файла. Если вы вложите один файл, то структура будет понятной недостаточно чётко, а когда вы увидите структуру письма уже с двумя файлами, то станет понятно как вложить в письмо хоть сотню файлов.

Логически я для себя определил, что письмо состоит условно из ТРЁХ блоков, это ХЕДЕР, с описанием параметров письма, далее часть с текстом сообщения - вторая и третья - куски с вложенными файлами и заключительным разделителем в конце письма...

...Создав письмо с парой вложенных файлов надо его сохранить куда-нить на диск. Делается это в меню: MESSAGE -> Save as...
Затем просто просматриваете письмо в notepad`е
Там вы увидите подобный текст:

Date: Sat, 4 Dec 2004 04:56:31 +0800
From: "yahoo.nm.ru" <yahoo@nm.ru>
X-Mailer: The Bat! (v1.53d) Personal
Reply-To: "root.nm.ru" <root@nm.ru>
Organization: Mad Home 8-)))
X-Priority: 3 (Normal)
Message-ID: <154191472583.20041204045631@nm.ru>
To: =?koi8-r?B?xeLj5e3o6SDF4vHl5eI=?= <root@nm.ru>
Subject: =?koi8-r?B?0uXs4CDv6PH87OAu?=
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----------FD5CFA41FCD64"

Date- это дата создания письма. Необязательное поле.
From - адрес отправителя. Не знаю точнонужен или нет, но пусть будет.
X-Mailer - программа, которая сгенерировала письмо, тоже нафиг не надо в принципе.
Reply-To - кому слать ответ. Поле нафиг не нужное.
Organization - сами догадайтесь :-))) Тоже совершенно необязательное поле.
X-Priority - приоритет доставки писем почтовым сервером. Тоже выкидываем поле за ненадобностью.
Message-ID - номер сообщения, генерится рандомом и можно выкинуть. Не критично и не нужно.
To - кому уходит письмо. Нужное поле, если кидаем через сокет письмо, но в своём классе (как вы в дальнейшем увидите) я отправляю письмо стандартной командой mail(); поэтому нам это поле также не требуется. Его подставляет процедура отправки автоматом.
Subject - Тема письма. Её тоже подставляет в заголовок функция mail() поэтому я не формировал в заголовке письма его тему.
MIME-Version: 1.0 - это поле необходимо. Оно указывает на стандарт, которому соответствует формат разбиения письма.
Content-Type: multipart/mixed - это поле также необходимо. Оно указывает на то, что письмо состоит из нескольких частей (multipart/mixed).
boundary - необходимое поле, которое задаёт строки разделители между частями самого письма.

Само письмо можно разбить условно на три блока:

1) Заголовок - Кому, куда, от кого, кем, чем и прочее.

2) Тело сомого текстового сообщения. Оно начинается разделительными символами, которые указаны в boundary.
ричём одна особенность, boundary - это порсто набор символов. ЛЮБЫХ символов, лишь бы они не встречались в самом тексте письма.
Каждая часть начинается с ДВУХ знаков минус и этим самым boundary.

Последняя строчка письма также заканчивается двумя минусами перед boundary и двумя минусами уже ПОСЛЕ boundary, в то время как в самом теле письма эти разделители идут ТОЛЬКО с двумя минусами ПЕРЕД boundary.
Пример:

------------FD5CFA41FCD64
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit

Привет пацаны !!!
------------FD5CFA41FCD64
Content-Type: application/msword; name="aaa.doc"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="aaa.doc"

0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAAA
EAAAQwAAAAEAAAD+////AAAAAEAAAAD/////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

------------FD5CFA41FCD64--

В заголовке письма вы видели поле:
boundary="----------FD5CFA41FCD64"
Это и есть скажем так инициализация разделителя. В начале у него 10 минусов (ну так генерится оно программой, что собственно и не обязательно совсем)
В теле-же письма уже 12 минусов, так как начальный блок отмечается ДВУМЯ минусами.
В примере выше первый блок - это текст самого письма:
------------FD5CFA41FCD64
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit

Привет пацаны !!!

Content-Type: text/plain
- это говорит, что в блоке содержится текст. Просто текст и всё.
В кодировке koi8-r (для программы, которой будет просматриваться это сообщение).
Можно в кодировке koi8-r ну или в любой другой.
Content-Transfer-Encoding: 8bit - это уже формат декодирования сообщения, содержащегося в блоке. У меня 8bit, есть ещё 7bit и quoted-printable.
Далее (ВНИМАНИЕ, ДВА ПЕРЕНОСА СТРОКИ !!! "\n\n" Так надо) идёт код самого блока. Здесь он не закодирован, поэтому его можно нормально прочитать.

Дальше идёт разделитель, который отмечает начало следующего блока:

------------FD5CFA41FCD64
Content-Type: application/msword; name="aaa.doc"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="aaa.doc"

0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAAA
EAAAQwAAAAEAAAD+////AAAAAEAAAAD/////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////


------------FD5CFA41FCD64--

Content-Type: application/msword
- это тип файла, который мы сунули в данный блок. Тип - application/msword короче - просто вордовский документ. Что указывать необязательно. Тоесть можно указать тип: application/octet-stream - это для файлов с неопределённым типом, что ничуть не влияет на передаваемые данные. Именно так я и сделал в своём классе.
name="aaa.doc" - имя файла, содержащегося в блоке.

Далее важно:
Content-Transfer-Encoding: base64 -
формат передачи данных, в котором закодирован в блоке файл. Тоесть мы берём файл, загоняем его в переменную и просто кодируем стандартной функцией ПХП: base64_encode($fdata);
Content-Disposition: attachment; filename="aaa.doc"
- это указывает, что в блоке всётаки аттаченый файл. Тоже необходимо указывать. Также опять его имя и далее через ДВА ПЕРЕВОДА СТРОКИ !!! "\n\n" начинается сам файл, который кодирован функцией base64_encode

Потом файл заканчивается и мы видим два переноса строки "\n\n" и заключительный разделитель: boundary Опять-же с двумя минусами в начале, но теперь и ПОСЛЕ него стоят два минуса. Это говорит о том, что текст письма заканчивается.

Если мы хотим вложить два и более файла, то просто докидываем ещё блоки в письмо, ставя в самом конце --boundary--

Теперь приведу класс, который я написал на базе вышеприведённой информации.


// Определяем класс: mime_mail Если вы не в курсе, что такое классы в программировании, то приведу для вас аналогию. Есть допустим ЧЕРТЁЖ СТУЛА и есть сам СТУЛ. Так вот, Чертёж стула - это КЛАСС, тоесть описание того, из чего состоит ОБЪЕКТ(стул). Как он выглядит, чем держится за землю %) Мы передаём чертёж плотнику и уже он делает для нас сам стул, тоесть по описанию класса, создаёт ОБЪЕКТ.


class mime_mail {

function mime_mail(){ $this->body = ""; $this->boundary = "b".md5(uniqid(time())); }
// Внутренняя функция класса, по имени самого класса. Поэтому при создании объекта она запускается и инициализирует внутренние переменные объекта.
Мы видим как-раз инициализацию: $this->boundary = "b".md5(uniqid(time()));
Создаётся разделитель блоков в письме.


function addfile($fname, $fdata)
// Внутренняя функция класса. В неё передаются ДВА значения, это $fname и $fdata.
Тоесть ИМЯ файла и строка - сам файл.

{
@$this->body.="--".$this->boundary."\n". // Отмечаем начало блока разделителем и все дальнейшие сложения строк спихиваем в переменную: $this->body через операцию конкатенации строк, тоесть добавление к предыдущей строке следующего куска .=
Точка и знак равенства.


"Content-Type: application/octet-stream; name=\"$fname\"\n". // Говорим, что файл хрен знает какого типа, но его имя - $fname - этого достаточно

"Content-Transfer-Encoding: base64\n". // Кодировка данных файла в формате - base64 и небольшая мелочь, если вам непонятно, это \" тоесть в паре двойных кавычек мы ставим \" для того, чтобы экранированная слешем кавычка не воспринималась как окончание блока, а именно как кавычка. Как и в регулярных выражениях, специальные символы экранируются слешем.

"Content-Disposition: attachment; filename=\"$fname\"\n\n"; // Указание на содержимое блока - это приаттаченый файл. Также указываем имя файла.
@$this->body.=base64_encode($fdata)."\n"; // тут мы просто кодируем файл, поступивший в функцию класса и находящийся в переменной: $fdata Также добавляем кодированную строку в переменную класса: $this->body
}

function send_mail($from, $to, $theme, $message) // Функция отправки сообщения и одновременно конструктор хедера письма. Передаваемые в функцию данные:
$from - откого
$to - кому
$theme - тема
$message - сообщение


{
$header="From: \"$from\" <$from>\n". // Добавляем поле ОТ КОГО и все последующие данные скидываем во внутреннюю переменную функции: $header
"Organization: Mad Home 8-)))\n". // Ха - ха - ха (((-8
"MIME-Version: 1.0\n".
"Content-Type: multipart/mixed; boundary=\"".$this->boundary."\"\n\n". // Добавляем тип письма и разделитель BOUNDARY
"--".$this->boundary."\n". // Указываем, что начинается блок
"Content-Type: text/plain; charset=koi8-r\n". // Указываем, что блок этот с текстовым сообщением
"Content-Transfer-Encoding: 7bit\n\n". // Кодировка блока - 7bit
"$message\n".$this->body."\n\n--".$this->boundary."--\n"; // Добавляем сообщение в первый блок, затем все приаттаченные файлы из переменной класса: $this->body и затем заканчиваем письмо добавлением из переменной $this->boundary разделителя с двумя минусами после него

mail($to, $theme, '', $header); // отправляем сообщение, при этом заголовок КОМУ и ТЕМА - функция отправки сама дописывает, по этой причине я и не формировал данные поля в теле запроса.
} }


// Как со всем этим работать ?
$file1='aaa.doc'; // даем в переменную имя первого приаттачиваемого файла
$flop1=fopen($file1, "r"); // открываем его для чтения на дескриптор: $flop1
$ln1=filesize($file1); // определяем размер файла
$fdata1=fread($flop1, $ln1); // читаем файл в переменную: $fdata1

// Та же фигня со вторым файлом происходит
$file2='bbb.jpg';
$flop2=fopen($file2, "r");
$ln2=filesize($file2);
$fdata2=fread($flop2, $ln2);

// Теперь работа с классом отправки сообщения
$mail = new mime_mail(); // Создаём объект, тоесть по чертежу(классу) нам создают стул(объект)

$mail->addfile($file1, $fdata1); // Добавляем первый файл вызовом функции созданного объекта($mail) и в объекте он скидывается со всей необходимой для отправки структурой в переменную: $this->body Обратите внимание, что в объект файл поступает не в виде только имени, а уже готовая строка данных для последующего кодирования

$mail->addfile($file2, $fdata2); // Добавляем второй файл, который в объекте прибавляется к внутренней переменной: $this->body и также со всей необходимой структурой

$mail->send_mail("root@nm.ru","root@yahoo.com","Hello gize !!!","Привет пацаны.");
// Функция отправки файла в объекте с передаваемыми ей параметрами, тоесть КОМУ, ОТ КОГО, ТЕМА и ТЕКСТОВОЕ СООБЩЕНИЕ для письма

Вот собственно всё, теперь приведу этот-же класс, но в рабочем виде и без комментариев:

class mime_mail {
function mime_mail(){ $this->body = ""; $this->boundary = "b".md5(uniqid(time())); }
function addfile($fname, $fdata)
{
@$this->body.="--".$this->boundary."\n".
"Content-Type: application/octet-stream; name=\"$fname\"\n".
"Content-Transfer-Encoding: base64\n".
"Content-Disposition: attachment; filename=\"$fname\"\n\n";
@$this->body.=base64_encode($fdata)."\n";
}
function send_mail($to, $from, $theme, $message)
{
$header="From: \"$from\" <$from>\n".
"Organization: Mad Home 8-)))\n".
"MIME-Version: 1.0\n".
"Content-Type: multipart/mixed; boundary=\"".$this->boundary."\"\n\n".
"--".$this->boundary."\n".
"Content-Type: text/plain; charset=koi8-r\n".
"Content-Transfer-Encoding: 7bit\n\n".
"$message\n".$this->body."\n\n--".$this->boundary."--\n";

mail($to, $theme, '', $header);
} }

// Пример использования
$file2='bbb.jpg'; $flop2=fopen($file2, "r"); $ln2=filesize($file2); $fdata2=fread($flop2, $ln2);

$mail = new mime_mail(); // Создание объекта
$mail->addfile($file2, $fdata2); // Добавление файла в него

$mail->send_mail("root@nm.ru","root@yahoo.com","Hello gize !!!","Привет пацаны."); // Отправка
unset($mail); // Уничтожаем объект


Также примечание: если вы будете гонять отправку писем в цикле, то объект будет переполняться, поэтому в конце каждого цикла ставьте функцию:
unset($mail); Это что-то типа деструктора объекта, тоесть на каждой итерации, после отработки объекта, он будет уничтожаться и в следующей "реинкарнации" :-) создаётся по новой.



В следующем выпуске я расскажу про структуру HTTP запросов на сервер. Они представляют из себя практически тоже самое с небольшими изменениями в заголовке запроса и передаваемые переменные также разбиваются структурой, аналогичной "--BOUNDARY".

Если этот выпуск кому-то помог и вы считаете, что я потратил время с пользой на выпуск, то можете кинуть мне на кошелёк WEB Money рублей по десять - пятнадцать ;-) Не зря - же я двое суток с этим копался и отлаживал.
Сюда:
Z814927902248
R978507355477
Скачать прогу можно тут: http://download.webmoney.ru/wm2.EXE
Если у кого ещё нет. С миру по нитке - ООН`у на рубашку :-)

Можно даже отключить назойливые баннеры в этой рассылке, если достаточно будет для службы сервера. Не хочется кормить вас назойливой рекламой не в тему - сам её не переношу :-) Поэтому думаю вы меня поймёте.
До следующего выпуска и досвиданья :-)

 
     
Евгений Евсеев - legat\\Собака/nm.ru

http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.prog.php1sql1ssi
Отписаться

В избранное