Как написать свой почтовый клиент

Воз­можно, ты уже уме­ешь писать прос­тые сце­нарии на Python, но про­бовал ли ты делать гра­фичес­кие прог­раммы с нас­тоящи­ми работа­ющи­ми кно­поч­ками? Поверь, это тоже край­не увле­катель­но и не очень слож­но! Сегод­ня мы поп­ракти­куем­ся в этом, написав на Python и Qt прос­той поч­товик, который будет отправ­лять пись­ма с атта­чами.

Содержание

  1. Как написать почтовик на Python и Qt
  2. Создание интерфейса почтовика
  3. Создание основной части почтовика
  4. Настройки почты
  5. Заключение

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

Создание интерфейса почтовика

Для начала давай соз­дадим интерфейс будуще­го при­ложе­ния. Для это­го вос­поль­зуем­ся фрей­мвор­ком для раз­работ­ки кросс‑плат­формен­ного ПО — Qt 5. Пот­ребу­ется ска­чать его и уста­новить.

От­кро­ем Qt Designer и в появив­шемся окне выбора пос­тавим галоч­ку на пун­кте Main Window.

Почтовый клиент на Python и Qt. Создание нового окна

Почтовый клиент на Python и Qt. Создание нового окна.

За­тем соз­дадим окно при помощи готовых инс­тру­мен­тов, а имен­но Label для тек­ста, Line Edit для строк вво­да, Text Edit для поля вво­да тек­ста, а так­же Push Button для кно­пок. Раз­ложить всё можешь так, как тебе пон­равит­ся, глав­ное — пос­тавить все эле­мен­ты, показан­ные на кар­тинке, и запом­нить их номера. Даль­ше будет понят­но, зачем это.

Почтовый клиент на Python и Qt. Ин­терфейс кли­ента

Почтовый клиент на Python и Qt. Ин­терфейс кли­ента

Не забудь в поле вво­да пароля пос­тавить параметр
echoMode = Password, что­бы скрыть пароль, а так­же вык­лючить поля, которые могут быть активны толь­ко пос­ле пра­виль­ного вво­да дан­ных. Что­бы это сде­лать, необ­ходимо в поле
enabled убрать галоч­ку.

Пос­ле завер­шения работы мое окно выг­лядит как на скрин­шоте.

Почтовый клиент на Qt и Python

Сох­раняй документ, и получишь файл с рас­ширени­ем
ui, который и будет фай­лом интерфей­са. Я наз­вал его
Main.ui. Теперь к коду!

Пер­вым делом импорти­руем две биб­лиоте­ки. Они нуж­ны для работы с Qt 5.

from PyQt5.QtWidgets import *

from PyQt5 import uic

Те­перь про­писы­ваем класс интерфей­са и в нем все фун­кции. У нас будет фун­кция вво­да (
login), фун­кция прик­репле­ния фай­ла (
attach_sth) и фун­кция отправ­ки сооб­щений (
send_mail). Так­же в осно­ве основ лежит фун­кция вызова гра­фичес­кого интерфей­са с демонс­тра­цией окна и акти­ваци­ей трех кно­пок.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

class MyGUI(QMainWindow):

    def __init__(self):

        super(MyGUI, self).__init__()

        uic.loadUi(‘Main.ui’, self)

        self.show()

        self.pushButton.clicked.connect(self.login)

        self.pushButton_2.clicked.connect(self.attach_sth)

        self.pushButton_3.clicked.connect(self.send_mail)

    def login(self):

        pass

    def attach_sth(self):

        pass

    def send_mail(self):

        pass

app = QApplication([])

windows = MyGUI()

app.exec_()

Здесь инте­рес­на фун­кция
app.exec_(). Она оста­нав­лива­ет работу с тер­миналом, что­бы все вза­имо­дей­ствие с при­ложе­нием шло толь­ко через гра­фичес­кий интерфейс.

Код уже мож­но запус­тить! Ты уви­дишь окно с нарисо­ван­ным тобой интерфей­сом, но в нем пока что ничего не будет работать. Конеч­но, мы же еще не опре­дели­ли, как это дол­жно про­исхо­дить.

Создание основной части почтовика

Итак, задача — написать дви­жок. Прис­тупим.

Фун­кция вхо­да. В ней мы дела­ем две вещи: логиним­ся и соз­даем перемен­ную
msg (перемен­ная с содер­жани­ем пись­ма).

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

def login(self):

        try:

            self.server = smtplib.SMTP(self.lineEdit_3.text(),

            # Обращаемся к протоколу SMTP по нужному порту

            self.lineEdit_4.text())

            # Отправляем запрос ELHO

            self.server.ehlo()

            # TLS-соединение

            self.server.starttls()

            # Снова отправляем запрос ELHO

            self.server.ehlo()

            self.server.login(self.lineEdit.text(),

            # Ввод логина и пароля

            self.lineEdit_2.text())

            # Переводим все поля входа в выключенное состояние

            self.lineEdit.setEnabled(False)

            self.lineEdit_2.setEnabled(False)

            self.lineEdit_3.setEnabled(False)

            self.lineEdit_4.setEnabled(False)

            self.pushButton.setEnabled(False)

            # Переводим все поля письма во включенное состояние

            self.lineEdit_5.setEnabled(True)

            self.lineEdit_6.setEnabled(True)

            self.textEdit.setEnabled(True)

            self.pushButton_2.setEnabled(True)

            self.pushButton_3.setEnabled(True)

            # Создание переменной msg

            self.msg = MIMEMultipart()

        # При ошибке в логине и/или пароле

        except smtplib.SMTPAuthenticationError:

            # Создать окно

            massage_box = QMessageBox()

            # Окно заполнить текстом

            massage_box.setText(‘Недопустимые данные!’)

            # Переключиться на окно

            massage_box.exec()

        except:

            massage_box = QMessageBox()

            massage_box.setText(‘Не удалось выполнить вход!’)

            massage_box.exec()

EHLO — это гла­гол рас­ширен­ного про­токо­ла переда­чи прос­тых сооб­щений (ESMTP), опре­делен­ный в RFC 5321. Сер­веры ESMTP могут объ­являть о сво­их воз­можнос­тях во вре­мя началь­ного под­клю­чения. Сре­ди них — мак­сималь­ный допус­тимый раз­мер сооб­щения и под­держи­ваемые методы про­вер­ки под­линнос­ти. HELO — это более ста­рая коман­да SMTP, опре­делен­ная в RFC 821. Боль­шинс­тво SMTP-сер­веров под­держи­вают ESMTP и EHLO. Если сер­вер не под­держи­вает EHLO, вмес­то него мож­но исполь­зовать HELO.

Да­лее идет фун­кция прик­репле­ния вло­жений:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

def attach_sth(self):

        # Открываем диалоговое окно выбора файлов

        options = QFileDialog.Options()

        # Смотрим все типы файлов

        filenames, _ = QFileDialog.getOpenFileNames(self, ‘Open

        File’, », ‘All Files (*.*)’, options=options)

        # Собираем файлы

        if filenames != []:

            for filename in filenames:

                attachment = open(filename, ‘rb’)

                filename = filename[filename.rfind(‘/’) + 1:]

                # Кодируем файл по стандарту MIME

                p = MIMEBase(‘application’, ‘octet-stream’)

                p.set_payload(attachment.read())

                encoders.encode_base64(p)

                # Пишем имя файла

                p.add_header(‘Content-Disposition’, f‘attachment; filename={filename}’)

                self.msg.attach(p)

                # Ставим запятые после первого вложения

                if not self.label_8.text().endswith(‘: ‘):

                    self.label_8.setText(self.label_8.text() + ‘,’)

                self.label_8.setText(self.label_8.text() + ‘ ‘ + filename)

Соз­дание той самой перемен­ной
msg:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

def send_mail(self):

        # Создаем окно с уточнением

        dialog = QMessageBox()

        dialog.setText(‘Вы хотите отправить сообщение?’)

        # Выбор ответов

        dialog.addButton(QPushButton(‘Да’), QMessageBox.YesRole)

        dialog.addButton(QPushButton(‘Нет’), QMessageBox.NoRole)

        if dialog.exec_() == 0:

            try:

                # От кого сообщение

                self.msg[‘From’] = ‘Xakep’

                # Кому

                self.msg[‘To’] = self.lineEdit_6.text()

                # Тема

                self.msg[‘Subject’] = self.lineEdit_5.text()

                # Собираем сообщение

                self.msg.attach(MIMEText(self.textEdit.toPlainText(), ‘plain’))

                text = self.msg.as_string()

                # Отправляем по пути

                self.server.sendmail(self.lineEdit.text(),

                self.lineEdit_6.text(), text)

                # Создаем окно с подтверждением

                massage_box = QMessageBox()

                massage_box.setText(‘Сообщение отправлено!’)

                massage_box.exec()

            except:

                # Создаем окно с ошибкой

                massage_box = QMessageBox()

                massage_box.setText(‘Не удалось отправить сообщение!’)

                massage_box.exec()

Весь код можешь пос­мотреть на GitHub.

Настройки почты

Я выб­рал поч­ту Gmail, пос­коль­ку в нем мож­но вык­лючить защиту от сто­рон­них при­ложе­ний. Мно­гие дру­гие поч­товики ста­вят зап­рет на нее, а здесь дос­таточ­но перей­ти по ссыл­ке и выс­тавить раз­решение.

Вклю­чение отправ­ки с нашего почтового кли­ента

Вклю­чение отправ­ки с нашего почтового кли­ента

Ад­рес SMTP для Gmail —
smtp.gmail.com, порт — 587. Для дру­гих поч­товых кли­ентов мож­но пос­мотреть эти дан­ные, прос­то загуг­лив их наз­вание со сло­вом SMTP.

Заключение

Вот мы и написа­ли свой базовый кли­ент для отправ­ки писем с любыми вло­жени­ями. Как видишь, все, что нуж­но, — это нарисо­вать фор­му и написать обра­бот­чики, про­цесс очень прос­той и пря­моли­ней­ный.

На вся­кий слу­чай пре­дуп­реждаю: можешь даже не пытать­ся исполь­зовать этот код без изме­нений в незакон­ных целях. Мы здесь никак не скры­ваем свой IP, никак не шиф­руем фай­лы и не обхо­дим анти­виру­сы.

РЕКОМЕНДУЕМ:
Система распознавания лиц на Python

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

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (4 оценок, среднее: 4,75 из 5)

Загрузка…

Проектируем удобный почтовый клиент

Время на прочтение
6 мин

Количество просмотров 28K

image
Недавно, просматривая старые статьи Хабра, наткнулся на опрос о почтовых клиентах, который показал, что подавляющее большинство читателей предпочитает Gmail. Я сам пользуюсь им много лет, хотя пару раз в год безрезультатно пытаюсь “пересесть” на что-то поудобнее. С тех пор я стал думать о том, как можно было бы сделать Gmail лучше. Идей накопилось столько, что я решил создать собственный почтовый клиент. Любителей почитать про интерфейсы прошу под кат. И да поможет мне Фиттс.

Низкий старт. Портрет пользователя и позиционирование

В первую очередь стоит задуматься о позиционировании продукта. Обычно этим вопросом пренебрегают при проектировании интерфейсов и переходят сразу к дизайну. В итоге получается продукт для всех и ни для кого.

Я составил портрет своего целевого пользователя. Получился вот такой полностью вымышленный человек, под которого и будет создаваться продукт:
image

Интуиция мне подсказывает, что такой почтовый клиент лучше приживется сначала на Западе, а уже потом у нас. Поэтому я решил все материалы сразу делать на английском.

Начало

Конкурентов нужно знать в лицо. Я протестировал десяток веб-клиентов из опроса и сделал выводы, которые учел при дальнейшем проектировании. Когда я создавал прототип, прежде всего руководствовался не принципом “чтобы было не похоже на…”, а своими логическими соображениями, законами Фиттса и Хика, работами Раскина, Купера и других авторитетных проектировщиков.

Сразу запустил небольшой опрос по друзьям в соц.сети:
image

Результат весьма ожидаемый: люди хотят, чтобы было быстро, просто и красиво. Голосовала не совсем целевая аудитория, зато результат получен быстро и бесплатно: пока этого достаточно.

Самое главное: интерактивный прототип

Некоторые бросаются сразу делать дизайн. Честно говоря, я совершенно не понимаю, как можно отлаживать “механику” взаимодействия на графических макетах. В общем, начинаем с интерактивного html-прототипа в Axure, в котором можно было бы прямо по ходу проектирования тестировать и переделывать различные элементы взаимодействия.

Далее приведу наиболее интересные скриншоты с пояснениями. Если любопытно пощупать руками, в конце статьи вы найдете ссылку на прототип.

Итак, авторизуемся под аккаунтом Gmail, разрешаем приложению использовать данные, попадаем во входящие
image

