The OpenNET Project / Index page

[ новости/++ | форум | wiki | теги ]

Каталог документации / Раздел "Электронная почта" (Архив | Для печати)

Обзор протокола IMAP4 rev 1

Автор: Сергей Холодилов (sergh)
The RSDN Group
Оригинал: sergh.pisem.net

Версия текста: 1.0

Overview
Типы данных
Взаимодействие клиента и сервера - общий взгляд
Состояние сессии - общий взгляд
Содержимое почтового ящика - общий взгляд
Команды
Соединение
LOGIN
LOGOUT
LIST
CREATE
DELETE
RENAME
SELECT
CLOSE
FETCH
APPEND
STORE
COPY
Остальные команды
И ещё три слова про команды

Этот документ - часть моего отчёта по лабораторной работе, цель которой состояла в реализации простого клиента IMAP4 rev1. Глубже, чем было нужно по работе, я в IMAP не лез. Когда в тексте упоминается "проект" речь идёт о написанной мною реализации. Также в тексте периодически встречаются упоминания "pochta.ru", это бесплатный почтовый сервис http://pochta.ru, один из немногих, предоставляющих доступ к ящику по протоколу IMAP4. Проект тестировался только с pochta.ru, с ними работает нормально.

Описание несколько упрощено, но зато общая идея становится ясна быстро и без потерь. Заранее признаю - не всё в стандарте IMAP4 мне понятно, но всё было и не нужно. К счастью. То, что описано - понятно.

Чтобы не соблазнять спамеров, встречающиеся в тексте почтовые и IP адреса изменены.

Overview

Протокол IMAP4 rev 1 описан в RFC 2060, принятом в 1996 году. IMAP предназначен для управления письмами, хранящимися на почтовом сервере, аббревиатура расшифровывается как Internet Message Access Protocol.

Протокол довольно сложный, имеет 24 стандартные команды (сравните с 12-ю командами POP3), плюс возможны расширения.

Типы данных

Помимо обычных и интуитивно понятных типов данных, RFC 2060 описывает два типа строк:

Более точное определение строк:

string          ::= quoted / literal

quoted          ::= <"> *QUOTED_CHAR <">
QUOTED_CHAR     ::= <any TEXT_CHAR except quoted_specials> / "\" quoted_specials
quoted_specials ::= <"> / "\"
TEXT_CHAR       ::= <any CHAR except CR and LF>
CHAR            ::= <any 7-bit US-ASCII character except NUL, 0x01 - 0x7f>

literal         ::= "{" number "}" CRLF *CHAR8
                    ;; Number represents the number of CHAR8 octets
CHAR8           ::= <any 8-bit octet except NUL, 0x01 - 0xff>

Взаимодействие клиента и сервера - общий взгляд

Соединение устанавливается клиентом, используется протокол TCP, 143-й порт. Клиент посылает серверу команды (commands), сервер посылает клиенту ответы (responses). Команды выглядят так:

тег команда <аргументы> CRLF

Где тег - любая последовательность букв, цифр и ещё некоторых символов, уникальная в пределах сессии. Ответы сервера относятся к одной из трёх групп:

Типичный обмен сообщениями выглядит так:

> произвольный_тег команда <аргументы> CRLF
< * какие-то данные CRLF
< * ещё какие-то данные CRLF
< тот_же_самый_тег OK команда Completed CRLF

Неуспешное выполнение:

> произвольный_тег команда <аргументы> CRLF
< * какие-то данные CRLF
< * ещё какие-то данные CRLF
< тот_же_самый_тег NO у вас документов нет CRLF

В случае передачи литеральной строки на сервер:

> произвольный_тег команда <аргументы> {длина}CRLF
< + go ahead CRLF
> <данные, столько, сколько указано> CRLF
< * какие-то данные CRLF
< тот_же_самый_тег OK команда Completed CRLF

Если литеральную строчку нужно передать серверу, он не спрашивает разрешения, а просто передаёт.

> произвольный_тег команда <аргументы> CRLF
< * какие-то данные {длина}CRLF
< <данные, сколько указано>CRLF
< тот_же_самый_тег OK команда Completed CRLF

Такое взаимодействие имеет две особенности:

Состояние сессии - общий взгляд

