уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

В продолжение темы работы с библиотекой Synapse в Delphi решил дальше углубиться в работу с почтой и рассмотреть работу с протоколом IMAP, чтобы уже было более менее полное представление о том, какие возможности дает Synapse по работе с электронной почтой.

Если говорить об IMAP очень поверхностно, то у этого протокола по сравнению с POP3 имеется очевидное преимущество, а именно: электронными письмами можно манипулировать с компьютера пользователя (клиента) без постоянной пересылки с сервера и обратно файлов с полным содержанием писем.

Думаю, что это одно из ключевых преимуществ IMAP — нет необходимости ждать пересылку всего письма с сервера, если все, что нам требуется — это посмотреть его заголовок. Естественно, у протокола IMAP имеется ещё ряд преимуществ по сравнению с POP3, но во всех прелестях IMAP мы будем знакомиться, думаю, что на протяжении ещё пары-тройки статей, а пока начнем рассмотрение работы с IMAP в Synapse с самых-самых простых операций. 

Итак, что из себя представляет протокол IMAP:

IMAP (англ. Internet Message Access Protocol) — протокол прикладного уровня для доступа к электронной почте.
Базируется на транспортном протоколе TCP и использует порт 143.
IMAP предоставляет пользователю обширные возможности для работы с почтовыми ящиками, находящимися на центральном сервере. Почтовая программа, использующая этот протокол, получает доступ к хранилищу корреспонденции на сервере так, как будто эта корреспонденция расположена на компьютере получателя. Электронными письмами можно манипулировать с компьютера пользователя (клиента) без постоянной пересылки с сервера и обратно файлов с полным содержанием писем.
Для отправки писем используется протокол SMTP.

В Synapse для работы с IMAP используется модуль imapsend.pas, который содержит один единственный класс:

TIMAPSend = class(TSynaClient)

в котором и содержатся все необходимые нас свойства и методы для работы с IMAP. Разберемся с работой этого класса на примере небольшой программы. Для начала создаем новый проект Delphi и размещаем на главной форме следующие компоненты:

Форма для логина на сервер IMAP

Форма для логина на сервер IMAP

Теперь попробуем залогиниться на почтовом сервере. Снова обращаю Ваше внимание на тот факт, что

для примера я использую работу с почтовыми серверами mail.ru. Если на Вашем почтовом сервере логин не проходит — смотрите его настройки.
Теперь подключаем в uses модуль imapsend и создаем у класса главной формы такое поле:
type
  TForm9 = class(TForm)
    [...]
  private
    ImapClient: TIMAPSend;
  public
    { Public declarations }
  end;

В OnCreate и OnDestroy формы пишем:

procedure TForm9.FormCreate(Sender: TObject);
begin
  ImapClient:=TIMAPSend.Create;
end;
 
procedure TForm9.FormDestroy(Sender: TObject);
begin
  ImapClient.Free
end;

Пишем такой обработчик OnClick:

procedure TForm9.btnLoginClick(Sender: TObject);
begin
  if SameText(btnLogin.Caption,'Login') then
    begin
      ImapClient.TargetHost:=edImapServer.Text;
      ImapClient.TargetPort:=edPort.Text;
      ImapClient.UserName:=edLogin.Text;
      ImapClient.Password:=edPassword.Text;
      ImapClient.AutoTLS:=chkAutoTLS.Checked;
      ImapClient.FullSSL:=chkSSL.Checked;
      if ImapClient.Login then
        btnLogin.Caption:='Logout'
      else
       raise Exception.Create('Ошибка выполнения команды LOGIN');
    end
  else
    begin
      if ImapClient.Logout then
        btnLogin.Caption:='Login'
      else
        raise Exception.Create('Ошибка выполнения команды LOGOUT');
    end;
end;

Теперь разберемся, что здесь происходит, зачем и почему именно так, а не иначе.

ImapClient.TargetHost:=edImapServer.Text;
  ImapClient.TargetPort:=edPort.Text;
  ImapClient.UserName:=edLogin.Text;
  ImapClient.Password:=edPassword.Text;