Своему почтовому клиенту я дал временное рабочее название UXMail и буду использовать его только в рамках этой статьи.

На всякий случай уточню, что это лишь функциональный прототип, а значит все цвета, шрифты, иконки и формы — лишь условные, внимания на стиль обращать не стоит: все будет нормально “отрисовано” позже профессиональным дизайнером.

Навигация и письма

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

Я уверен, что этот временный недостаток с лихвой окупится при постоянном использовании.

И тут не обошлось без GTD и геймификации

Я уважаю принципы методологии Getting Things Done о “пустом инбоксе”, поэтому в интерфейсе старался всячески мотивировать не откладывать обработку писем, и как можно скорее очистить папку с входящими. Например, когда последнее письмо отправляется в архив, UXMail хвалит пользователя
image

Предполагается ротировать надписи и картинки, чтобы была мотивация узнать, как еще тебя похвалит почтовый клиент. Этакий элемент нынче модной геймификации.

При наведении на письмо во входящих появляется кнопка добавления этого письма в архив. Пользователю достаточно будет подвинуть курсор на несколько пикселей и кликнуть по иконке:
image

После чего письмо с анимацией “улетает” в иконку архива в меню.

Закон Фиттса как всегда безупречен

Для сравнения, то же самое действие — добавление в архив — в Gmail. Пользователь вынужден каждый раз проделывать пять(!) операций вместо двух:
image

  1. Подвести курсор к флагу
  2. Установить флаг
  3. Подвести курсор к кнопке “в архив”
  4. Кликнуть
  5. Перевести курсор на следующее письмо

Отвечаем на письмо

В интерфейсе входящего письма ничего концептуально нового нет. При клике поле ввода “разъезжается” до состояния, в котором удобно написать ответ
image

Менее популярные элементы интерфейса скрыты. Например, копия при ответе. Панель форматирования сильно приглушена цветом, но при наведении и клике инструменты “подсвечиваются”:
image

Отмена и подтверждение действий

К модальным подтверждениям типа “Вы уверены, что хотите …?” пользователи быстро привыкают и машинально нажимают “да” даже не вчитываясь. Поэтому лучше делать всплывающие уведомления: в таком случае у пользователя будет 3-4 секунды, чтобы передумать и отменить свое решение
image

Цепочки писем и микроцепочки

Некоторым это очень нравится, некоторым очень не нравится. Кажется, равнодушных уже почти не осталось. В комментариях к одной статье на Хабре цепочки писем даже сравнивали со свежей кинзой.

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

  1. Не понятно, сколько новых писем появилось в цепочке. Просто появился один “непрочитанный” элемент — цепочка по теме
  2. Открывая цепочку, все письма помечаются как “прочитанные”. В результате можно пропустить новое письмо
  3. Непонятно, к какому письму цепочки будут применены общие действия (ответить, переслать и другие). Хотя в Gmail это уже частично решено, чего не скажешь о некоторых других клиентах

У цепочек есть и одно существенное преимущество: они позволяют проследить всю линию общения и вспомнить какую-то важную информацию. Признаться, я сначала тоже собирался делать цепочки, “приглушив” перечисленные недостатки рядом интерфейсных “костылей”, но при этом UXMail терял в простоте и минималистичности.

В итоге я решил ввести новое понятие — микроцепочки. Фактически, микроцепочка — это входящее сообщение и ответ пользователя на это сообщение. Такой механизм позволяет сохранить простоту и прямолинейность интерфейса без цепочек и получить определенные преимущества интерфейса с цепочками хотя бы в рамках одной итерации “входящее письмо — ответ на него”. В подавляющем большинстве случаев этого достаточно, чтобы восстановить в голове смысл переписки
image

Проверяйте спам чаще

Папку со спамом я сознательно “вытащил” на верхний уровень. Я считаю, что пользователям стоит хотя бы изредка проверять спам на предмет важных писем, потому что спам-фильтры пока не 100% корректно работают. Соответственно, предполагается такое взаимодействие: пользователь пробегает глазами список писем в спаме, с помощью быстрой кнопки отправляет некоторые письма обратно во входящие, а в конце списка его ждет большая кнопка “Удалить весь спам безвозвратно” — и далее будет опять хвалебная надпись и картинка
image

Метки и фильтры: их-то за что?

А вот фанатам меток и фильтров UXMail скорее всего не понравится: фильтры придется настраивать через интерфейс Gmail, а меток вообще нет — остались только папки
image

Я вижу следующие недостатки использования папок совместно с фильтрами:

  1. Нарушается принцип “одного пустого инбокса”. Т.е. чтобы продуктивно обрабатывать почту нужно следить за тем, чтобы сразу несколько папок были пустыми или хотя бы просто обработанными
  2. Увеличивается вероятность пропустить важное письмо: человек — не робот: может банально забыть о какой-то папке и не проверить ее
  3. Если посадить два человека рядом и слать им одни и те же письма, то, рискну предположить, обрабатывать поток писем быстрее будет тот, кто получает все в одну папку, т.к. он не тратит время на переключение между папками

По поводу меток я в сомнении: пока решил их вообще не поддерживать. Метки — это когда все письма остаются в общем “инбоксе”, просто помечаются словами “работа”, “учеба” и так далее. Я как-то пользовался метками, потом перестал, т.к. обратил внимание, что они улучшают продуктивность лишь ненадолго. Потом приходится систематически тратить время на “ревью” созданных меток и добавление новых. Это при том что в 9 из 10 случаев и так понятно, какое письмо по учебе, а какое — по работе.

Управление вложениями

Еще одна фича, которая, на мой взгляд, может пользоваться хорошим спросом — централизованное управление вложениями. Сейчас в почтовых клиентах достаточно трудно искать полученные файлы, особенно, если не помнишь ни имени отправителя, ни даты. В UXMail можно пролистать на одном экране все вложения и отфильтровать их по дате, отправителю или теме:
image

Все-все, уже закругляемся. Настройки

Я изучил интерфейсы настроек нескольких популярных почтовых клиентов, подумал и убрал все лишнее. Осталось только 4 раздела: почтовые ящики, подписи, автоответчик и уведомления
image

Каждая настройка в обязательном порядке сопровождается пояснительным текстом и иллюстрацией того как это будет выглядеть в интерфейсе. Некоторые настройки продублированы в “повседневных” интерфейсах. Например, можно добавить еще один адрес отправителя прямо из контекста создания нового письма:
image

А когда пользователь включает автоответчик, в верхней панели статично закрепляется сообщение о том, что включен автоответчик
image

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

Пожалуй, на этом все. В интерактивном прототипе вы найдете еще много занятных механизмов, с которыми можно “поиграться”, но описывать все в статье было бы совсем объемно и скучно (боюсь, с объемом я и так немного перестарался).

Какие планы?

Собрать отзывы с Хабра, доработать прототип, сделать графический дизайн и промо-материалы, привлечь инвестиции на разработку через краудфандинговые платформы.

И зачем я это все написал?

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

Передача данных

Формат обмена данными

Класс POPClient

Внутренний класс POPCommand

Внутренний класс POPResponse

Класс POPException

Команды протокола РОРЗ
Листинг 1
Листинг 2

Написание программ на Java представляет собой весьма интересное


Передача данных
Формат обмена данными
Класс POPClient
    Внутренний класс POPCommand
    Внутренний класс POPResponse
Класс POPException

Команды протокола РОРЗ

Листинг 1
Листинг 2


Написание программ на Java представляет собой весьма интересное занятие, поскольку можно абстрагироваться от многих деталей, с которыми неизбежно сталкиваются пользователи Cи++, Паскаля и других языков программирования третьего поколения. С самого начала специалисты корпорации Sun задумали Java как язык, стирающий разницу между разрозненными компьютерами и сетями, и, видимо, поэтому значительная часть библиотеки классов Java абстрагирует сетевые операции и транспортные протоколы.

Простейшая Java-программа, принимающая почту с сервера, — это по силам каждому.

Передача данных

Прежде чем написать почтовый клиент, неплохо было бы изучить язык, на котором общаются почтовый клиент и сервер, — протокол POP3 (Post Office Protocol). Именно с помощью этого протокола в большинстве случаев принимаются почтовые сообщения такими популярными почтовыми программами, как Microsoft Outlook и Express Netscape Messanger. Для начала примите к сведению, что все почтовые операции на нижнем уровне производятся с применением транспортного протокола TCP. Сокет сервера передает данные в формате POP3 TCP-пакетами в порт 110 (более ранняя вторая версия протокола POP использовала порт 109).

Кроме того, необходимо знать, что команды от клиента к серверу пересылаются в виде текстовых строк, завершающихся парой символов «перевод каретки» (CR) и «перевод строки» (LF). Получив такое сообщение, почтовый сервер приступает к тяжелой работе: либо отвечает на ваши команды, либо пересылает сообщения, имеющиеся в вашем почтовом ящике. Вот здесь начинается настоящее мучение, ибо те, кто разрабатывал протокол POP3, променяли удобство использования на простоту реализации, не подумав о программистах, которым с ним работать.

Формат обмена данными

Команды, которые вы отправляете серверу, делятся на две категории: без параметров и содержащие параметры. Если простые команды без параметров состоят всего из одного слова, то в команды с параметрами добавляются текстовые параметры (или цифровые параметры, преобразованные в текстовую форму и разделенные пробелами). Каждый параметр может иметь длину до 40 символов. В качестве примера простой команды можно привести команду «STAT», которая запрашивает состояние вашего почтового ящика. Примером команды с параметром может служить команда «USER Mitrich», сообщающая серверу, что к нему подключается пользователь с именем Mitrich. Прежде чем отправиться дальше, загляните в таблицу и ознакомьтесь со всеми командами, которые описывает протокол POP3.

Возвращаемые сообщения могут быть двух видов: одно- и многострочные. В первом случае сервер возвращает одну строку; она начинается с признака статуса +OK или -ERR, говорящих об успехе или неудаче выполнения последней команды. Следом за признаком статуса сервер передает полезную информацию различного назначения. Завершается строка символами ‘
‘ и ‘
‘. Если же возвращаемое сообщение многострочное, то оно передается строка за строкой, каждая из которых завершается ‘
‘ и ‘
‘, а в качестве признака окончания передачи используется строка из единственного символа ‘.’ (точка), также оканчивающаяся символами возврата каретки и перевода строки.

Класс POPClient

Ядро почтового клиента — это класс POPClient, реализованный на языке Java (см. листинг 1). Он предоставляет пользователю несколько полезных функций для управления посылкой команд серверу и получения ответа от последнего. Класс POPClient вмещает в себя два внутренних класса — POPCommand и POPResponse. Такого разбиения единого, казалось бы, класса на подклассы требует сама суть объектно-ориентированного программирования: каждая сущность должна быть реализована как отдельный класс. А поскольку и POPCommand и POPResponse являются вспомогательными по отношению к главному классу POPClient, то они выполнены не как независимые классы, а как классы внутренние. Средства пакета JDK 1.1 предусматривают решение подобных задач.

Внутренний класс POPCommand