Взаимодействуя с сервером, клиент переводит его в одно из пяти состояний. Приведённый в RFC 2060 граф переходов:

            +--------------------------------------+
            |initial connection and server greeting|
            +--------------------------------------+
                      || (1)       || (2)        || (3)
                      VV           ||            ||
            +-----------------+    ||            ||
            |non-authenticated|    ||            ||
            +-----------------+    ||            ||
             || (7)   || (4)       ||            ||
             ||       VV           VV            ||
             ||     +----------------+           ||
             ||     | authenticated  |<=++       ||
             ||     +----------------+  ||       ||
             ||       || (7)   || (5)   || (6)   ||
             ||       ||       VV       ||       ||
             ||       ||    +--------+  ||       ||
             ||       ||    |selected|==++       ||
             ||       ||    +--------+           ||
             ||       ||       || (7)            ||
             VV       VV       VV                VV
            +--------------------------------------+
            |     logout and close connection      |
            +--------------------------------------+

Состояния:

ПРИМЕЧАНИЕ

Это терминология RFC 2060, на мой взгляд, состояние следовало назвать "До соединения". Потому что "установка соединения и приветствие" это, на мой взгляд, не состояние, а процесс перехода из одного состояния в другое.

Переходы (цифры соответствуют цифрам на графе):

  1. Обычное соединение
  2. Соединение с предварительной аутентификацией (в случае, если аутентификация выполнена некоторым "внешним" способом и клиенту не нужно аутентифицироваться явно).
  3. Отклонённое соединение
  4. Успешная аутентификация (команды LOGIN или AUTHENTICATE)
  5. Успешное выделение папки (SELECT или EXAMINE)
  6. Закрытие папки или неудачная попытка выделить папку (CLOSE, SELECT, EXAMINE)
  7. Выход (команда LOGOUT), остановка сервера или разрыв соединения.

Очевидно, команды можно посылать только тогда, когда сессия находится в одном из трёх состояний:

В разных состояниях допустимы разные команды, где команду использовать можно, а где нельзя понятно по смыслу команды, здесь эта информация не приводится, в RFC она есть.

Содержимое почтового ящика - общий взгляд

Тезисно:

Команды

Соединение

Сразу после успешного соединения, сервер посылает клиенту приветствие. Оно может выглядеть так (типичный случай):

< * OK Привет, юзер!

Так (с автоматической аутентификацией):

< * PREAUTH Здравствуйте, Иван Иванович!

Или, если не повезло, так:

< * BYE Ушла на базу, вернусь после пяти

LOGIN

Аутентификация:

> a000 LOGIN user password
< a000 OK LOGIN COMPLETED

Да, пароль идёт открытым текстом. Более защищённый вариант предлагает команда AUTHENTICATE, но с ней я не стал разбираться.

LOGOUT

Завершение сессии и выход

> a000 LOGOUT
< * BYE IMAP4rev1 Server logging out
< a000 OK LOGOUT COMPLETED

LIST

Возвращает список папок

> a000 LIST arg1 arg2
< * LIST (<флаги>) "<символ-разделитель в иерархических именах>" "<имя папки>"
< ...
< * LIST (<флаги>) "<символ-разделитель в иерархических именах>" "<имя папки>"
< a000 OK LIST COMPLETED

Аргументы:

Результаты:

В проекте используется только форма LIST "" *

CREATE

Создаёт папку (см. замечание об именах папок в конце статьи). Сервер SHOULD уметь создавать сразу весь запрашиваемый путь. Но не MUST

ПРИМЕЧАНИЕ

На случай, если вы не в курсе: SHOULD и MUST - стандартные RFC-шные термины.

> a000 CREATE "qwerty"
< a000 OK CREATE completed

DELETE

Удаляет папку. Сложное поведение в случае существования вложенных папок:

> a000 DELETE "qwerty"
< a000 OK DELETE completed

RENAME

Переименовывает папку

> a000 RENAME "qwerty" "asdfg"
< a000 OK RENAME completed

SELECT

Выделяет папку, возвращает различную информацию о папке.

> a000 SELECT INBOX
< * 172 EXISTS
< * 1 RECENT
< * OK [UNSEEN 12] Message 12 is first unseen
< * OK [UIDVALIDITY 3857529045] UIDs valid
< * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
< * OK [PERMANENTFLAGS (\Deleted \Seen)] Limited
< a000 OK [READ-WRITE] SELECT completed

