Сборщик писем по imap протоколу

31.01.2020

Задача: собрать исходники писем с почтовых ящиков и сохранить сырые данные на сервере (локальном).

Средства реализации: php & curl

Репозиторий с исходным кодом решения задачи здесь. Есть подробный readme, а код внутри прокомментирован.

Алгоритм довольно прост:

  1. подключение к почтовому ящику через imap протокол,
  2. проход по всем директориям на ящике
  3. скачивание исходников писем и распределение по директориям, как на ящике
Дале расмотрим проблемы, возникающие на эатпе сборки писем и их решение.

Директории

Первая проблема заключалась в получении, разборе и использовании названий директорий на почтовом ящике. При авторизации на imap сервере в ответе будет получен список директорий на ящике в UTF7-IMAP кодировке:

•••
plaintext
* LIST (\Unmarked \HasNoChildren \Drafts) "|" Drafts * LIST (\Unmarked \NoInferiors) "|" INBOX * LIST (\Unmarked \HasNoChildren) "|" Outbox * LIST (\Unmarked \HasNoChildren \Sent) "|" Sent * LIST (\Unmarked \HasNoChildren \Junk) "|" Spam * LIST (\Unmarked \HasNoChildren \Trash) "|" Trash

Нет проблем пока не появляются директории на кириллице с пробелами (название синтетическое для примера):

•••
plaintext
* LIST (\Trash) "/" "&BBoEPgRABDcEOAQ9BDA- &BBoEPgRABDcEOAQ9BDA-"

При попытке сканирования такой директории по адресу: imaps://imap.domain.zone/&BBoEPgRABDcEOAQ9BDA- &BBoEPgRABDcEOAQ9BDA-/, imap сервер выдал:

•••
plaintext
imap URL using bad/illegal format or missing URL

Как видно проблема в недопустимых символах, решение нашлось здесь, надо было просто экранировать эту часть URL (название директории) при помощи curl_escape.

Декодировать строку можно так:

•••
php
$sDir = mb_convert_encoding($value, "UTF-8", "UTF7-IMAP");

Кодировка

Исходник письма может быть в любой кодировке.

Изначально требовалось чтобы скрипт отправлял исходник письма и некоторые другие данные на другой сервер, для этого данные паковал json_encode, пока не пошли письма в windows-1251 кодировке, на что json_encode сказал:

•••
plaintext
Malformed UTF-8 characters, possibly incorrectly encoded

Проблема была решена при помощи base64_encode исходника письма с последующим json_encode, а принимающий сервер делал обратные операции и получал неизменные данные.

Удаление писем

Удалять письма надо по убыванию порядковых номеров (письмо с номером 1 удалять самым последним), иначе обратный порядок может привести к тому что порядковые номера при удалении будут изменены на imap сервере, а дальше неверная идентификация писем.

Это не относится к удалению писем по UID

Если речь идет о последовательном удалении, тогда для этого (после получения и парсинга ответа после SEARCH ALL UNDELETED) можно просто применить arsort к массиву с порядковыми номерами:
•••
php
//получение порядковых номеров всех неудаленных писем в директории curl_setopt($hCurl, CURLOPT_URL, "$sImap/$value"); curl_setopt($hCurl, CURLOPT_CUSTOMREQUEST, "SEARCH ALL UNDELETED"); $sResponse = curl_exec($hCurl); $sResponse = str_replace("\r\n", "\n", $sResponse); $sResponse = mb_substr($sResponse, mb_strlen("* SEARCH ")); //парсим строку извлекая порядковые номера сообщений в директории $aStrs = explode(" ", $sResponse); //сортировка массива по убыванию, чтобы удалять не сбивая порядковые номера arsort($aStrs);