Класс POPCommand, как вы, наверное, уже догадались, является инкапсуляцией команды протокола POP3. Для его создания нет конструктора, поэтому этим занимается компилятор Java. С помощью объектов этого класса формируются команды для посылки серверу и производится собственно посылка команды. Кроме того, обязанностью этого класса является чтение ответа сервера на посланную ранее команду. Самую черную работу на нижнем уровне выполняют следующие методы:

  • public boolean user(String name);
  • public boolean pass(String password);
  • public boolean quit();
  • public boolean dele(int number);
  • public boolean rset();
  • public boolean list(int number);
  • public boolean retr(int number).
  • Нетрудно догадаться, что это всего лишь оболочки для посылки команд с аналогичными именами. Внутри этих методов происходит стандартная обработка: имеющийся аргумент проверяется, передается вспомогательному методу transactCommand() и затем возвращается в виде true (команда прошла и обработана сервером) или false (произошла ошибка). Когда же сервер прислал вместо вразумительного ответа «тарабарщину», происходит возбуждение исключения под названием POPException (о нем ниже в разделе «Класс POPException»).

    Метод transactCommand принимает текстовую строку, которая соответствует посылаемой команде, передает ее методу sendCommand(), затем читает ответ сервера с помощью метода readCommandResponse(). В заключение вызывается метод isSucceed() внутреннего класса POPResponse. Этот метод возвращает результат последней транзакции.

    Если работа sendCommand() достаточно понятна, то работа метода readCommandResponse() требует некоторого пояснения. В начале своей работы этот метод создает объект класса StringBuffer для временного хранения данных. Потом из потока ввода, подключенного к сокету, командой ir.readLine() читается строка ответа, которая добавляется к буферу строки методом append(). В момент окончания работы метода значение буфера преобразуется в строку методом toString() и записывается во внутреннее хранилище класса POPResponse методом setBuff().

    Но не все так просто, как может показаться с самого начала. Дело в том, что читать данные из потока сокета можно, а вот установить момент, когда они считаны до конца, нельзя. И документация в этом деле, увы, ничем не поможет. Там рекомендовано читать строки из сокета до тех пор, пока возвращаемое значение не будет равно null. Но уверяю вас, этого момента вы не дождетесь. Точно так же вы не сможете воспользоваться методом read() для чтения из-за того, что и он блокируется при достижении конца передачи данных. Со стороны это выглядит как зависание, но на самом деле поток, в котором вызывается метод чтения данных, просто переходит в состояние ожидания очередной порции информации. Поэтому мы идем на компромисс и в дополнение к методу readCommandResponse() реализуем метод readMessage(), который предназначается лишь для считывания многострочного почтового сообщения. Конец передачи данных мы определяем по строке, состоящей из одного символа точки «.». Метод readMessage() вызывается из командного метода retr() и, вместо того чтобы, как другие команды, сделать вызов метода transactCommand(), выполняет следующие три строки на Java:

    sendCommand("RETR " + Integer.toString(number));
    readMessage();
    return response.isSucceed();

    Обратите внимание, что многие методы класса POPCommand объявлены как private, чтобы предотвратить прямой доступ к ним.

    Внутренний класс POPResponse

    Следующий внутренний класс POPResponse отвечает за хранение полученных от сервера данных и предоставление удобного интерфейса для их обработки. Все данные, принятые от сервера, сохраняются методом setBuff() в переменной buff, которая есть не что иное, как ссылка на строку класса String. Для вспомогательных нужд есть метод getBuff, с помощью которого можно получить содержимое хранилища в том виде, в каком оно было получено от почтового сервера.

    Для предварительной обработки данных вы можете вызывать еще два полезных метода: cutOffStatus() и getServerComment(). Первый из них берет содержимое временного хранилища, отрезает от него признак статуса (+OK или -ERR) и возвращает оставшуюся строку. Второй метод возвращает текстовое сообщение, которое было передано POP-сервером сразу за признаком статуса. Следует, однако, отметить, что применять этот метод можно лишь после того, как серверу была передана команда, следом за которой сервер непременно пришлет сообщение (см. таблицу), иначе возвращенная методом getServerComment() строка будет бессмысленна.

    Настал черед главного класса POPClient, с которым нам нужно разобраться. Сразу после объявления его самого описывается странная на первый взгляд переменная debug. Значение этой переменной управляет включением и выключением вывода отладочных сообщений в поток System.out. Если установить значение true, то специально написанный метод logText() будет посылать любую текстовую строку, которую вы ему передадите в окно консоли. После окончания отладки класса, вы можете установить debug в состояние false, и ваши строки, передаваемые logText(), уже не будут выводиться. Мало того, компилятор автоматически уберет посылки текста в окно консоли, оптимизируя код. Данная возможность походит на директивы условной компиляции препроцессора языков Cи и Cи++. О другой функции метода logText() мы поговорим чуть позже.

    В начале класса POPClient объявляется несколько полей, имеющих следующее назначение:

  • POPCommand command — ссылка на объект внутреннего класса POPCommand;
  • POPResponse response — ссылка на объект внутреннего класса POPResponse;
  • Socket socket — ссылка на сокет, связанный с почтовым сервером;
  • BufferedReader ir — объект, читающий данные из потока ввода сокета;
  • PrintWriter ow — объект, записывающий данные в поток вывода сокета;
  • PrintWriter log — объект, записывающий данные в файл протокола;
  • Vector messages — хранилище полученных почтовых сообщений;
  • boolean logEnabled — флаг, разрешающий или запрещающий вывод текста
    в файл протокола.
  • В конструкторе по умолчанию производится создание новых экземпляров объектов внутренних классов POPCommand и POPResponse для дальнейшей работы. Если вам требуется протоколировать работу вашего почтового клиента, то воспользуйтесь другим конструктором, принимающим имя файла, который будет служить файлом протокола. Сначала этот конструктор вызовет конструктор по умолчанию, инициализируя таким образом внутренние классы, а затем создаст поток вывода данных в файл. Если таковой поток не удается создать, то возникает исключение ввода-вывода, которое мы перехватываем, и устанавливаем флаг разрешения протокола в положение «отключено» (false).

    Для забывчивых программистов в классе предусмотрен финализатор — метод finalize(), вызываемый сборщиком мусора в момент завершения работы класса. Финализатор вызывает метод disconnect(), отключающий клиента от почтового сервера, если программист по каким-либо причинам не вызывал метод disconnect().

    Для соединения с POP-сервером создаваемый нами класс оснащен методом connect(), который достаточно прост. На первом этапе connect() создает сокет для присоединения к серверу с заданным именем и портом. Если сервер с именем hostName не существует, то после некоторого ожидания будет возбуждено исключение UnknownHostException. То же произойдет при возникновении проблем с вводом-выводом данных — будет сгенерировано исключение IOException. Мы перехватываем эти исключения и генерируем свое — POPException. В качестве параметра конструктора задается константа, определяющая вид исключения, которое мы хотим возбудить.

    После создания сокета, мы вызываем его методы getInputStream()и getOutputStream(), чтобы получить ссылки на потоки ввода и вывода сокета, через которые мы будем посылать и получать данные. Для удобства работы полученные ссылки преобразуются в экземпляры объектов классов BufferedReader и PrintWriter, пришедших на смену морально устаревшим классам потоков BufferedInputStream и PrintStream. Остается считать ответ сервера:

    command.readCommandResponse();

    и определить, произошло ли соединение:

    return response.isSucceed();

    Для отключения от сервера существует метод disconnect(). Он работает довольно прямолинейно: посылает серверу команду QUIT и поочередно закрывает потоки протокола сокетов, а затем и сам сокет.

    Метод logText(), который косвенно упоминался выше, служит для выполнения двух функций: запись строки, полученной через параметр text, в файл протокола, если протоколирование разрешено. Эти же данные пересылаются в окно консоли в том случае, если флаг debug задан как true.

    Для получения доступа к почте необходимо вызвать метод login(), посылающий имя пользователя и пароль почтовому серверу. Если какой-либо из параметров неверен, то происходит генерация исключения POPException.

    Еще два метода deleteMessage() и undoDeletes() служат для удаления и отмены удаления сообщений. Если вы хотите удалить сообщение, то вызываете deleteMessage(), передавая номер почтового сообщения, которое подлежит удалению. Учтите, что нумерация начинается с 1, как это принято у почтового сервера. Само сообщение при этом лишь помечается на удаление и в дальнейших транзакциях не участвует. Реально оно будет удалено в момент отключения клиента от сервера. Если же вы поняли, что совершили ошибку, то можете просто вызвать undoDeletes(), после чего пометка на удаление будет снята, и вы сможете читать это сообщение. Правда, придется заново загрузить все почтовые сообщения на локальный компьютер.

    Класс POPException

    Следуя концепции объектно-ориентированного программирования, просто необходимо создать свой собственный класс обработки исключительной ситуации — POPException. Он не только расширяет стандартный класс исключения Exception, но и добавляет специальное расширение — поле why, хранящее в виде числа причину возникновения исключения. Для удобства в классе определяются несколько констант, дающих этому числу осмысленное название. Если вы, создавая объект класса POPException, зададите в его конструкторе причину сбоя, то внутри класса вспомогательный метод assignMessage() подберет соответствующее текстовое сообщение. Перехватив исключительную ситуацию класса POPException, можно получить или разумное текстовое описание методом getMessage(), или же код причины методом why().

    Завершая описание, советуем в дополнение к протоколу POP3 ознакомиться со следующими рабочими документами:

    RFC821 — Simple Mail Transfer Protocol;
    RFC1321 — The MD5 Message-Digest Algorithm.


    Команды протокола РОРЗ

    Успех и неудача выполнения команды отмечаются признаками статуса +ОК или -ERR соответственно

    Команда

    Назначение

    Возможные возвращаемые значения

    USER <имя пользователя> Посылка имени пользователя серверу +ОК <комментарий сервера> — если имя пользователя
    правильное
    -ERR <комментарий сервера> — если имя пользователя
    неверное
    PASS <пароль> Посылка пароля серверу +ОК<комментарий сервера> — если пароль принят сервером
    -ERR
    <комментарий сервера> — если пароль неверный или с почтовым ящиком
    уже кто-то работает
    QUIT Окончание сеанса работы +ОК
    STAT Получить состояние почтового ящика +ОК <кол-во сообщений> <общий размер всех сообщений>
    UST [<номер сообщения>] Получить параметры всех сообщений в ящике пользователя.
    Если задан номер сообщения, то будут получены только его параметры
    +ОК <параметры сообщений>
    -ERR <комментарий
    сервера> — если запрошенного сообщения в ящике нет.
    Возвращаемые
    параметры сообщений зависят от того, был ли задан номер сообщения. Если
    — да, то сразу после +ОК следует сообщение сервера. Затем строка за строкой
    передаются параметры всех сообщений в формате <номер сообщения> <размер
    сообщения>
    RETR <номер сообщения> Получить сообщение с сервера +ОК <тест запрошенного сообщения> — если команда
    прошла удачно
    -ERR <комментарий сервера> — если запрошенное
    сообщение отсутствует на сервере
    DELE <номер сообщения> Пометить сообщение на сервере как удаленное. Реально
    оно будет удалено после команды QUIT
    +ОК <комментарий сервера> — если сообщение было
    помечено на удаление
    -ERR <комментарий сервера> — если сообщение
    не существует или уже отмечено как удаленное
    NOOP Пустая операция +ОК
    RSET Отменить удаление удаление сообщений, помеченных как
    удаленные
    +ОК <комментарий сервера>
    ТОР <номер сообщения> <кол-во строк> Считать заголовок сообщения и первые строки в количестве,
    заданном параметром <кол-во строк>
    +ОК
    Далее строка за строкой передается заголовок
    сообщения. За ним следует пустая строка и, если имеется второй параметр,
    передаются начальные строки сообщения
    UIDL [<номер сообщения>] Получить уникальные идентификаторы всех сообщений в ящике
    пользователя. Если задан номер сообщения, то будет получен только его идентификатор
    +ОК <параметры сообщений>
    -ERR <комментарий
    сервера> — если запрошенного сообщения в ящике нет.
    Возвращаемые
    параметры сообщений зависят от того, был ли задан номер сообщения. Если
    — да, то сразу после +ОК идут номер запрошенного сообщения и его идентификатор.
    Если команда вызвана без параметра, то после статуса +ОК следует сообщение
    сервера. Затем строка за строкой передаются параметры всех сообщений в
    формате <номер сообщения> <идентификатор>
    APOP <имя пользователя> <дайджест> Осуществляет подключение к почтовому серверу по закодированной
    алгоритмом MD5 строке, защищая транзакцию от разглашения пароля пользователя
    +ОК <комментарий сервера> — если имя пользователя
    или дайджест соответствуют имеющемуся почтовому ящику пользователя
    -ERR
    <комментарий сервера> — если имя пользователя или дайджест неверны

    Листинг 1

    //  Базовый класс для приема сообщений от
    //  почтового сервера (с использованием
    //  протокола POP3).
    
    // Формируем пакет
    package Mitrich.mail;
    
    // Импортируем нужные классы
    import java.io.*;
    import java.net.*;
    import java.util.Enumeration;
    import java.util.Vector;
    import java.util.StringTokenizer;
    // Вспомогательный класс. См. листинг 2
    import Mitrich.mail.POPException;
    
    // Главный класс реализации протокола POP3
    public class POPClient
    {
      // После окончания отладки установить false
      private final static boolean debug = true;
    
      // Номер порта POP3
      public final static short POP3_PORT = 110;
    
      // Константы класса
      private final String EOL = "
    ";
      private final String S_OK = "+OK";
      private final String S_ERR = "-ERR";
    
      // Поля класса
      private POPCommand command = null;
      private POPResponse response = null;
      private Socket socket = null;
      private BufferedReader ir = null;
      private PrintWriter ow = null;
      private PrintWriter log = null;
      private Vector messages = null;
      private boolean logEnabled = false;
    
    
      // Конструктор по умолчанию
      public POPClient()
      {
        // Создаем экземпляр класса команды
        command = this.new POPCommand();
        // Создаем экземпляр класса ответа сервера
        response = this.new POPResponse();
      }
    
      // Этот конструктор создает файл протокола
      // с именем, заданным параметром logName
      public POPClient(String logName)
      {
        this();
        logEnabled = true;
        try
        {
          // Создадим поток вывода в файл протокола
          // с автоматическим сбросом буферов на диск
          log = new  PrintWriter(
                   new FileOutputStream(logName, true),
                      true);
        }
        catch(IOException e)
        {
          // Возникла проблема с созданием файла протокола
          // Протоколирование отключается
          logEnabled = false;
        }
      }
    
      // Финализатор
      protected void finalize() throws Throwable
      {
        // Отсоединиться от почтового сервера, если
        // пользователь забыл вызвать метод disconnect()
        disconnect();
      }
    
      // Установить соединение с почтовым сервером
      public boolean connect(String hostName, int portNumber)
             throws POPException
      {
        try
        {
          logText("Creating a socket...");
          // Создаем сокет
          socket = new Socket(hostName, portNumber);
    
          logText("Creating an input stream...");
          // Получаем ссылку на поток ввода данных от сокета
          ir = new BufferedReader(
                new InputStreamReader(
                   socket.getInputStream()));
    
          logText("Creating an output stream...");
          // Получаем ссылку на поток вывода данных в сокет
          ow = new PrintWriter(
                new DataOutputStream(
                    socket.getOutputStream()), true);
    
          // Слушаем ответ сервера
          command.readCommandResponse();
          // Ответ +OK или -ERR ?
          return response.isSucceed();
        }
        catch(UnknownHostException e)
        {
          logText("Host unknown");
          // Заданный адрес сервера недействителен
          throw new POPException(POPException.HOST_UNKNOWN);
        }
        catch(IOException e)
        {
          logText("Creating an I/O channel failed");
          // Ошибка сокета. Возможно, сервер отключился
          throw new POPException(POPException.SOCKET_ERROR);
        }
      }
    
      // Отключаемся от сервера
      public void disconnect()
      {
        try
        {
          logText("Disconnecting... ");
          // Посылается запрос на отключение
          command.quit();
          if(ir != null)
          {
            ir.close();
            ir = null;
          }
          if(ow != null)
          {
            ow.close();
            ow = null;
          }
          if(socket != null)
          {
            socket.close();
            socket = null;
          }
          if(log != null)
          {
            log.close();
            log = null;
          }
        }
        catch(Exception e)
        { logText("Disconnection failed");}
      }
    
      // Записать строку в файл протокола
      public void logText(String text)
      {
        if(logEnabled) log.println(text);
        if(debug) System.out.println(text);
      }
    
      // Входим в почтовый сервер
      public void login(String name, String password)
                               throws POPException
      {
        logText("Sending the user name...");
        // Передаем имя пользователя серверу
        if( !command.user(name) )
            throw new POPException(POPException.BAD_NAME);
        logText("Sending the password...");
        // Если пароль есть, то передаем его серверу
        if(password != null)
          if( !command.pass(password) )
            throw new POPException(POPException.BAD_PASSWORD);
      }
    
      // Загрузить всю почту на компьютер пользователя
      public void downloadMessages()
                               throws POPException
      {
        StringTokenizer st = null;
        String tmpStr = null;
        // Создаем пустой вектор для хранения сообщений
        messages = new Vector();
    
        // Перебираем сообщения на сервере
        for(int i = 1; command.list(i); i++)
        {
          // Если сообщение с заданным номером имеется,
          // считать его с сервера
          if(command.retr(i))
          {
            // Найти место в данных, где находится первая
            // отметка конца строки
            int offset = response.buff.indexOf(EOL);
            // Записать сообщение в вектор, отрезав от
            // него строку статуса
            messages.addElement(
              response.buff.substring(
                offset + EOL.length()));
          }
          else throw new POPException(POPException.RETR_ERROR);
        }
      }
    
      // Возвращает пользователю текст сообщения.
      // Нумерация начинается с 1
      public String getMessage(int number)
      {
        return (String)messages.elementAt(number - 1);
      }
    
      // Удаляет сообщение. Нумерация начинается с 1
      public boolean deleteMessage(int number)
                               throws POPException
      {
        if(command.dele(number))
        {
          // Удалить сообщение из вектора 
          messages.removeElementAt(number-1);
          return true;
        }
        return false;
      }
    
      // Отменить удаление сообщений, которые были
      // удалены вызовом метода deleteMessage()
      public boolean undoDeletes() throws POPException
      {
        if(command.rset())
        {
          messages = null;
          // Перезагрузить почту с сервера
          downloadMessages();
          return true;
        }
        return false;
      }
    
      // Этот внутренний класс предварительно сохраняет
      // необработанные данные
      class POPResponse
      {
        // Временное хранилище полученных данных
        private String buff = "";
    
        // Возвращает true, если последняя команда
        // выполнена успешно, false - если
        // неудачно. Возбуждает исключение, если
        // сервер прислал неопределенный ответ
        public boolean isSucceed() throws POPException
        {
          boolean result = true;
    
          if(!buff.startsWith(S_OK))
          {
            if(!buff.startsWith(S_ERR))
            {
              throw new POPException(POPException.BAD_RESPONSE);
            }
            result = false;
          }
          return result;
        }
    
        // Записать данные во временный буфер
        protected void setBuff(String s)
        {
          buff = s;
        }
    
        // Считать данные из временного буфера
        protected String getBuff()
        {
          return buff;
        }
    
        // Возвратить данные без статусного признака
        protected String cutOffStatus()
        {
          int offset = buff.indexOf(' ');
          if(offset != -1)
          {
            String tmpStr = buff.substring(offset);
            return tmpStr.trim();
          }
          return null;
        }
    
        // Получить комментарий сервера к последней
        // выполненной команде
        public String getServerComment()
        {
          String tmpStr = null;
    
          tmpStr = cutOffStatus();
          int offset = tmpStr.indexOf(EOL);
          if(offset != -1)
            return tmpStr.substring(0, offset);
          return null;
        }
      }
    
    
      // Внутренний класс для представления команды,
      // посылаемой серверу
      class POPCommand
      {
        // Посылает строку почтовому серверу
        private void sendCommand(String command)
                                  throws POPException
        {
          logText("Sending command... ");
          try{ ow.println(command); }
          catch(IOException e)
          { throw new POPException(POPException.IO_ERROR); }
        }
    
        // Получить ответ на служебную команду
        private void readCommandResponse() throws POPException
        {
          logText("Reading response...");
          StringBuffer tmpBuff = new StringBuffer();
    
          try { tmpBuff.append(ir.readLine()); }
          catch(IOException e)
          {
            throw new POPException(POPException.IO_ERROR);
          }
          response.setBuff(tmpBuff.toString());
        }
    
        // Считать почтовое сообщение
        private void readMessage() throws POPException
        {
          logText("Reading message...");
          String tmpStr = new String("");
          StringBuffer tmpBuff = new StringBuffer();
    
          try
          {
            // Читать строку за строкой, пока не будет
            // найдена строка-терминатор.
            while(!(tmpStr = ir.readLine()).equals("."))
              tmpBuff.append(tmpStr + "
    ");
          }
          catch(IOException e)
          {
            throw new POPException(POPException.IO_ERROR);
          }
          tmpStr = tmpBuff.toString();
          response.setBuff(tmpStr);
        }
    
        // Метод-оболочка для выполнения типичной команды
        private boolean transactCommand(String command)
                                 throws POPException
        {
          sendCommand(command);
          readCommandResponse();
          return response.isSucceed();
        }
    
        // Команды передачи имени пользователя
        public boolean user(String name) throws POPException
        {
          return transactCommand("USER " + name);
        }
    
        // Команды передачи пароля
        public boolean pass(String password) throws POPException
        {
          return transactCommand("PASS " + password);
        }
    
        // Команда завершения сеанса работы с почтой
        public boolean quit() throws POPException
        {
          return transactCommand("QUIT");
        }
    
        // Команда удаления сообщения
        public boolean dele(int number) throws POPException
        {
          if(number != 0)
            return transactCommand("DELE " + Integer.toString(number));
          else return false;
        }
    
        // Команда отмены удаления
        public boolean rset() throws POPException
        {
          return transactCommand("RSET");
        }
    
        // Получить информацию о сообщении
        public boolean list(int number) throws POPException
        {
          if(number != 0)
            return transactCommand("LIST " + Integer.toString(number));
          else return false;
        }
    
        // Команда чтения сообщения с сервера
        public boolean retr(int number) throws POPException
        {
          if(number != 0)
          {
            sendCommand("RETR " + Integer.toString(number));
            readMessage();
            return response.isSucceed();
          }
          else return false;
        }
      }
    }
    
    
    
    

    Листинг 2

    //  Вспомогательный класс представляет исключения,
    //  которые могут возникнуть в процессе работы
    //  Автор - Дмитрий Рамодин
    //  Изд. дом "Открытые Системы"
    //
    
    // Формируем пакет
    package Mitrich.mail;
    
    public class POPException extends Exception
    {
      // Константы типов исключительных ситуаций
      public static final int NOT_AVAILABLE = 0;
      public static final int BAD_RESPONSE = 1;
      public static final int BAD_NAME = 2;
      public static final int BAD_PASSWORD = 3;
      public static final int SOCKET_ERROR = 4;
      public static final int HOST_UNKNOWN = 5;
      public static final int IO_ERROR = 6;
      public static final int RETR_ERROR = 7;
      
      // Причина возникновения исключения
      private static int why = NOT_AVAILABLE;
    
      // Конструктор по умолчанию
      public POPException()
      {
        super();
      }
    
      // Конструктор со строкой
      public POPException(String message)
      {
        super(message);
      }
    
      // Конструктор, в котором задается причина
      // исключительной ситуации
      public POPException(int reason)
      {
        super(POPException.assignMessage(reason));
      }
    
      // Задать строку, соответствующую причине
      private static String assignMessage(int reason)
      {
        why = reason;
    
        switch(reason)
        {
          case BAD_RESPONSE:
            return new String(
               "Bad response from the mail server
    ");
          case SOCKET_ERROR:
            return new String(
               "Socket I/O couldn't be established
    ");
          case BAD_NAME:
            return new String(
               "There is not such user name
    ");
          case BAD_PASSWORD:
            return new String(
               "Invalid password
    ");
          case HOST_UNKNOWN:
            return new String(
               "Wrong hostname
    ");
          case IO_ERROR:
            return new String(
               "I/O operation error.
    ");
          case RETR_ERROR:
            return new String(
               "Fatal error occured during message reading
    ");
          default:
            return new String(
               "Unknown POPClient failure
    ");
        }
      }
    
      // Возвращает причину исключительной ситуации
      public int why()
      {
        return why;
      }
    }

    IMailPlus.zip

    Introduction

    The code here is written under the common creativity license you could download the project from the link above or clicking on the next link IMailPlus.zip It has been enspired from Rodolfo Finochietti POP3 Project I have redesigned it and implement it and added to it SSL Support and optimized the design so that you could easily add other email clients below you will find a screen shot from the created demo application

    imailplusdemo

    This post will go with you step by step towards building your own Mail Client using C#, First to start I’ll start you need to first know the protocol you will be retrieving emails with I will discuss here the POP3 Protocol. The POP3 Protocols an application-layer Internet standard protocol along with IMAP4 they are the two most prevalent Internet standard protocols for e-mail retrieval. To understand how the protocol works lets examine a (Server — Client) communication example. First the server is waiting for connection from any client
    S: <wait for connection on TCP port 110>

    The Client opens a TCP Connection with the server

    C: <open connection>

    The Server then response with the +OK Ready State

    S: +OK POP3 server ready <1123.69123552@mail.ramymostafa.com>

    Then we authenticate using the username and password

    C: USER username_here

    S: +OK send PASS
    C: PASS password

    S: +OK Welcome.

    C: LIST

    S: +OK 2 messages (320 octets)

    S: 1 120

    S: 2 200

    S: .

    C: RETR 1

    S: +OK 120 octets

    S: <the POP3 server sends message 1>

    S: .

    C: DELE 1

    S: +OK message 1 deleted

    C: RETR 2

    S: +OK 200 octets

    S: <the POP3 server sends message 2>

    The QUIT command will remove retrieved messages from the server and sign off.

    C: QUIT

    S: +OK dewey POP3 server signing off (maildrop empty)
    C: <close connection>

    S: <wait for next connection>

    Designing the Mail Client

    imailpluslibrarydiagram

    Now that we know how the POP3 protocol work we could start building our Mail Client. But before that let’s discuss the structure of an e-mail message.

    mailmessage A mail message consists of

    • Header which includes things like
      • Mime type
      • Received Date
      • Subject
      • To
      • From
    • Body which includes the Messages
      • HTML Message
      • Text Message

    So, Basically the email client should first connect to the server and then authenticate itself with the server then start to exchange the protocol messages with the server and parse the replies to extract from it the message data or the reply status itself etc…, It should be able also to retrieve the emails from the server and parse the message retrieve to extract from it the MailMessage Information.

    Implementing the Email Client

    Connect the Client To The Server using the given username, password and server it takes as parameters the Mail Server Domain Reference, The Mail Account User Name and Password.

            public override void Connect(string server, string UserName, string Password)
    {
           try
           {
               if (_isConnected)
               {
                   return;
               }
               if (!UseSSL)
               {
                   Connect(server, PortNumber);
                   string response = Response();
                   if (!response.Trim().StartsWith("+OK"))
                   {
                        
                   }
                   else
                   {
                       ExecuteCommand("USER", UserName);
                       ExecuteCommand("PASS", Password);
                   }
                   _isConnected = true;
               }
               else
               {
                   byte[] bts;
                   int res;
                   string responseString = "";
                   ResponseList.Clear();
                   Connect(server, PortNumber);
                   inStream = new SslStream(this.GetStream(), false,
                      new RemoteCertificateValidationCallback(ValidateServerCertificate),
                       new LocalCertificateSelectionCallback(SelectLocalCertificate));
                   inStream.AuthenticateAsClient(server);
                   bts = new byte[1024];
                   res = inStream.Read(bts, 0, bts.Length);
                   ResponseList.Add(Encoding.ASCII.GetString(bts, 0, res));
                   responseString = ExecuteCommand("USER", UserName);
                   ResponseList.Add(responseString);
                   responseString = ExecuteCommand("PASS", Password);
                   ResponseList.Add(responseString);
                   if (!responseString.Trim().StartsWith("+OK"))
                   {
                       
                   }
                   else
                       _isConnected = true;
                }
            }
            catch (Exception ex)
            {
                
            }
    }

    The GetMailList Method first Execute the Command List then it Retrieves a MetaMessageInfo from the Response String here the response string is parsed to retrieve Meta Information of the emails on the server like Number of Messages, and Messages Length

     List result = new List();
     string responseString = ExecuteCommand("LIST");
    MetaMessageInfo info = new MetaMessageInfo(MailClientType.POP3,responseString);

    Here we make a loop to retrieve messages until we retrieve all messages using the number of messages in the POPMessage constructor it extracts all the message info from the string to fill its attributes

    for (int i = 1; i <= info.NumberOfMessages; i++)
    {
        responseString = ExecuteCommand("RETR", i.ToString(), true);
        POPMessage message = new POPMessage(responseString);
        message.Number = i.ToString();
        message.Retrieved = true;
        if (message.MessageBoundaryId != null)
             result.Add(message);
    }

    The Method that prase the response string to retrieve info from the Email is the LoadMethod

    public override void Load(string messageString)
    {
        string message = messageString.Replace("+OK message follows", "");
        Message = message;
        string messageIdPure = MessageBoundaryId;
        
       
        int bodyIndex = message.IndexOf("--" + messageIdPure, StringComparison.CurrentCultureIgnoreCase);
        int messageLength = message.Length - bodyIndex;
        if (bodyIndex < 0)
            return;
        string bodyString = message.Substring(bodyIndex, messageLength);
        string[] splitMessageOptions = new string[1];
        splitMessageOptions[0] = "--" + messageIdPure;
        
        string[] messages = bodyString.Split(splitMessageOptions, StringSplitOptions.RemoveEmptyEntries);
        
        GetMessageBody(messages);
    }

    to extract a header property I use

    _from = GetHeaderValue(EmailHeaders.From);

    Where the EmailHeaders.From is an enumerator with the headers and the GetHeaderValue Method extracts the required header value from the complete message string.

    IMailPlusLibrary Usage

    to use the library it’s simple all you need to to do is to do the below code

    POPEmailClient popClient = new POPEmailClient();
    popClient.UseSSL = account.UseSSL;
    popClient.PortNumber = Convert.ToInt32(account.PortNumber);
    popClient.Connect(account.Server, account.UserName, account.Password);
    account.CurrentAccountMails = popClient.GetMailList();

    Share Your Thoughts

    Finally I hope that this project would be of a great help for you. Please feel free to contact me regarding anything you don’t understand in the code yo will find the library code fully XML documented. Also if you have any cool ideas or updates for it please share your thoughts here.

    IMailPlus.zip

    Introduction

    The code here is written under the common creativity license you could download the project from the link above or clicking on the next link IMailPlus.zip It has been enspired from Rodolfo Finochietti POP3 Project I have redesigned it and implement it and added to it SSL Support and optimized the design so that you could easily add other email clients below you will find a screen shot from the created demo application

    imailplusdemo

    This post will go with you step by step towards building your own Mail Client using C#, First to start I’ll start you need to first know the protocol you will be retrieving emails with I will discuss here the POP3 Protocol. The POP3 Protocols an application-layer Internet standard protocol along with IMAP4 they are the two most prevalent Internet standard protocols for e-mail retrieval. To understand how the protocol works lets examine a (Server — Client) communication example. First the server is waiting for connection from any client
    S: <wait for connection on TCP port 110>

    The Client opens a TCP Connection with the server

    C: <open connection>

    The Server then response with the +OK Ready State

    S: +OK POP3 server ready <1123.69123552@mail.ramymostafa.com>

    Then we authenticate using the username and password

    C: USER username_here

    S: +OK send PASS
    C: PASS password

    S: +OK Welcome.

    C: LIST

    S: +OK 2 messages (320 octets)

    S: 1 120

    S: 2 200

    S: .

    C: RETR 1

    S: +OK 120 octets

    S: <the POP3 server sends message 1>

    S: .

    C: DELE 1

    S: +OK message 1 deleted

    C: RETR 2

    S: +OK 200 octets

    S: <the POP3 server sends message 2>

    The QUIT command will remove retrieved messages from the server and sign off.

    C: QUIT

    S: +OK dewey POP3 server signing off (maildrop empty)
    C: <close connection>

    S: <wait for next connection>

    Designing the Mail Client

    imailpluslibrarydiagram

    Now that we know how the POP3 protocol work we could start building our Mail Client. But before that let’s discuss the structure of an e-mail message.

    mailmessage A mail message consists of

    • Header which includes things like
      • Mime type
      • Received Date
      • Subject
      • To
      • From
    • Body which includes the Messages
      • HTML Message
      • Text Message

    So, Basically the email client should first connect to the server and then authenticate itself with the server then start to exchange the protocol messages with the server and parse the replies to extract from it the message data or the reply status itself etc…, It should be able also to retrieve the emails from the server and parse the message retrieve to extract from it the MailMessage Information.

    Implementing the Email Client

    Connect the Client To The Server using the given username, password and server it takes as parameters the Mail Server Domain Reference, The Mail Account User Name and Password.

            public override void Connect(string server, string UserName, string Password)
    {
           try
           {
               if (_isConnected)
               {
                   return;
               }
               if (!UseSSL)
               {
                   Connect(server, PortNumber);
                   string response = Response();
                   if (!response.Trim().StartsWith("+OK"))
                   {
                        
                   }
                   else
                   {
                       ExecuteCommand("USER", UserName);
                       ExecuteCommand("PASS", Password);
                   }
                   _isConnected = true;
               }
               else
               {
                   byte[] bts;
                   int res;
                   string responseString = "";
                   ResponseList.Clear();
                   Connect(server, PortNumber);
                   inStream = new SslStream(this.GetStream(), false,
                      new RemoteCertificateValidationCallback(ValidateServerCertificate),
                       new LocalCertificateSelectionCallback(SelectLocalCertificate));
                   inStream.AuthenticateAsClient(server);
                   bts = new byte[1024];
                   res = inStream.Read(bts, 0, bts.Length);
                   ResponseList.Add(Encoding.ASCII.GetString(bts, 0, res));
                   responseString = ExecuteCommand("USER", UserName);
                   ResponseList.Add(responseString);
                   responseString = ExecuteCommand("PASS", Password);
                   ResponseList.Add(responseString);
                   if (!responseString.Trim().StartsWith("+OK"))
                   {
                       
                   }
                   else
                       _isConnected = true;
                }
            }
            catch (Exception ex)
            {
                
            }
    }

    The GetMailList Method first Execute the Command List then it Retrieves a MetaMessageInfo from the Response String here the response string is parsed to retrieve Meta Information of the emails on the server like Number of Messages, and Messages Length

     List result = new List();
     string responseString = ExecuteCommand("LIST");
    MetaMessageInfo info = new MetaMessageInfo(MailClientType.POP3,responseString);

    Here we make a loop to retrieve messages until we retrieve all messages using the number of messages in the POPMessage constructor it extracts all the message info from the string to fill its attributes

    for (int i = 1; i <= info.NumberOfMessages; i++)
    {
        responseString = ExecuteCommand("RETR", i.ToString(), true);
        POPMessage message = new POPMessage(responseString);
        message.Number = i.ToString();
        message.Retrieved = true;
        if (message.MessageBoundaryId != null)
             result.Add(message);
    }

    The Method that prase the response string to retrieve info from the Email is the LoadMethod

    public override void Load(string messageString)
    {
        string message = messageString.Replace("+OK message follows", "");
        Message = message;
        string messageIdPure = MessageBoundaryId;
        
       
        int bodyIndex = message.IndexOf("--" + messageIdPure, StringComparison.CurrentCultureIgnoreCase);
        int messageLength = message.Length - bodyIndex;
        if (bodyIndex < 0)
            return;
        string bodyString = message.Substring(bodyIndex, messageLength);
        string[] splitMessageOptions = new string[1];
        splitMessageOptions[0] = "--" + messageIdPure;
        
        string[] messages = bodyString.Split(splitMessageOptions, StringSplitOptions.RemoveEmptyEntries);
        
        GetMessageBody(messages);
    }

    to extract a header property I use

    _from = GetHeaderValue(EmailHeaders.From);

    Where the EmailHeaders.From is an enumerator with the headers and the GetHeaderValue Method extracts the required header value from the complete message string.

    IMailPlusLibrary Usage

    to use the library it’s simple all you need to to do is to do the below code

    POPEmailClient popClient = new POPEmailClient();
    popClient.UseSSL = account.UseSSL;
    popClient.PortNumber = Convert.ToInt32(account.PortNumber);
    popClient.Connect(account.Server, account.UserName, account.Password);
    account.CurrentAccountMails = popClient.GetMailList();

    Share Your Thoughts

    Finally I hope that this project would be of a great help for you. Please feel free to contact me regarding anything you don’t understand in the code yo will find the library code fully XML documented. Also if you have any cool ideas or updates for it please share your thoughts here.

    import java.awt.*;

    import java.awt.event.*;

    import java.net.*;

    import java.util.*;

    import javax.mail.*;

    import javax.mail.internet.*;

    import javax.swing.*;

    import javax.swing.event.*;

    // The E-mail Client.

    public class EmailClient extends JFrame {

        // Message table’s data model.

        private MessagesTableModel tableModel;

        // Table listing messages.

        private JTable table;

        // This the text area for displaying messages.

        private JTextArea messageTextArea;

      /* This is the split panel that holds the messages

         table and the message view panel. */

        private JSplitPane splitPane;

        // These are the buttons for managing the selected message.

        private JButton replyButton, forwardButton, deleteButton;

        // Currently selected message in table.

        private Message selectedMessage;

        // Flag for whether or not a message is being deleted.

        private boolean deleting;

        // This is the JavaMail session.

        private Session session;

        // Constructor for E-mail Client.

        public EmailClient() {

            // Set application title.

            setTitle(«E-mail Client»);

            // Set window size.

            setSize(640, 480);

            // Handle window closing events.

            addWindowListener(new WindowAdapter() {

                public void windowClosing(WindowEvent e) {

                    actionExit();

                }

            });

            // Setup file menu.

            JMenuBar menuBar = new JMenuBar();

            JMenu fileMenu = new JMenu(«File»);

            fileMenu.setMnemonic(KeyEvent.VK_F);

            JMenuItem fileExitMenuItem = new JMenuItem(«Exit»,

                    KeyEvent.VK_X);

            fileExitMenuItem.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionExit();

                }

            });

            fileMenu.add(fileExitMenuItem);

            menuBar.add(fileMenu);

            setJMenuBar(menuBar);

            // Setup buttons panel.

            JPanel buttonPanel = new JPanel();

            JButton newButton = new JButton(«New Message»);

            newButton.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionNew();

                }

            });

            buttonPanel.add(newButton);

            // Setup messages table.

            tableModel = new MessagesTableModel();

            table = new JTable(tableModel);

            table.getSelectionModel().addListSelectionListener(new

                    ListSelectionListener() {

                public void valueChanged(ListSelectionEvent e) {

                    tableSelectionChanged();

                }

            });

            // Allow only one row at a time to be selected.

            table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

            // Setup E-mails panel.

            JPanel emailsPanel = new JPanel();

            emailsPanel.setBorder(

                    BorderFactory.createTitledBorder(«E-mails»));

            messageTextArea = new JTextArea();

            messageTextArea.setEditable(false);

            splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,

                    new JScrollPane(table), new JScrollPane(messageTextArea));

            emailsPanel.setLayout(new BorderLayout());

            emailsPanel.add(splitPane, BorderLayout.CENTER);

            // Setup buttons panel 2.

            JPanel buttonPanel2 = new JPanel();

            replyButton = new JButton(«Reply»);

            replyButton.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionReply();

                }

            });

            replyButton.setEnabled(false);

            buttonPanel2.add(replyButton);

            forwardButton = new JButton(«Forward»);

            forwardButton.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionForward();

                }

            });

            forwardButton.setEnabled(false);

            buttonPanel2.add(forwardButton);

            deleteButton = new JButton(«Delete»);

            deleteButton.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionDelete();

                }

            });

            deleteButton.setEnabled(false);

            buttonPanel2.add(deleteButton);

            // Add panels to display.

            getContentPane().setLayout(new BorderLayout());

            getContentPane().add(buttonPanel, BorderLayout.NORTH);

            getContentPane().add(emailsPanel, BorderLayout.CENTER);

            getContentPane().add(buttonPanel2, BorderLayout.SOUTH);

        }

        // Exit this program.

        private void actionExit() {

            System.exit(0);

        }

        // Create a new message.

        private void actionNew() {

            sendMessage(MessageDialog.NEW, null);

        }

        // Called when table row selection changes.

        private void tableSelectionChanged() {

        /* If not in the middle of deleting a message, set

           the selected message and display it. */

            if (!deleting) {

                selectedMessage =

                        tableModel.getMessage(table.getSelectedRow());

                showSelectedMessage();

                updateButtons();

            }

        }

        // Reply to a message.

        private void actionReply() {

            sendMessage(MessageDialog.REPLY, selectedMessage);

        }

        // Forward a message.

        private void actionForward() {

            sendMessage(MessageDialog.FORWARD, selectedMessage);

        }

        // Delete the selected message.

        private void actionDelete() {

            deleting = true;

            try {

                // Delete message from server.

                selectedMessage.setFlag(Flags.Flag.DELETED, true);

                Folder folder = selectedMessage.getFolder();

                folder.close(true);

                folder.open(Folder.READ_WRITE);

            } catch (Exception e) {

                showError(«Unable to delete message.», false);

            }

            // Delete message from table.

            tableModel.deleteMessage(table.getSelectedRow());

            // Update GUI.

            messageTextArea.setText(«»);

            deleting = false;

            selectedMessage = null;

            updateButtons();

        }

        // Send the specified message.

        private void sendMessage(int type, Message message) {

            // Display message dialog to get message values.

            MessageDialog dialog;

            try {

                dialog = new MessageDialog(this, type, message);

                if (!dialog.display()) {

                    // Return if dialog was cancelled.

                    return;

                }

            } catch (Exception e) {

                showError(«Unable to send message.», false);

                return;

            }

            try {

                // Create a new message with values from dialog.

                Message newMessage = new MimeMessage(session);

                newMessage.setFrom(new InternetAddress(dialog.getFrom()));

                newMessage.setRecipient(Message.RecipientType.TO,

                        new InternetAddress(dialog.getTo()));

                newMessage.setSubject(dialog.getSubject());

                newMessage.setSentDate(new Date());

                newMessage.setText(dialog.getContent());

                // Send new message.

                Transport.send(newMessage);

            } catch (Exception e) {

                showError(«Unable to send message.», false);

            }

        }

        // Show the selected message in the content panel.

        private void showSelectedMessage() {

            // Show hour glass cursor while message is loaded.

            setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

            try {

                messageTextArea.setText(

                        getMessageContent(selectedMessage));

                messageTextArea.setCaretPosition(0);

            } catch (Exception e) {

                showError(«Unabled to load message.», false);

            } finally {

                // Return to default cursor.

                setCursor(Cursor.getDefaultCursor());

            }

        }

      /* Update each button’s state based off of whether or not

         there is a message currently selected in the table. */

        private void updateButtons() {

            if (selectedMessage != null) {

                replyButton.setEnabled(true);

                forwardButton.setEnabled(true);

                deleteButton.setEnabled(true);

            } else {

                replyButton.setEnabled(false);

                forwardButton.setEnabled(false);

                deleteButton.setEnabled(false);

            }

        }

        // Show the application window on the screen.

        public void show() {

            super.show();

            // Update the split panel to be divided 50/50.

            splitPane.setDividerLocation(.5);

        }

        // Connect to e-mail server.

        public void connect() {

            // Display connect dialog.

            ConnectDialog dialog = new ConnectDialog(this);

            dialog.show();

            // Build connection URL from connect dialog settings.

            StringBuffer connectionUrl = new StringBuffer();

            connectionUrl.append(dialog.getType() + «://»);

            connectionUrl.append(dialog.getUsername() + «:»);

            connectionUrl.append(dialog.getPassword() + «@»);

            connectionUrl.append(dialog.getServer() + «/»);

        /* Display dialog stating that messages are

           currently being downloaded from server. */

            final DownloadingDialog downloadingDialog =

                    new DownloadingDialog(this);

            SwingUtilities.invokeLater(new Runnable() {

                public void run() {

                    downloadingDialog.show();

                }

            });

            // Establish JavaMail session and connect to server.

            Store store = null;

            try {

                // Initialize JavaMail session with SMTP server.

                Properties props = new Properties();

                props.put(«mail.smtp.host», dialog.getSmtpServer());

                session = Session.getDefaultInstance(props, null);

                // Connect to e-mail server.

                URLName urln = new URLName(connectionUrl.toString());

                store = session.getStore(urln);

                store.connect();

            } catch (Exception e) {

                // Close the downloading dialog.

                downloadingDialog.dispose();

                // Show error dialog.

                showError(«Unable to connect.», true);

            }

            // Download message headers from server.

            try {

                // Open main «INBOX» folder.

                Folder folder = store.getFolder(«INBOX»);

                folder.open(Folder.READ_WRITE);

                // Get folder’s list of messages.

                Message[] messages = folder.getMessages();

                // Retrieve message headers for each message in folder.

                FetchProfile profile = new FetchProfile();

                profile.add(FetchProfile.Item.ENVELOPE);

                folder.fetch(messages, profile);

                // Put messages in table.

                tableModel.setMessages(messages);

            } catch (Exception e) {

                // Close the downloading dialog.

                downloadingDialog.dispose();

                // Show error dialog.

                showError(«Unable to download messages.», true);

            }

            // Close the downloading dialog.

            downloadingDialog.dispose();

        }

        // Show error dialog and exit afterwards if necessary.

        private void showError(String message, boolean exit) {

            JOptionPane.showMessageDialog(this, message, «Error»,

                    JOptionPane.ERROR_MESSAGE);

            if (exit)

                System.exit(0);

        }

        // Get a message’s content.

        public static String getMessageContent(Message message)

        throws Exception {

            Object content = message.getContent();

            if (content instanceof Multipart) {

                StringBuffer messageContent = new StringBuffer();

                Multipart multipart = (Multipart) content;

                for (int i = 0; i < multipart.getCount(); i++) {

                    Part part = (Part) multipart.getBodyPart(i);

                    if (part.isMimeType(«text/plain»)) {

                        messageContent.append(part.getContent().toString());

                    }

                }

                return messageContent.toString();

            } else {

                return content.toString();

            }

        }

        // Run the E-mail Client.

        public static void main(String[] args) {

            EmailClient client = new EmailClient();

            client.show();

            // Display connect dialog.

            client.connect();

        }

    }

    ConnectDialog:

    import java.awt.*;

    import java.awt.event.*;

    import javax.swing.*;

    /* This class displays a dialog for entering e-mail

       server connection settings. */

    public class ConnectDialog extends JDialog {

        // These are the e-mail server types.

        private static final String[] TYPES = {«pop3», «imap»};

        // Combo box for e-mail server types.

        private JComboBox typeComboBox;

        // Server, username and SMTP server text fields.

        private JTextField serverTextField, usernameTextField;

        private JTextField smtpServerTextField;

        // Password text field.

        private JPasswordField passwordField;

        // Constructor for dialog.

        public ConnectDialog(Frame parent) {

            // Call super constructor, specifying that dialog is modal.

            super(parent, true);

            // Set dialog title.

            setTitle(«Connect»);

            // Handle closing events.

            addWindowListener(new WindowAdapter() {

                public void windowClosing(WindowEvent e) {

                    actionCancel();

                }

            });

            // Setup settings panel.

            JPanel settingsPanel = new JPanel();

            settingsPanel.setBorder(

                    BorderFactory.createTitledBorder(«Connection Settings»));

            GridBagConstraints constraints;

            GridBagLayout layout = new GridBagLayout();

            settingsPanel.setLayout(layout);

            JLabel typeLabel = new JLabel(«Type:»);

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.EAST;

            constraints.insets = new Insets(5, 5, 0, 0);

            layout.setConstraints(typeLabel, constraints);

            settingsPanel.add(typeLabel);

            typeComboBox = new JComboBox(TYPES);

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.WEST;

            constraints.gridwidth = GridBagConstraints.REMAINDER;

            constraints.insets = new Insets(5, 5, 0, 5);

            constraints.weightx = 1.0D;

            layout.setConstraints(typeComboBox, constraints);

            settingsPanel.add(typeComboBox);

            JLabel serverLabel = new JLabel(«Server:»);

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.EAST;

            constraints.insets = new Insets(5, 5, 0, 0);

            layout.setConstraints(serverLabel, constraints);

            settingsPanel.add(serverLabel);

            serverTextField = new JTextField(25);

            constraints = new GridBagConstraints();

            constraints.gridwidth = GridBagConstraints.REMAINDER;

            constraints.insets = new Insets(5, 5, 0, 5);

            constraints.weightx = 1.0D;

            layout.setConstraints(serverTextField, constraints);

            settingsPanel.add(serverTextField);

            JLabel usernameLabel = new JLabel(«Username:»);

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.EAST;

            constraints.insets = new Insets(5, 5, 0, 0);

            layout.setConstraints(usernameLabel, constraints);

            settingsPanel.add(usernameLabel);

            usernameTextField = new JTextField();

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.WEST;

            constraints.fill = GridBagConstraints.HORIZONTAL;

            constraints.gridwidth = GridBagConstraints.REMAINDER;

            constraints.insets = new Insets(5, 5, 0, 5);

            constraints.weightx = 1.0D;

            layout.setConstraints(usernameTextField, constraints);

            settingsPanel.add(usernameTextField);

            JLabel passwordLabel = new JLabel(«Password:»);

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.EAST;

            constraints.insets = new Insets(5, 5, 5, 0);

            layout.setConstraints(passwordLabel, constraints);

            settingsPanel.add(passwordLabel);

            passwordField = new JPasswordField();

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.WEST;

            constraints.fill = GridBagConstraints.HORIZONTAL;

            constraints.gridwidth = GridBagConstraints.REMAINDER;

            constraints.insets = new Insets(5, 5, 5, 5);

            constraints.weightx = 1.0D;

            layout.setConstraints(passwordField, constraints);

            settingsPanel.add(passwordField);

            JLabel smtpServerLabel = new JLabel(«SMTP Server:»);

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.EAST;

            constraints.insets = new Insets(5, 5, 5, 0);

            layout.setConstraints(smtpServerLabel, constraints);

            settingsPanel.add(smtpServerLabel);

            smtpServerTextField = new JTextField(25);

            constraints = new GridBagConstraints();

            constraints.gridwidth = GridBagConstraints.REMAINDER;

            constraints.insets = new Insets(5, 5, 5, 5);

            constraints.weightx = 1.0D;

            layout.setConstraints(smtpServerTextField, constraints);

            settingsPanel.add(smtpServerTextField);

            // Setup buttons panel.

            JPanel buttonsPanel = new JPanel();

            JButton connectButton = new JButton(«Connect»);

            connectButton.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionConnect();

                }

            });

            buttonsPanel.add(connectButton);

            JButton cancelButton = new JButton(«Cancel»);

            cancelButton.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionCancel();

                }

            });

            buttonsPanel.add(cancelButton);

            // Add panels to display.

            getContentPane().setLayout(new BorderLayout());

            getContentPane().add(settingsPanel, BorderLayout.CENTER);

            getContentPane().add(buttonsPanel, BorderLayout.SOUTH);

            // Size dialog to components.

            pack();

            // Center dialog over application.

            setLocationRelativeTo(parent);

        }

        // Validate connection settings and close dialog.

        private void actionConnect() {

            if (serverTextField.getText().trim().length() < 1

                    || usernameTextField.getText().trim().length() < 1

                    || passwordField.getPassword().length < 1

                    || smtpServerTextField.getText().trim().length() < 1) {

                JOptionPane.showMessageDialog(this,

                        «One or more settings is missing.»,

                        «Missing Setting(s)», JOptionPane.ERROR_MESSAGE);

                return;

            }

            // Close dialog.

            dispose();

        }

        // Cancel connecting and exit program.

        private void actionCancel() {

            System.exit(0);

        }

        // Get e-mail server type.

        public String getType() {

            return (String) typeComboBox.getSelectedItem();

        }

        // Get e-mail server.

        public String getServer() {

            return serverTextField.getText();

        }

        // Get e-mail username.

        public String getUsername() {

            return usernameTextField.getText();

        }

        // Get e-mail password.

        public String getPassword() {

            return new String(passwordField.getPassword());

        }

        // Get e-mail SMTP server.

        public String getSmtpServer() {

            return smtpServerTextField.getText();

        }

    }

    MessagesTableModel:

    import java.util.*;

    import javax.mail.*;

    import javax.swing.*;

    import javax.swing.table.*;

    // This class manages the e-mail table’s data.

    public class MessagesTableModel extends AbstractTableModel {

        // These are the names for the table’s columns.

        private static final String[] columnNames = {«Sender»,

        «Subject», «Date»};

        // The table’s list of messages.

        private ArrayList messageList = new ArrayList();

        // Sets the table’s list of messages.

        public void setMessages(Message[] messages) {

            for (int i = messages.length 1; i >= 0; i) {

                messageList.add(messages[i]);

            }

            // Fire table data change notification to table.

            fireTableDataChanged();

        }

        // Get a message for the specified row.

        public Message getMessage(int row) {

            return (Message) messageList.get(row);

        }

        // Remove a message from the list.

        public void deleteMessage(int row) {

            messageList.remove(row);

            // Fire table row deletion notification to table.

            fireTableRowsDeleted(row, row);

        }

        // Get table’s column count.

        public int getColumnCount() {

            return columnNames.length;

        }

        // Get a column’s name.

        public String getColumnName(int col) {

            return columnNames[col];

        }

        // Get table’s row count.

        public int getRowCount() {

            return messageList.size();

        }

        // Get value for a specific row and column combination.

        public Object getValueAt(int row, int col) {

            try {

                Message message = (Message) messageList.get(row);

                switch (col) {

                    case 0: // Sender

                        Address[] senders = message.getFrom();

                        if (senders != null || senders.length > 0) {

                            return senders[0].toString();

                        } else {

                            return «[none]»;

                        }

                    case 1: // Subject

                        String subject = message.getSubject();

                        if (subject != null && subject.length() > 0) {

                            return subject;

                        } else {

                            return «[none]»;

                        }

                    case 2: // Date

                        Date date = message.getSentDate();

                        if (date != null) {

                            return date.toString();

                        } else {

                            return «[none]»;

                        }

                }

            } catch (Exception e) {

                // Fail silently.

                return «»;

            }

            return «»;

        }

    }

    MessageDialog:

    import java.awt.*;

    import java.awt.event.*;

    import javax.mail.*;

    import javax.swing.*;

    // This class displays the dialog used for creating messages.

    public class MessageDialog extends JDialog {

        // Dialog message identifiers.

        public static final int NEW = 0;

        public static final int REPLY = 1;

        public static final int FORWARD = 2;

        // Message from, to and subject text fields.

        private JTextField fromTextField, toTextField;

        private JTextField subjectTextField;

        // Message content text area.

        private JTextArea contentTextArea;

        // Flag specifying whether or not dialog was cancelled.

        private boolean cancelled;

        // Constructor for dialog.

        public MessageDialog(Frame parent, int type, Message message)

        throws Exception {

            // Call super constructor, specifying that dialog is modal.

            super(parent, true);

        /* Set dialog title and get message’s «to», «subject»

           and «content» values based on message type. */

            String to = «», subject = «», content = «»;

            switch (type) {

                // Reply message.

                case REPLY:

                    setTitle(«Reply To Message»);

                    // Get message «to» value

                    Address[] senders = message.getFrom();

                    if (senders != null || senders.length > 0) {

                        to = senders[0].toString();

                    }

                    to = message.getFrom()[0].toString();

                    // Get message subject.

                    subject = message.getSubject();

                    if (subject != null && subject.length() > 0) {

                        subject = «RE: « + subject;

                    } else {

                        subject = «RE:»;

                    }

                    // Get message content and add «REPLIED TO» notation.

                    content = «n—————— « +

                            «REPLIED TO MESSAGE» +

                            » ——————n» +

                            EmailClient.getMessageContent(message);

                    break;

                    // Forward message.

                case FORWARD:

                    setTitle(«Forward Message»);

                    // Get message subject.

                    subject = message.getSubject();

                    if (subject != null && subject.length() > 0) {

                        subject = «FWD: « + subject;

                    } else {

                        subject = «FWD:»;

                    }

                    // Get message content and add «FORWARDED» notation.

                    content = «n—————— « +

                            «FORWARDED MESSAGE» +

                            » ——————n» +

                            EmailClient.getMessageContent(message);

                    break;

                    // New message.

                default:

                    setTitle(«New Message»);

            }

            // Handle closing events.

            addWindowListener(new WindowAdapter() {

                public void windowClosing(WindowEvent e) {

                    actionCancel();

                }

            });

            // Setup fields panel.

            JPanel fieldsPanel = new JPanel();

            GridBagConstraints constraints;

            GridBagLayout layout = new GridBagLayout();

            fieldsPanel.setLayout(layout);

            JLabel fromLabel = new JLabel(«From:»);

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.EAST;

            constraints.insets = new Insets(5, 5, 0, 0);

            layout.setConstraints(fromLabel, constraints);

            fieldsPanel.add(fromLabel);

            fromTextField = new JTextField();

            constraints = new GridBagConstraints();

            constraints.fill = GridBagConstraints.HORIZONTAL;

            constraints.gridwidth = GridBagConstraints.REMAINDER;

            constraints.insets = new Insets(5, 5, 0, 0);

            layout.setConstraints(fromTextField, constraints);

            fieldsPanel.add(fromTextField);

            JLabel toLabel = new JLabel(«To:»);

            constraints = new GridBagConstraints();

            constraints.anchor = GridBagConstraints.EAST;

            constraints.insets = new Insets(5, 5, 0, 0);

            layout.setConstraints(toLabel, constraints);

            fieldsPanel.add(toLabel);

            toTextField = new JTextField(to);

            constraints = new GridBagConstraints();

            constraints.fill = GridBagConstraints.HORIZONTAL;

            constraints.gridwidth = GridBagConstraints.REMAINDER;

            constraints.insets = new Insets(5, 5, 0, 0);

            constraints.weightx = 1.0D;

            layout.setConstraints(toTextField, constraints);

            fieldsPanel.add(toTextField);

            JLabel subjectLabel = new JLabel(«Subject:»);

            constraints = new GridBagConstraints();

            constraints.insets = new Insets(5, 5, 5, 0);

            layout.setConstraints(subjectLabel, constraints);

            fieldsPanel.add(subjectLabel);

            subjectTextField = new JTextField(subject);

            constraints = new GridBagConstraints();

            constraints.fill = GridBagConstraints.HORIZONTAL;

            constraints.gridwidth = GridBagConstraints.REMAINDER;

            constraints.insets = new Insets(5, 5, 5, 0);

            layout.setConstraints(subjectTextField, constraints);

            fieldsPanel.add(subjectTextField);

            // Setup content panel.

            JScrollPane contentPanel = new JScrollPane();

            contentTextArea = new JTextArea(content, 10, 50);

            contentPanel.setViewportView(contentTextArea);

            // Setup buttons panel.

            JPanel buttonsPanel = new JPanel();

            JButton sendButton = new JButton(«Send»);

            sendButton.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionSend();

                }

            });

            buttonsPanel.add(sendButton);

            JButton cancelButton = new JButton(«Cancel»);

            cancelButton.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    actionCancel();

                }

            });

            buttonsPanel.add(cancelButton);

            // Add panels to display.

            getContentPane().setLayout(new BorderLayout());

            getContentPane().add(fieldsPanel, BorderLayout.NORTH);

            getContentPane().add(contentPanel, BorderLayout.CENTER);

            getContentPane().add(buttonsPanel, BorderLayout.SOUTH);

            // Size dialog to components.

            pack();

            // Center dialog over application.

            setLocationRelativeTo(parent);

        }

        // Validate message fields and close dialog.

        private void actionSend() {

            if (fromTextField.getText().trim().length() < 1

                    || toTextField.getText().trim().length() < 1

                    || subjectTextField.getText().trim().length() < 1

                    || contentTextArea.getText().trim().length() < 1) {

                JOptionPane.showMessageDialog(this,

                        «One or more fields is missing.»,

                        «Missing Field(s)», JOptionPane.ERROR_MESSAGE);

                return;

            }

            // Close dialog.

            dispose();

        }

        // Cancel creating this message and close dialog.

        private void actionCancel() {

            cancelled = true;

            // Close dialog.

            dispose();

        }

        // Show dialog.

        public boolean display() {

            show();

            // Return whether or not display was successful.

            return !cancelled;

        }

        // Get message’s «From» field value.

        public String getFrom() {

            return fromTextField.getText();

        }

        // Get message’s «To» field value.

        public String getTo() {

            return toTextField.getText();

        }

        // Get message’s «Subject» field value.

        public String getSubject() {

            return subjectTextField.getText();

        }

        // Get message’s «content» field value.

        public String getContent() {

            return contentTextArea.getText();

        }

    }

    DownloadingDialog:

    import java.awt.*;

    import javax.swing.*;

    /* This class displays a simple dialog instructing the user

       that messages are being downloaded. */

    public class DownloadingDialog extends JDialog {

        // Constructor for dialog.

        public DownloadingDialog(Frame parent) {

            // Call super constructor, specifying that dialog is modal.

            super(parent, true);

            // Set dialog title.

            setTitle(«E-mail Client»);

            // Instruct window not to close when the «X» is clicked.

            setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

            // Put a message with a nice border in this dialog.

            JPanel contentPane = new JPanel();

            contentPane.setBorder(

                    BorderFactory.createEmptyBorder(5, 5, 5, 5));

            contentPane.add(new JLabel(«Downloading messages…»));

            setContentPane(contentPane);

            // Size dialog to components.

            pack();

            // Center dialog over application.

            setLocationRelativeTo(parent);

        }

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    
    /*********************************************************************
     
    MODULE NAME:    b64.c
     
    AUTHOR:         Bob Trower 08/04/01
     
    PROJECT:        Crypt Data Packaging
     
    COPYRIGHT:      Copyright (c) Trantor Standard Systems Inc., 2001
     
    NOTE:           This source code may be used as you wish, subject to
                    the MIT license.  See the LICENCE section below.
     
    DESCRIPTION:
                    This little utility implements the Base64
                    Content-Transfer-Encoding standard described in
                    RFC1113 ([url]http://www.faqs.org/rfcs/rfc1113.html[/url]).
     
                    This is the coding scheme used by MIME to allow
                    binary data to be transferred by SMTP mail.
     
                    Groups of 3 bytes from a binary stream are coded as
                    groups of 4 bytes in a text stream.
     
                    The input stream is 'padded' with zeros to create
                    an input that is an even multiple of 3.
     
                    A special character ('=') is used to denote padding so
                    that the stream can be decoded back to its exact size.
     
                    Encoded output is formatted in lines which should
                    be a maximum of 72 characters to conform to the
                    specification.  This program defaults to 72 characters,
                    but will allow more or less through the use of a
                    switch.  The program enforces a minimum line size
                    of 4 characters.
     
                    Example encoding:
     
                    The stream 'ABCD' is 32 bits long.  It is mapped as
                    follows:
     
                    ABCD
     
                     A (65)     B (66)     C (67)     D (68)   (None) (None)
                    01000001   01000010   01000011   01000100
     
                    16 (Q)  20 (U)  9 (J)   3 (D)    17 (R) 0 (A)  NA (=) NA (=)
                    010000  010100  001001  000011   010001 000000 000000 000000
     
     
                    QUJDRA==
     
                    Decoding is the process in reverse.  A 'decode' lookup
                    table has been created to avoid string scans.
     
    DESIGN GOALS:   Specifically:
            Code is a stand-alone utility to perform base64 
            encoding/decoding. It should be genuinely useful 
            when the need arises and it meets a need that is 
            likely to occur for some users.  
            Code acts as sample code to show the author's 
            design and coding style.  
     
            Generally: 
            This program is designed to survive:
            Everything you need is in a single source file.
            It compiles cleanly using a vanilla ANSI C compiler.
            It does its job correctly with a minimum of fuss.  
            The code is not overly clever, not overly simplistic 
            and not overly verbose. 
            Access is 'cut and paste' from a web page.  
            Terms of use are reasonable.  
     
    VALIDATION:     Non-trivial code is never without errors.  This
                    file likely has some problems, since it has only
                    been tested by the author.  It is expected with most
                    source code that there is a period of 'burn-in' when
                    problems are identified and corrected.  That being
                    said, it is possible to have 'reasonably correct'
                    code by following a regime of unit test that covers
                    the most likely cases and regression testing prior
                    to release.  This has been done with this code and
                    it has a good probability of performing as expected.
     
                    Unit Test Cases:
     
                    case 0:empty file:
                        CASE0.DAT  ->  ->
                        (Zero length target file created
                        on both encode and decode.)
     
                    case 1:One input character:
                        CASE1.DAT A -> QQ== -> A
     
                    case 2:Two input characters:
                        CASE2.DAT AB -> QUJD -> AB
     
                    case 3:Three input characters:
                        CASE3.DAT ABC -> QUJD -> ABC
     
                    case 4:Four input characters:
                        case4.dat ABCD -> QUJDRA== -> ABCD
     
                    case 5:All chars from 0 to ff, linesize set to 50:
     
                        AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIj
                        JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZH
                        SElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWpr
                        bG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P
                        kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz
                        tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX
                        2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7
                        /P3+/w==
     
                    case 6:Mime Block from e-mail:
                        (Data same as test case 5)
     
                    case 7: Large files:
                        Tested 28 MB file in/out.
     
                    case 8: Random Binary Integrity:
                        This binary program (b64.exe) was encoded to base64,
                        back to binary and then executed.
     
                    case 9 Stress:
                        All files in a working directory encoded/decoded
                        and compared with file comparison utility to
                        ensure that multiple runs do not cause problems
                        such as exhausting file handles, tmp storage, etc.
     
                    -------------
     
                    Syntax, operation and failure:
                        All options/switches tested.  Performs as
                        expected.
     
                    case 10:
                        No Args -- Shows Usage Screen
                        Return Code 1 (Invalid Syntax)
                    case 11:
                        One Arg (invalid) -- Shows Usage Screen
                        Return Code 1 (Invalid Syntax)
                    case 12:
                        One Arg Help (-?) -- Shows detailed Usage Screen.
                        Return Code 0 (Success -- help request is valid).
                    case 13:
                        One Arg Help (-h) -- Shows detailed Usage Screen.
                        Return Code 0 (Success -- help request is valid).
                    case 14:
                        One Arg (valid) -- Uses stdin/stdout (filter)
                        Return Code 0 (Sucess)
                    case 15:
                        Two Args (invalid file) -- shows system error.
                        Return Code 2 (File Error)
                    case 16:
                        Encode non-existent file -- shows system error.
                        Return Code 2 (File Error)
                    case 17:
                        Out of disk space -- shows system error.
                        Return Code 3 (File I/O Error)
     
                    -------------
     
                    Compile/Regression test:
                        gcc compiled binary under Cygwin
                        Microsoft Visual Studio under Windows 2000
                        Microsoft Version 6.0 C under Windows 2000
     
    DEPENDENCIES:   None
     
    LICENCE:        Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc.
     
                    Permission is hereby granted, free of charge, to any person
                    obtaining a copy of this software and associated
                    documentation files (the "Software"), to deal in the
                    Software without restriction, including without limitation
                    the rights to use, copy, modify, merge, publish, distribute,
                    sublicense, and/or sell copies of the Software, and to
                    permit persons to whom the Software is furnished to do so,
                    subject to the following conditions:
     
                    The above copyright notice and this permission notice shall
                    be included in all copies or substantial portions of the
                    Software.
     
                    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
                    KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
                    WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
                    PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
                    OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
                    OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
                    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
                    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     
    VERSION HISTORY:
                    Bob Trower 08/04/01 -- Create Version 0.00.00B
     
    ******************************************************************* */
     
    #include <stdio.h>
    #include <stdlib.h>
     
    /*
    ** Translation Table as described in RFC1113
    */
    static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
     
    /*
    ** Translation Table to decode (created by author)
    */
    static const char cd64[]="|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\]^_`abcdefghijklmnopq";
     
    /*
    ** encodeblock
    **
    ** encode 3 8-bit binary bytes as 4 '6-bit' characters
    */
    void encodeblock( unsigned char in[3], unsigned char out[4], int len )
    {
        out[0] = cb64[ in[0] >> 2 ];
        out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
        out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
        out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
    }
     
    /*
    ** encode
    **
    ** base64 encode a stream adding padding and line breaks as per spec.
    */
    void encode( FILE *infile, FILE *outfile, int linesize )
    {
        unsigned char in[3], out[4];
        int i, len, blocksout = 0;
     
        while( !feof( infile ) ) {
            len = 0;
            for( i = 0; i < 3; i++ ) {
                in[i] = (unsigned char) getc( infile );
                if( !feof( infile ) ) {
                    len++;
                }
                else {
                    in[i] = 0;
                }
            }
            if( len ) {
                encodeblock( in, out, len );
                for( i = 0; i < 4; i++ ) {
                    putc( out[i], outfile );
                }
                blocksout++;
            }
            if( blocksout >= (linesize/4) || feof( infile ) ) {
                if( blocksout ) {
                    fprintf( outfile, "rn" );
                }
                blocksout = 0;
            }
        }
    }
     
    /*
    ** decodeblock
    **
    ** decode 4 '6-bit' characters into 3 8-bit binary bytes
    */
    void decodeblock( unsigned char in[4], unsigned char out[3] )
    {   
        out[ 0 ] = (unsigned char ) (in[0] << 2 | in[1] >> 4);
        out[ 1 ] = (unsigned char ) (in[1] << 4 | in[2] >> 2);
        out[ 2 ] = (unsigned char ) (((in[2] << 6) & 0xc0) | in[3]);
    }
     
    /*
    ** decode
    **
    ** decode a base64 encoded stream discarding padding, line breaks and noise
    */
    void decode( FILE *infile, FILE *outfile )
    {
        unsigned char in[4], out[3], v;
        int i, len;
     
        while( !feof( infile ) ) {
            for( len = 0, i = 0; i < 4 && !feof( infile ); i++ ) {
                v = 0;
                while( !feof( infile ) && v == 0 ) {
                    v = (unsigned char) getc( infile );
                    v = (unsigned char) ((v < 43 || v > 122) ? 0 : cd64[ v - 43 ]);
                    if( v ) {
                        v = (unsigned char) ((v == '$') ? 0 : v - 61);
                    }
                }
                if( !feof( infile ) ) {
                    len++;
                    if( v ) {
                        in[ i ] = (unsigned char) (v - 1);
                    }
                }
                else {
                    in[i] = 0;
                }
            }
            if( len ) {
                decodeblock( in, out );
                for( i = 0; i < len - 1; i++ ) {
                    putc( out[i], outfile );
                }
            }
        }
    }
     
    /*
    ** returnable errors
    **
    ** Error codes returned to the operating system.
    **
    */
    #define B64_SYNTAX_ERROR        1
    #define B64_FILE_ERROR          2
    #define B64_FILE_IO_ERROR       3
    #define B64_ERROR_OUT_CLOSE     4
    #define B64_LINE_SIZE_TO_MIN    5
     
    /*
    ** b64_message
    **
    ** Gather text messages in one place.
    **
    */
    char *b64_message( int errcode )
    {
        #define B64_MAX_MESSAGES 6
        char *msgs[ B64_MAX_MESSAGES ] = {
                "b64:000:Invalid Message Code.",
                "b64:001:Syntax Error -- check help for usage.",
                "b64:002:File Error Opening/Creating Files.",
                "b64:003:File I/O Error -- Note: output file not removed.",
                "b64:004:Error on output file close.",
                "b64:004:linesize set to minimum."
        };
        char *msg = msgs[ 0 ];
     
        if( errcode > 0 && errcode < B64_MAX_MESSAGES ) {
            msg = msgs[ errcode ];
        }
     
        return( msg );
    }
     
    /*
    ** b64
    **
    ** 'engine' that opens streams and calls encode/decode
    */
     
    int b64( int opt, char *infilename, char *outfilename, int linesize )
    {
        FILE *infile;
        int retcode = B64_FILE_ERROR;
     
        if( !infilename ) {
            infile = stdin;
        }
        else {
            infile = fopen( infilename, "rb" );
        }
        if( !infile ) {
            perror( infilename );
        }
        else {
            FILE *outfile;
            if( !outfilename ) {
                outfile = stdout;
            }
            else {
                outfile = fopen( outfilename, "wb" );
            }
            if( !outfile ) {
                perror( outfilename );
            }
            else {
                if( opt == 'e' ) {
                    encode( infile, outfile, linesize );
                }
                else {
                    decode( infile, outfile );
                }
                if (ferror( infile ) || ferror( outfile )) {
                    retcode = B64_FILE_IO_ERROR;
                }
                else {
                     retcode = 0;
                }
                if( outfile != stdout ) {
                    if( fclose( outfile ) != 0 ) {
                        perror( b64_message( B64_ERROR_OUT_CLOSE ) );
                        retcode = B64_FILE_IO_ERROR;
                    }
                }
            }
            if( infile != stdin ) {
                fclose( infile );
            }
        }
     
        return( retcode );
    }
     
    /*
    ** showuse
    **
    ** display usage information, help, version info
    */
    void showuse( int morehelp )
    {
        {
            printf( "n" );
            printf( "  b64      (Base64 Encode/Decode)      Bob Trower 08/03/01 n" );
            printf( "           (C) Copr Bob Trower 1986-01.      Version 0.00B n" );
            printf( "  Usage:   b64 -option  [ -l num ] [<FileIn> [<FileOut>]]  n" );
            printf( "  Purpose: This program is a simple utility that implementsn" );
            printf( "           Base64 Content-Transfer-Encoding (RFC1113).     n" );
        }
        if( !morehelp ) {
            printf( "           Use -h option for additional help.              n" );
        }
        else {
            printf( "  Options: -e  encode to Base64   -h  This help text.      n" );
            printf( "           -d  decode from Base64 -?  This help text.      n" );
            printf( "  Note:    -l  use to change line size (from 72 characters)n" );
            printf( "  Returns: 0 = success.  Non-zero is an error code.        n" );
            printf( "  ErrCode: 1 = Bad Syntax, 2 = File Open, 3 = File I/O     n" );
            printf( "  Example: b64 -e binfile b64file     <- Encode to b64     n" );
            printf( "           b64 -d b64file binfile     <- Decode from b64   n" );
            printf( "           b64 -e -l40 infile outfile <- Line Length of 40 n" );
            printf( "  Note:    Will act as a filter, but this should only be   n" );
            printf( "           used on text files due to translations made by  n" );
            printf( "           operating systems.                              n" );
            printf( "  Release: 0.00.00, Tue Aug 7   2:00:00 2001, ANSI-SOURCE Cn" );
        }
    }
     
    #define B64_DEF_LINE_SIZE   72
    #define B64_MIN_LINE_SIZE    4
     
    #define THIS_OPT(ac, av) (ac > 1 ? av[1][0] == '-' ? av[1][1] : 0 : 0)
     
    /*
    ** main
    **
    ** parse and validate arguments and call b64 engine or help
    */
    int main( int argc, char **argv )
    {
        int opt = 0;
        int retcode = 0;
        int linesize = B64_DEF_LINE_SIZE;
        char *infilename = NULL, *outfilename = NULL;
     
        while( THIS_OPT( argc, argv ) ) {
            switch( THIS_OPT(argc, argv) ) {
                case 'l':
                        linesize = atoi( &(argv[1][2]) );
                        if( linesize < B64_MIN_LINE_SIZE ) {
                            linesize = B64_MIN_LINE_SIZE;
                            printf( "%sn", b64_message( B64_LINE_SIZE_TO_MIN ) );
                        }
                        break;
                case '?':
                case 'h':
                        opt = 'h';
                        break;
                case 'e':
                case 'd':
                        opt = THIS_OPT(argc, argv);
                        break;
                 default:
                        opt = 0;
                        break;
            }
            argv++;
            argc--;
        }
        switch( opt ) {
            case 'e':
            case 'd':
                infilename = argc > 1 ? argv[1] : NULL;
                outfilename = argc > 2 ? argv[2] : NULL;
                retcode = b64( opt, infilename, outfilename, linesize );
                break;
            case 0:
                retcode = B64_SYNTAX_ERROR;
            case 'h':
                showuse( opt );
                break;
     
        }
        if( retcode ) {
            printf( "%sn", b64_message( retcode ) );
        }
     
        return( retcode );
    }

    Понравилась статья? Поделить с друзьями:
  • Как написать свой полный адрес
  • Как написать свой стенд ап
  • Как написать свой поисковик
  • Как написать свой стартер спринг
  • Как написать свой плеер на питоне