У класса TImapSend нет собственных свойств для логина/пароля и хоста/порта, но т.к. этот класс — наследник TSynaClient у которого эти свойства присутствуют, то мы свободно ими воспользовались.

  ImapClient.AutoTLS:=chkAutoTLS.Checked;
  ImapClient.FullSSL:=chkSSL.Checked;

Почтовый сервер может использовать как SSL, так и TLS, поэтому предусматриваем эти ситуации. При этом не забываем:

1. Подключить модуль ssl_openssl в uses
2. Положить рядом с exe библиотеки libeay32.dll и ssleay32.dll, которые Вы можете скачать со страницы исходников

Далее мы пробуем залогиниться на сервере, используя метод Login:

    if ImapClient.Login then
      btnLogin.Caption := 'Logout'
    else
      raise Exception.Create('Ошибка выполнения команды LOGIN');

Метод логин работает следующим образом:
1. Пробуем установить соединение с сервером. Если соединение выполнено, то сервер вернет нам либо

* PREAUTH ...

что будет означать, что логин уже выполнен, либо строкой

* OK ...

что будет означать, что соединение создано и надо отправить на сервер команду LOGIN, чтобы провести аутентификацию.
2. Проверяем возможности сервера. Для этого на сервер отправляется команда CAPABILITY. В результате сервер пришлет нам список своих возможностей, а также, если сервер является IMAP-сервером, то в списке возможностей должна содержаться:

IMAP4rev1 ...

3. Проверяется необходимо ли использовать TLS при работе. Если в списке возможностей имеется STARTTLS, то пробуем использовать TLS (для этого свойство AutoTLS должно быть True) и снова отправляем команду CAPABILITY, чтобы заполнить список возможностей сервера.
4. Пробуем отправить команду LOGIN на сервер. Для этого используется protected-метод AuthLogin. Логин и пароль в этом случае отправляются в открытом виде.
5. Если логин прошел успешно, то сервер ответит «OK», а свойство класса AuthDone станет равным True.

После того, как мы залогинились на сервере мы можем сразу посмотреть где мы находимся в данный момент и отправлять на сервер различные команды. Итак, вначале посмотрим, что будут содержать свойства TImapSend после логина на сервере mail.ru.

Для того, чтобы посмотреть, что отвечает нам сервер я положил на форму компонент TMemo, написал такой обработчик события OnReadFilter сокета:

procedure TForm9.DataFilter(Sender: TObject; var Value: AnsiString);
begin
  memLog.Lines.Add(Value)
end;

и назначил этот обработчик в OnCreate формы:

procedure TForm9.FormCreate(Sender: TObject);
begin
  ImapClient := TIMAPSend.Create;
  ImapClient.Sock.OnReadFilter:=DataFilter;
end;

Теперь запускаем нашу программку, задаем логин и пароль доступа к почтовому ящику на mail.ru и пробуем залогиниться. После клика по кнопке «Login» в Memo появятся такие строки, содержащие ответы сервера:
* OK Welcome
* CAPABILITY IMAP4rev1 ID XLIST UIDPLUS STARTTLS LOGINDISABLED
S1 OK CAPABILITY completed
S2 NO Command disabled. Please use STARTTLS first.

все в соответствии с тем, как работает метод Login у TImapSend, т.е.:
1. Соединение было установлено, сервер с нами поздаровался и предложил залогиниться (строка 1)
2. Мы попросили список возможностей сервер и сервер нам ответил (строки 2,3)
3. Мы попробовали отправить на сервер логин и пароль, но сервер нам отказал в аутентификации, т.к. мы забыли подключить TLS.
Теперь, включаем TLS (ставим флажок у чекбокса AutoTLS) и снова пробуем залогиниться. Логин проходит успешно, о чем нам говорит лог:
* OK Welcome
* CAPABILITY IMAP4rev1 ID XLIST UIDPLUS STARTTLS LOGINDISABLED
S3 OK CAPABILITY completed
S4 OK Starting TLS.
* CAPABILITY IMAP4rev1 ID XLIST UIDPLUS AUTH=PLAIN
S5 OK CAPABILITY completed
* CAPABILITY IMAP4rev1 ID XLIST UIDPLUS
S6 OK Authentication successful

Теперь посмотрим, что содержат свойства класса после логина.

property ResultString: string read FResultString;

