Tuesday, March 25, 2014

Как связать протокол POP3 и IMAP

В этой статье поговорим об отличии двух почтовых протоколов – POP и IMAP – и о том, как их можно связать друг с другом.
Мотивирующая картинка J
Почтовый протокол POP3 используется только для загрузки новых сообщений с почтового сервера. Он позволяет почтовому клиенту подключиться к серверу лишь на период времени, который требуется для загрузки сообщений.
Не допускается одновременное подключение нескольких почтовых клиентов к определенному ящику (разрешено только одно подключение).
При использовании почтового протокола IMAP соединение не разрывается до тех пор, пока ведется работа с ящиком в почтовой программе; загрузка сообщений с сервера производится только по запросу почтового клиента.
Разрешен одновременный доступ нескольких почтовых клиентов к ящику, причем каждый из них может отслеживать изменения, производимые другими подключенными клиентами, а также статус всех сообщений (прочитано, отправлен ответ, удалено).
Оба эти протокола выполняют идентичные задачи по работе с почтой, только Pop3 позволяет скачивать все файлы одновременно, а IMAP – сначала список файлов, а затем – по необходимости сами файлы.
Одна из частых проблем при использовании протоколов Pop3 и IMAP, заключается в том, что многие разработчики пробуют реализовать эти протоколы под один интерфейс и с одинаковой логикой. Это в корне неверно. Дело в том, что при использовании протокола IMAP мы можем грузить не все письма, а только непрочитанные для примера. Для того чтобы реализовать передачу данных, мы использовали в проекте библиотеку MailBee.NET. Пример реализации приема с помощью этой библиотеки вы можете посмотреть здесь. В примере по ссылке выше приведена реализация протокола POP3 и IMAP с использованием паттерна фабричный метод (Factory Method). В одной из таких реализаций мы использовали для уникальности письма UID (уникальный идентификатор письма на сервере). Дело в том, что для IMAP и POP3 эти идентификаторы разные.
RFC 1939 (POP3): The unique-id of a message is an arbitrary server-determined string, consisting of one to 70 characters in the range 0x21 to 0x7E, which uniquely identifies a message within a maildrop and which persists across sessions.
RFC 3501 (IMAP): (Unique Identifier (UID) Message Attribute is) a 32-bit value assigned to each message, which when used with the unique identifier validity value (see below) forms a 64-bit value that MUST NOT refer to any other message in the mailbox or any subsequent mailbox with the same name forever.
В итоге имеем, что для POP3 UID представляет собой строку от одного до 70 символов, а для IMAP это целое 32 битное число.
Давайте посмотрим примеры отличия UIDs. Примеры отличия для POP3:
pop.meta.ua UID8863-1253275464
pop3.ukr.net 1356790973352505619
Пример UIDs для IMAP:
imap.ukr.net 1432
Теоретически можно написать почтовый сервер, который поддерживает как IMAP, так и POP3, и использовать тот же UID для обоих протоколов, но я не знаю ни одного сервера, который будет на самом деле сделать это. На практике вы должны относиться к POP3-идентификаторам и IMAP-идентификаторам как несвязанным значениям. Вроде, все логично, но что делать, если мы работали с одним ящиком, который поддерживает и POP3 и IMAP. И вы в своей программе даете возможность указать пользователю, какой протокол для получения почты он хочет использовать. Если мы использовали, например, pop3.ukr.net, а затем решили переключиться на использование imap.ukr.net, то мы получим кучу проблем на свою голову. Потому что если посмотреть выше пример по отличию в UID для этих двух протоколов для ukr.net, то увидим основную проблему. А именно: дублирование некоторых писем. Если пользователю не важен факт загрузки писем на свой диск, то этим фактом можно пренебречь, но если факт доставки почты является существенным, этот факт нельзя игнорировать. Поэтому многие разработчики реализуют работу с протоколом IMAP как с протоколом POP3.
Как же можно "подружить" эти два протокола? После долгих размышлений с коллегами на работе и безуспешного поиска в интернете решений связывания писем, которые получены через протокол POP3, с письмами, которые получены через протокол IMAP, мы пришли к решению, как можно победить связывание писем, если у них присутствуют вложения. Первым делом нам необходимо завести базу для хранения идентификатора настроек почты (Например, ссылка на таблицу настроек почты, в которой хранятся адрес, порт, пароль и т.д.), уникальный идентификатор сообщения UID и дата получения письма. Этот способ поможет нам идентифицировать сообщения, полученные от конкретного почтового сервера для конкретного пользователя.
Следующим шагом нам необходимо создать таблицудля хранения хеш-файлов, которые идут в письме (attachment). Для того чтобы посчитать хеш, можем воспользоваться стандартными способами, доступными в .NET Framework. Например, чтобы использовать MD5, воспользуемся классом MD5CryptoServiceProvider() или другим хеш-алгоритмом из доступных HashAlgorithm на MSDN, или, по желанию, можно написать свой алгоритм. Основная суть заключается в том, что если вы храните у себя вложения, которые идут в письмах, вы можете хранить только те, которые не дублируются. Как это работает: вы загрузили письмо из сервера, посчитали его хеш, проверили, есть ли такой в таблице хешей, если такого файла нет в данной таблице, мы сохраняем посчитанный хеш для данного файла в БД, чтобы при переключении протокола не грузить этот файл повторно. После этого если мы используем протокол IMAP, можем уведомить сервер о том, что это письмо мы прочитали, и при последующем запросе он не будет нам его возвращать.
Огромный минус данного подхода  необходимость загрузки полученных писем целиком, это может занять продолжительное время, в зависимости от пропускной способности интернет-канала. В таком случае мы выравниваем протокол POP3 с IMAP и можем использовать лишь малую часть из возможностей, которые предоставляет нам современный протокол IMAP. Но мы справляемся с проблемой хранения дубликатов. Если у вас есть возможность не рассматривать переключение с POP3 на IMAP в рамках одного почтового ящика, вас эта проблема не коснется. Вы сможете использовать всю мощь протокола IMAP, а для отдельных случаев оставить POP3 таким, каким он есть.

Примечание: некоторые разработчики могут подумать, почему для таких целей не разрулить по дате получения DataReceived. В библиотеке MailBee.Net в классе MailMessage, который хранит информацию о письме, есть поле DateReceived, которое содержит дату получения письма на почту MailMessage.DateReceived Property. Но с этим полем есть проблема: оно не подходит для протокола IMAP. Для IMAP необходимо использовать свойство Envelope.DateReceived Property, но если информация не доступна по какой то причине, то оба эти свойства с DateReceived будет содержаться MinValue. Поэтому даже если у нас поде DateReceived не равно минимальной дате, не факт, что после смены протокола с POP3 на IMAP эта дата будет идентична. Одна из проблем, которая была раньше с MailBee.NET, – что дата DateReceived отличалась на один час от реального времени Incorrect Date Received Time. Поэтому еще раз хотелось бы вам посоветовать, что если есть возможность рассматривать эти два протокола как отдельные уникальные протоколы, то у вас станет на одну проблему меньше. В противном случае вам придется связывать эти два протокола, что чревато "костылями", которые если и будут работать, то с огромной натяжкой.  

No comments:

Post a Comment