В продолжение темы работы с библиотекой 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 и размещаем на главной форме следующие компоненты:
Теперь попробуем залогиниться на почтовом сервере. Снова обращаю Ваше внимание на тот факт, что
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.
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Огромное спасибо за мою неявную просьбу.
да не за что =) Пользуйтесь на здоровье
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 не поддерживает такой поиск
Можно попробовать SearchMess(‘header from name’…
SearchMess(‘header from email@domain.com‘….
Делал программу по Вашим урокам. На 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. Делаю прогу, чтобы парсила письма с вложением из почты с определенных папок, сами вложения не нужны, из автоматом крепит отправитель, и из за это не получается прочитать текст письма из которого нужно выделить от трех до пяти значений и занести в БД. Дайте хотя бы направление… Подробнее »
из автоматом = их автоматом
Кажется разобрался. 357 это первое не прочитанное.
Дико извиняюсь, но хотелось бы узнать как добавить прокси к имап запросу? Уже все облазил а так и не нашел и никто не хочет отвечать (( Помогите. Подкиньте совет или хотя бы в какую сторону смотреть
А средства для парсинга полученного сообщения предусмотрены вообще?
Как «разделить» письмо на тему, от кого, время, тело письма?
Есть аналог IdMessage из Indy ?
Или все руками ?
Поддерживаю вопрос
А как правильно в 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!
обе длл-ки лежат в папке с экзешником. Подскажите какие могут быть причины? Может сталкивались?
А модуль ssl_openssl в uses подключен?
Спасибо, помогло.
Я рад что постоянно читаю Ваш блог, и благодаря ему пользуюсь библиотекой синапс.
Не за что. Приходите к нам ещё :)
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);
так выбираем сообщения
В IMAP есть возможность мгновенного получения входящих писем без необходимости каждые X минут обновлять данные с сервера (IMAP IDLE называется). Поддерживает ли эту возможность Synapse?