Статусная строка, содержащая результат последней операции. После успешной аутентификации она у нас будет содержать:
S4 OK Authentication successful
и, соответственно, свойство

property AuthDone: Boolean read FAuthDone;

будет равно True.
Следующее свойство

property FullResult: TStringList read FFullResult;

содержит весь результат выполнения последней IMAP-операции и у нас это свойство сейчас будет равно:
* CAPABILITY IMAP4rev1 ID XLIST UIDPLUS

property IMAPcap: TStringList read FIMAPcap;

Список возможностей сервера. После успешного логина будет содержать:
IMAP4rev1
ID
XLIST
UIDPLUS
AUTH=PLAIN

Остальные свойства нашего клиента буду принимать значения в зависимости от того, какие операции мы будем производить на сервере.
Теперь разберемся как выполнять различные команды на IMAP-сервере. Для выполнения любых IMAP-команд у TImapSend предусмотрены два метода:

function IMAPcommand(Value: string): string;
function IMAPuploadCommand(Value: string; const Data:TStrings): string;

Первый метод можно использовать для отправки команд на IMAP-сервер, которые не требуют загрузки каких-либо данных, второй — для команд предполагающих загрузку информации на сервер.
Для выполнения определенных IMAP-команда у класса имеется целый ряд методов:

function List(FromFolder: string; const FolderList: TStrings): Boolean;
function ListSearch(FromFolder, Search: string; const FolderList: TStrings): Boolean;
function CreateFolder(FolderName: string): Boolean;
function SelectFolder(FolderName: string): Boolean;

и другие методы, каждый из которых выполняет одну или несколько IMAP-команд. Разберемся как пользоваться этими методами класса.
Для начала попробуем получить с сервера список папок в почтовом ящике. К примеру, у меня в аккаунте на mail.ru есть такие папки:

Папки для писем на mail.ru

Папки для писем на mail.ru

Чтобы получить их названия от IMAP-сервера мы можем  воспользоваться методом List у класса. Например, так:

ImapClient.List('',memLog.Lines)

В результате в Memo будут помещены следующие строки:
INBOX
&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-
&BCcENQRABD0EPgQyBDgEOgQ4-
&BBoEPgRABDcEOAQ9BDA-
&BCEEPwQwBDw

Жутковато, не правда ли? Что это за кодировка? Это модифицированная под IMAP кодировка UTF-7, которая носит название UTF7-IMAP. И Synapse справляется с такой кодировкой легко. Чтобы привести строку, например &BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1- в нормальный человеческий вид нужно подключить в uses модуль synachar и сделать вот такой вызов:

CharsetConversion('&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-',TMimeChar.UTF_7mod,TMimeChar.CP1251)

Теперь посмотрим, что содержит свойство FullResult после выполнения метода List. А содержит свойство следующие строки:
* LIST (\Inbox) "/" "INBOX"
* LIST (\Sent) "/" "&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-"
* LIST (\Drafts) "/" "&BCcENQRABD0EPgQyBDgEOgQ4-"
* LIST (\Trash) "/" "&BBoEPgRABDcEOAQ9BDA-"
* LIST (\Spam) "/" "&BCEEPwQwBDw-"

Кто все эти люди строки? Давайте разбираться вместе. Разберем первую строку:
(\Sent) — это атрибут имени. Вообще, судя по rfc, этот атрибут может принимать одно из четырех значений:

\Noinferiors — для этого уровня иерархии невозможно существование любых дочерних уровней — их нет сейчас и не может быть в будущем.
\Noselect — это имя нельзя использовать как выбранный почтовый ящик.
\Marked — почтовый ящик отмечен сервером как «интересный» — возможно он содержит сообщения, добавленные с момента его предыдущего выбора.
\Unmarked — почтовый ящик не содержит сообщений, добавленных после предыдущего обращение к нему.

Но mail.ru на команду LIST возвращает ответ как и на команду XLIST, которая по сути-то и должна вернуть расширенные атрибуты типа \Trash, \Spam и т.д. Ну раз вернул сервер такие данные — будем работать с ними.

«/» — разделитель уровней иерархии

«какая-то строка» — имя папки.