Разберём ответ построчно (приблизительно и не очень точно):

< * 172 EXISTS

Количество писем в папке

< * 1 RECENT

Количество новых писем в папке

< * OK [UNSEEN 12] Message 12 is first unseen

Необязательная строчка. Первое не просмотренное письмо.

< * OK [UIDVALIDITY 3857529045] UIDs valid

UIDVALIDITY папки.

< * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)

Флаги, определённые в папке. Других флагов папке по идее нет.

< * OK [PERMANENTFLAGS (\Deleted \Seen)] Limited 

Флаги, которые можно менять.

< a000 OK [READ-WRITE] SELECT completed

Команда завершилась успешно, предоставлен доступ на чтение и запись.

Проект обрабатывает только первые две строчки, остальное просто пропускает.

CLOSE

Снимает выделение с текущей папки, удаляя при этом все сообщения, помеченные флагом \Deleted.

> a000 CLOSE
< a000 OK CLOSE COMPLETED

FETCH

Возвращает указанные части указанных сообщений. В проекте применяются два варианта:

> A0028 FETCH 1 (flags body.peek[header.fields (Date from to subject)])
< * 1 FETCH (FLAGS (\Answered \Seen) BODY[HEADER.FIELDS (Date from to subject)] {282}
< From: Ivan Ivanich <ivan@ivanovich.ru>
< To: =?windows-1251?Q?=27=FF=C5=CF=D6=C5=C8_=F3=CD=CA=CD=C4=D5=CA=CD=C1=27?=
< 	 <xxxxx@pisem.net>
< Subject: =?windows-1251?Q?RE=3A_SharePoint_=D5_=C1=DF=C5_=C1=DF=C5_=C1=DF?=
< 	=?windows-1251?Q?=C5?=
< Date: Tue, 22 Jan 2002 11:07:52 +0300
< 
< )
< A0028 OK Completed (0.000 sec)
> A0032 FETCH 1 body[]
< * 1 FETCH (BODY[] {2531}
< X-Sieve: cmu-sieve 2.0
< Return-Path: <ivan@ivanovich.ru>
< Received: from imap.front.ru (imap.front.ru [80.68.244.14])
< 	by imap.pisem.net (8.12.1/8.12.1) with ESMTP id g0M87LQ9090073
< 	for <xxxxx@imap.pisem.net>; Tue, 22 Jan 2002 11:07:21 +0300 (MSK)?g
< 	(envelope-from ivan@ivanovich.ru)
< Received: from bull.ivanovich.spb.su (mail.ivanovich.ru [000.000.00.000])
< 	by imap.front.ru (8.12.2/8.12.2) with ESMTP id g0M85H2X055212
< 	for <xxxxx@pisem.net>; Tue, 22 Jan 2002 11:05:33 +0300 (MSK)
< 	(envelope-from ivan@ivanovich.ru)
< Received: by bull.ivanovich.spb.su with Internet Mail Service (5.5.2650.21)
< 	id <DLLJ6ACF>; Tue, 22 Jan 2002 11:07:56 +0300
< Message-ID: <79816F81D569D5119F5B0060972CEF7C6188E5@bull.ivanovich.spb.su>
< From: Ivan Ivanich <ivan@ivanovich.ru>
< To: =?windows-1251?Q?=27=FF=C5=CF=D6=C5=C8_=F3=CD=CA=CD=C4=D5=CA=CD=C1=27?=
< 	 <xxxxx@pisem.net>
< Subject: =?windows-1251?Q?RE=3A_SharePoint_=D5_=C1=DF=C5_=C1=DF=C5_=C1=DF?=
< 	=?windows-1251?Q?=C5?=
< Date: Tue, 22 Jan 2002 11:07:52 +0300
< MIME-Version: 1.0
< X-Mailer: Internet Mail Service (5.5.2650.21)
< Content-Type: text/plain;
< 	charset="windows-1251"
< Content-Transfer-Encoding: base64
< 
< 0eXw5e...

... (поскольку это настоящее письмо от настоящего человека, я его не публикую)

< DQoNCtEg8+Lg5uXt6OXsLA0K3vDgDQo=
< 
< )
< A0032 OK Completed (0.000 sec)

Кратко про аргументы:

А формат ответа проще посмотреть, чем описывать словами.

