В продолжение темы работы с библиотекой Synapse в Delphi решил дальше углубиться в работу с почтой и рассмотреть работу с протоколом IMAP, чтобы уже было более менее полное представление о том, какие возможности дает Synapse по работе с электронной почтой.
Если говорить об IMAP очень поверхностно, то у этого протокола по сравнению с POP3 имеется очевидное преимущество, а именно: электронными письмами можно манипулировать с компьютера пользователя (клиента) без постоянной пересылки с сервера и обратно файлов с полным содержанием писем.
Думаю, что это одно из ключевых преимуществ IMAP — нет необходимости ждать пересылку всего письма с сервера, если все, что нам требуется — это посмотреть его заголовок. Естественно, у протокола IMAP имеется ещё ряд преимуществ по сравнению с POP3, но во всех прелестях IMAP мы будем знакомиться, думаю, что на протяжении ещё пары-тройки статей, а пока начнем рассмотрение работы с IMAP в Synapse с самых-самых простых операций.
Итак, что из себя представляет протокол IMAP:
Базируется на транспортном протоколе TCP и использует порт 143.
IMAP предоставляет пользователю обширные возможности для работы с почтовыми ящиками, находящимися на центральном сервере. Почтовая программа, использующая этот протокол, получает доступ к хранилищу корреспонденции на сервере так, как будто эта корреспонденция расположена на компьютере получателя. Электронными письмами можно манипулировать с компьютера пользователя (клиента) без постоянной пересылки с сервера и обратно файлов с полным содержанием писем.
Для отправки писем используется протокол SMTP.
В Synapse для работы с IMAP используется модуль imapsend.pas, который содержит один единственный класс:
TIMAPSend = class(TSynaClient) |
в котором и содержатся все необходимые нас свойства и методы для работы с IMAP. Разберемся с работой этого класса на примере небольшой программы. Для начала создаем новый проект Delphi и размещаем на главной форме следующие компоненты:
Теперь попробуем залогиниться на почтовом сервере. Снова обращаю Ваше внимание на тот факт, что
Теперь подключаем в 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, поэтому предусматриваем эти ситуации. При этом не забываем:
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 есть такие папки:
Чтобы получить их названия от 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.
Если Вы находитесь в поиске хорошего антивируса, то обратите внимание на антивирус касперского, так как сейчас проходит акция "курс английского в подарок к антивирусу" - получите двойную пользу: и защиту компа прокачаете и английский выучите.
| Делись! | Загружай! | Плюсуй! | Скажи "Спасибо"! |
| | | |













Огромное спасибо за мою неявную просьбу.
да не за что =) Пользуйтесь на здоровье
function SearchMess(Criteria: string; const FoundMess: TStrings): Boolean;
Привет, можно узнать критерии этой функции? Заранее спасибо.
ImapClient.SearchMess(‘TEXT «Guest»‘,memLog.Lines);
почему данная функция может не срабатывать?
Супер!! есть 1 вопрос помогите плз. как найти сообшению например которые пришли с mail.ru? очень надо )
Я правильно понимаю, что критерии размещены здесь — http://www.intuit.ru/department/internet/sendmail/7/6.html (Таблица 7.3.)?
Хочу вытянуть письма только от одного отправителя, пробовал FROM email@domain.com, а в ответ получаю «NO NOT IMPLEMENTED».
Что это может быть? На сервере нет поддержки FROM?
Работаю с mail.ru
Roman Kolesnik, так точно. Это и есть критерии поиска. Вполне возможно, что mail.ru не поддерживает такой поиск
Делал программу по Вашим урокам. На WinXP, Win7 все отлично, все работает, но вот на Win8 программа вываливается на строке
if ImapClient.Login then, а точнее — выдает Exception
raise Exception.Create(‘Ошибка выполнения команды LOGIN’);
Не подскажете в чем может быть дело?
не-а, тут точно не подскажу — у меня Windows 8 нету :)
Поясните где писать «обработчик события OnReadFilter сокета»
ну так в статье же написано…
ля того, чтобы посмотреть, что отвечает нам сервер я положил на форму компонент 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;
Спасибо за пояснения.
Вот, что выдает сервер при выборе папки
* 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. Делаю прогу, чтобы парсила письма с вложением из почты с определенных папок, сами вложения не нужны, из автоматом крепит отправитель, и из за это не получается прочитать текст письма из которого нужно выделить от трех до пяти значений и занести в БД.
Дайте хотя бы направление куда копать (каким протоколом лучше пользоваться Imap/pop3)
из автоматом = их автоматом
Кажется разобрался. 357 это первое не прочитанное.