С папками более менее разобрались. Теперь резюмируем. Итак, чтобы получить имена папок в нормальном (читабельном) виде необходимо вызвать метод List класса TImapSend и в полученном списке каждую строку преобразовать методом CharsetConversion. В результате у Вас должен получиться, например, такой код:

procedure TForm9.Button1Click(Sender: TObject);
var
  FolderList: TStringList;
  I: Integer;
begin
  FolderList:=TStringList.Create;
  try
    ImapClient.List('',FolderList);
    for I := 0 to FolderList.Count-1 do
      memLog.Lines.Add(CharsetConversion(FolderList[i],UTF_7mod, CP1251))
  finally
    FolderList.Free;
  end;
end;

Следующий шаг работы с IMAP-сервером — выбор необходимой папки. Для этого у класса TImapSend имеется специальный метод:

function SelectFolder(FolderName: string): Boolean;

Например, если я сделаю вот такой вызов метода:

ImapClient.SelectFolder('INBOX')

то в результате FullResult будет содержать следующие отклики IMAP-сервера:
* FLAGS (\Answered \Flagged \Deleted \Draft \Seen)
* 107 EXISTS
* 0 RECENT
* OK [UNSEEN 12] * OK [UIDVALIDITY 1350407122] * OK [PERMANENTFLAGS (\Flagged \Deleted \Seen)] * OK [UIDNEXT 2390]

А у самого объекта ImapClient свойства будут заполнены следующими значениями:
ResultString: S5 OK [READ-WRITE] SELECT completed
SelectedFolder: INBOX
SelectedCount: 107
SelectedRecent: 0
SelectedUIDvalidity: 1350407122

т.е. в данном случае сервер нам вернул информацию о том, что общее число писем — 107, новых 0, UID папки 1350407122. Это то, что запомнилось в свойствах TImapSend. По FullResult мы можем сказать также, что количество непросмотренных писем — 12, а следующему письму будет назначен UID 2390.
Теперь попробуем получить список сообщений. Для этого вначале необходимо воспользоваться методом:

function SearchMess(Criteria: string; const FoundMess: TStrings): Boolean;

Этот метод вернет нам список номеров сообщений — чем меньше номер, тем старее письмо. Затем вызвать один из двух методов:

function FetchMess(MessID: integer; const Mess: TStrings): Boolean;

Для получения всего сообщения с MessID.

function FetchHeader(MessID: integer; const Headers: TStrings): Boolean;

Для того, чтобы получить только заголовки сообщения.
В качестве примера, код ниже демонстрирует как можно получить список непрочитанных писем (с флагом \UNSEEN) и прочитать из этого списка самое последнее письмо:

var MessList: TStringList;
begin
MessList:=TStringList.Create;
try
  if ImapClient.SearchMess('UNSEEN',MessList) then
    begin
      memLog.Clear;
      ImapClient.FetchMess(StrToInt(MessList[MessList.Count-1]),memLog.Lines);
    end;
finally
  MessList.Free;
end;

На этом моменте мы сегодня и остановимся. Подведем небольшой итог. Итак, сегодня мы:
1. Научились логиниться на сервере IMAP
2. Научились получать список папок и правильно их преобразовывать в читабельный вид
3. Научились выбирать определенную папку
4. Научились получать необходимый нам список номеров сообщений и получать сообщения.
Для первого раза неплохо. В следующий раз разберемся с тем как можно работать с сообщениями по протоколу IMAP.

Книжная полка

Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
5 1 голос
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
26 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Evgeny Popov
04/01/2013 04:27

Огромное спасибо за мою неявную просьбу.

Guest
Guest
05/01/2013 23:52

function SearchMess(Criteria: string; const FoundMess: TStrings): Boolean;

Привет, можно узнать критерии этой функции? Заранее спасибо.

Guest
Guest
06/01/2013 15:01

ImapClient.SearchMess(‘TEXT «Guest»‘,memLog.Lines);
почему данная функция может не срабатывать?

Max
Max
16/02/2013 00:18

Супер!! есть 1 вопрос помогите плз. как найти сообшению например которые пришли с mail.ru? очень надо )

Roman Kolesnik
27/02/2013 22:03