APPEND

Записывает письмо в папку.

> a000 APPEND имя-папки {310}
< + go ahead
> Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
> From: Fred Foobar <foobar@Blurdybloop.COM>
> Subject: afternoon meeting
> To: mooch@owatagu.siam.edu
> Message-Id: <B27397-0100000@Blurdybloop.COM>
> MIME-Version: 1.0
> Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
>
> Hello Joe, do you think we can meet at 3:30 tomorrow?
>
< a000 OK APPEND Complete
ПРИМЕЧАНИЕ

В примере в RFC 2060 пропущен ответ сервера "+ go ahead", это ошибка, клиент не имеет права отправлять данные до получения подтверждения.

Аргументом команды является имя папки и само письмо, ещё при желании можно передать флаги и дату (здесь не указано, так как в проекте эти аргументы не используются). Письмо должно быть корректным письмом, то есть соответствовать RFC 822.

Если письмо добавляется в текущую папку, и добавление прошло успешно, сервер SHOULD отправить уведомление вида * 123 EXISTS, посчитав и новое письмо. Моя реализация на это очень рассчитывает, pochta.ru соответствует.

STORE

Изменяет указанные флаги указанных сообщений. Так - добавляет к списку флагов указанные:

> A003 STORE 1 +FLAGS.SILENT (\Seen \Deleted)
< A003 OK STORE completed

Так - удаляет из списка флагов:

> A003 STORE 1 -FLAGS.SILENT (\Deleted)
< A003 OK STORE completed

Так - заменяет список флагов:

> A003 STORE 1 FLAGS.SILENT (\Seen \Deleted)
< A003 OK STORE completed

Если вместо FLAGS.SILENT использовать просто FLAGS, сервер будет посылать дополнительные ответы с информацией о состоянии флагов затронутых писем. Например так:

> A003 STORE 2:4 +FLAGS (\Deleted)
< * 2 FETCH FLAGS (\Deleted \Seen)
< * 3 FETCH FLAGS (\Deleted)
< * 4 FETCH FLAGS (\Deleted \Flagged \Seen)
< A003 OK STORE completed 

COPY

Копирует сообщение в указанную папку

> A003 COPY 1 "qwerty"
< A003 OK COPY completed

Остальные команды

Следующие команды не реализованы по причине сложности и/или моей лени. Они добавляют протоколу дополнительные возможности, которые не поддерживаются проектом:

Следующие команды могли быть реализованы, но не понадобились. Никаких особых новых возможностей не добавляют:

И ещё три слова про команды

Во-первых, про имена. С именами папок всё не просто. С одной стороны, все команды, работающие с папками (LIST, CREATE, DELETE, RENAME, SELECT, EXAMINE, APPEND) доступны в режиме "Клиент аутентифицирован, папка НЕ выбрана". Логично предположить, что в этом режиме должны использоваться полные имена папок. Логично предположить, что в режиме "Выбрана текущая папка" эти команды должны работать так же, то есть использовать полные имена (по принципу наименьшего удивления). Логично предположить, что и единственная оставшаяся команда COPY будет работать с полными именами. С другой стороны, в RFC это явно не оговаривается, зато упоминаются некие "пространства имён". В общем, дело тёмное. Но на любимой pochta.ru все имена должны быть полные, ещё одно очко в пользу логики.

Во-вторых, про строки. Согласно RFC 2060, везде, где можно употребить обычную строку, можно употребить и литерал. Например, можно так:

> a000 LOGIN {31}
< + go ahead
> длинное русское имя с пробелами {8}
< + go ahead
> password
< a000 OK LOGIN Completed

Здесь первые два перевода строки являются составной частью литералов, последний - завершает команду. Другой вопрос - поддержит ли такое поведение сервер. И не отреагирует ли он "симметричным ответом"?

В-третьих, про UID. Если вы пишите не лабораторную работу, а настоящего клиента, наверное их придётся использовать. Команды и идеи должны быть те же, а вот детали и подробности.. Ничем не могу помочь, не пробовал.


Любой из материалов, опубликованных на sergh.pisem.net, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. (статья размещена на opennet.ru с согласия автора)

  Закладки на сайте
  Проследить за страницей
Created 1996-2017 by Maxim Chirkov  
ДобавитьРекламаВебмастеруГИД  
Hosting by Ihor