Я правильно понимаю, что критерии размещены здесь — http://www.intuit.ru/department/internet/sendmail/7/6.html (Таблица 7.3.)?
Хочу вытянуть письма только от одного отправителя, пробовал FROM email@domain.com, а в ответ получаю «NO NOT IMPLEMENTED».
Что это может быть? На сервере нет поддержки FROM?
Работаю с mail.ru

Дмитрий
Дмитрий
22/10/2013 14:33
Ответить на  Roman Kolesnik

Можно попробовать SearchMess(‘header from name’…

Дмитрий
Дмитрий
22/10/2013 14:35
Ответить на  Дмитрий

SearchMess(‘header from email@domain.com‘….

Roman Kolesnik
27/03/2013 20:58

Делал программу по Вашим урокам. На WinXP, Win7 все отлично, все работает, но вот на Win8 программа вываливается на строке
if ImapClient.Login then, а точнее — выдает Exception
raise Exception.Create(‘Ошибка выполнения команды LOGIN’);
Не подскажете в чем может быть дело?

Сергей
Сергей
22/04/2013 16:55

Поясните где писать «обработчик события OnReadFilter сокета»

Сергей
Сергей
23/04/2013 19:13

Спасибо за пояснения. Вот, что выдает сервер при выборе папки * 495 EXISTS * 2 RECENT * OK [UIDNEXT 3540] predicted next UID * OK [UIDVALIDITY 293298895] UIDs valid * OK [UNSEEN 357] message 357 is first unseen то есть не прочитанных 357, а их там 41 всего. В чем может быть ошибка? P.S. Делаю прогу, чтобы парсила письма с вложением из почты с определенных папок, сами вложения не нужны, из автоматом крепит отправитель, и из за это не получается прочитать текст письма из которого нужно выделить от трех до пяти значений и занести в БД. Дайте хотя бы направление… Подробнее »

Сергей
Сергей
24/04/2013 01:55
Ответить на  Сергей

из автоматом = их автоматом

Сергей
Сергей
24/04/2013 11:53

Кажется разобрался. 357 это первое не прочитанное.

Nikolay
Nikolay
14/10/2013 03:30

Дико извиняюсь, но хотелось бы узнать как добавить прокси к имап запросу? Уже все облазил а так и не нашел и никто не хочет отвечать (( Помогите. Подкиньте совет или хотя бы в какую сторону смотреть

Avazart
Avazart
21/12/2013 19:52

А средства для парсинга полученного сообщения предусмотрены вообще?
Как «разделить» письмо на тему, от кого, время, тело письма?
Есть аналог IdMessage из Indy ?
Или все руками ?

Артём
Артём
31/12/2016 23:45
Ответить на  Avazart

Поддерживаю вопрос

Donni
Donni
18/01/2014 15:11

А как правильно в imap использовать socks прокси? Что то у меня не выходит=(

Дмитрий Белковский

Использую следующий кусок кода ()
imap.Sock.OnReadFilter:=DataFilter;
imap.TargetHost:=cbHost.Text; // imap.mail.ru
imap.TargetPort:=EditPort.Text; // 143
imap.UserName:=EditLogin.Text;
InputQuery(‘Enter the password’, ‘Password’, p);
imap.Password:=p;
imap.AutoTLS:=true;
imap.FullSSL:=false;

if imap.Login then begin…
в общем-то логин не проходит (пасс верный), при отладке в синапсе встречаю error 10091 и SSL/TLS support is not compiled!
обе длл-ки лежат в папке с экзешником. Подскажите какие могут быть причины? Может сталкивались?

Дмитрий Белковский
Ответить на  Vlad

Спасибо, помогло.
Я рад что постоянно читаю Ваш блог, и благодаря ему пользуюсь библиотекой синапс.

Ekuzkanaza
Ekuzkanaza
09/10/2014 14:41

function SearchMess(Criteria: string; const FoundMess: TStrings): Boolean;

ImapClient.SearchMess(‘*’, MessList);
не дает список номеров дает количество сообщений

for m := 1 to StrToInt(Trim(MessList.Text)) — 1 do
ImapClient.FetchHeader(m, HeaderInfo);
так выбираем сообщения

Артём
Артём
03/01/2017 20:35

В IMAP есть возможность мгновенного получения входящих писем без необходимости каждые X минут обновлять данные с сервера (IMAP IDLE называется). Поддерживает ли эту возможность Synapse?