Files
jmc/jabber/mailconnection.py
David Rousselie 505543262f Check mail new logic
- use RECENT flag for search on IMAP server with write access so RECENT flag is deleted on new messages and fetch read only so SEEN flag is not set on fetched messages.
- It might work on Exchange 2003 that do not support STORE flag UNSEEN
- This change the get_mail loop so now each MailConnection type implement get_next_mail_index because logic is different from IMAP to POP3 connection.

darcs-hash:20060207213333-86b55-9c2ccf31fb9ae9e6dd454c907f3188bc6080b817.gz
2006-02-07 22:33:33 +01:00

483 lines
17 KiB
Python

##
## mailconnection.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri Jan 7 11:06:42 2005
## $Id: mailconnection.py,v 1.11 2005/09/18 20:24:07 dax Exp $
##
## Copyright (C) 2005
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import sys
import logging
import email
import email.Header
import traceback
import poplib
import imaplib
import socket
from jabber.lang import Lang
IMAP4_TIMEOUT = 10
POP3_TIMEOUT = 10
DO_NOTHING = 0
DIGEST = 1
RETRIEVE = 2
default_encoding = "iso-8859-1"
## All MY* classes are implemented to add a timeout (settimeout)
## while connecting
class MYIMAP4(imaplib.IMAP4):
def open(self, host = '', port = imaplib.IMAP4_PORT):
"""Setup connection to remote server on "host:port"
(default: localhost:standard IMAP4 port).
This connection will be used by the routines:
read, readline, send, shutdown.
"""
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(IMAP4_TIMEOUT)
self.sock.connect((host, port))
self.sock.settimeout(None)
self.file = self.sock.makefile('rb')
class MYIMAP4_SSL(imaplib.IMAP4_SSL):
def open(self, host = '', port = imaplib.IMAP4_SSL_PORT):
"""Setup connection to remote server on "host:port".
(default: localhost:standard IMAP4 SSL port).
This connection will be used by the routines:
read, readline, send, shutdown.
"""
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(IMAP4_TIMEOUT)
self.sock.connect((host, port))
self.sock.settimeout(None)
self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
class MYPOP3(poplib.POP3):
def __init__(self, host, port = poplib.POP3_PORT):
self.host = host
self.port = port
msg = "getaddrinfo returns an empty list"
self.sock = None
for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
self.sock = socket.socket(af, socktype, proto)
self.sock.settimeout(POP3_TIMEOUT)
self.sock.connect(sa)
self.sock.settimeout(None)
except socket.error, msg:
if self.sock:
self.sock.close()
self.sock = None
continue
break
if not self.sock:
raise socket.error, msg
self.file = self.sock.makefile('rb')
self._debugging = 0
self.welcome = self._getresp()
class MYPOP3_SSL(poplib.POP3_SSL):
def __init__(self, host, port = poplib.POP3_SSL_PORT, keyfile = None, certfile = None):
self.host = host
self.port = port
self.keyfile = keyfile
self.certfile = certfile
self.buffer = ""
msg = "getaddrinfo returns an empty list"
self.sock = None
for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
self.sock = socket.socket(af, socktype, proto)
self.sock.settimeout(POP3_TIMEOUT)
self.sock.connect(sa)
self.sock.settimeout(None)
except socket.error, msg:
if self.sock:
self.sock.close()
self.sock = None
continue
break
if not self.sock:
raise socket.error, msg
self.file = self.sock.makefile('rb')
self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
self._debugging = 0
self.welcome = self._getresp()
class MailConnection(object):
""" Wrapper to mail connection and action.
Abstract class, do not represent real mail connection type"""
_logger = logging.getLogger("jabber.MailConnection")
def __init__(self, login = "", password = "", host = "", \
port = 110, ssl = False):
""" Initialize MailConnection object for common parameters of all
connections types
:Parameters:
- 'login': login used to connect mail server
- 'password': password associated with 'login' to connect mail server
- 'host': mail server hostname
- 'port': mail server port
- 'ssl': activate ssl to connect server
:Types:
- 'login': string
- 'password': string
- 'host': string
- 'port': int
- 'ssl': boolean"""
self.login = login
self.password = password
self.store_password = True
self.host = host
self.port = port
self.ssl = ssl
self.status = "offline"
self.connection = None
self.chat_action = RETRIEVE
self.online_action = RETRIEVE
self.away_action = RETRIEVE
self.xa_action = RETRIEVE
self.dnd_action = RETRIEVE
self.offline_action = DO_NOTHING
self.interval = 5
self.lastcheck = 0
self.default_lang_class = Lang.en
self.waiting_password_reply = False
self.in_error = False
self.live_email_only = False
self.first_check = True
def __eq__(self, other):
return self.get_type() == other.get_type() \
and self.login == other.login \
and (not self.store_password or self.password == other.password) \
and self.store_password == other.store_password \
and self.host == other.host \
and self.port == other.port \
and self.ssl == other.ssl \
and self.chat_action == other.chat_action \
and self.online_action == other.online_action \
and self.away_action == other.away_action \
and self.xa_action == other.xa_action \
and self.dnd_action == other.dnd_action \
and self.offline_action == other.offline_action \
and self.interval == other.interval \
and self.live_email_only == other.live_email_only
def __str__(self):
return self.get_type() + "#" + self.login + "#" + \
(self.store_password and self.password or "/\\") + "#" \
+ self.host + "#" + str(self.port) + "#" + str(self.chat_action) + "#" \
+ str(self.online_action) + "#" + str(self.away_action) + "#" + \
str(self.xa_action) + "#" + str(self.dnd_action) + "#" + \
str(self.offline_action) + "#" + str(self.interval) + "#" + str(self.live_email_only)
def get_decoded_part(self, part, charset_hint):
content_charset = part.get_content_charset()
if content_charset:
return unicode(part.get_payload(decode=True).decode(content_charset))
else:
result = ""
try:
result = unicode(part.get_payload(decode=True))
except Exception, e:
try:
result = unicode(part.get_payload(decode=True).decode("iso-8859-1"))
except Exception, e:
try:
result = unicode(part.get_payload(decode=True).decode(default_encoding))
except Exception, e:
if charset_hint is not None:
try:
result = unicode(part.get_payload(decode=True).decode(charset_hint))
except Exception, e:
type, value, stack = sys.exc_info()
print >>sys.stderr, "".join(traceback.format_exception
(type, value, stack, 5))
return result
def format_message(self, email_msg, include_body = True):
from_decoded = email.Header.decode_header(email_msg["From"])
charset_hint = None
email_from = u""
result = u"From : "
for i in range(len(from_decoded)):
if from_decoded[i][1]:
charset_hint = from_decoded[i][1]
email_from += unicode(from_decoded[i][0].decode(from_decoded[i][1]))
else:
try:
email_from += unicode(from_decoded[i][0])
except Exception,e:
try:
email_from += unicode(from_decoded[i][0].decode("iso-8859-1"))
except Exception, e:
try:
email_from += unicode(from_decoded[i][0].decode(default_encoding))
except Exception, e:
type, value, stack = sys.exc_info()
print >>sys.stderr, "".join(traceback.format_exception
(type, value, stack, 5))
result += email_from + u"\n"
subject_decoded = email.Header.decode_header(email_msg["Subject"])
result += u"Subject : "
for i in range(len(subject_decoded)):
if subject_decoded[i][1]:
charset_hint = subject_decoded[i][1]
result += unicode(subject_decoded[i][0].decode(subject_decoded[i][1]))
else:
try:
result += unicode(subject_decoded[i][0])
except Exception,e:
try:
result += unicode(subject_decoded[i][0].decode("iso-8859-1"))
except Exception, e:
try:
result += unicode(subject_decoded[i][0].decode(default_encoding))
except Exception, e:
if charset_hint is not None:
try:
result += unicode(subject_decoded[i][0].decode(charset_hint))
except Exception, e:
type, value, stack = sys.exc_info()
print >>sys.stderr, "".join(traceback.format_exception
(type, value, stack, 5))
result += u"\n\n"
if include_body:
action = {
"text/plain" : lambda part: self.get_decoded_part(part, charset_hint),
"text/html" : lambda part: "\n<<<HTML part skipped>>>\n"
}
for part in email_msg.walk():
content_type = part.get_content_type()
if action.has_key(content_type):
result += action[content_type](part) + u'\n'
return (result, email_from)
def format_message_summary(self, email_msg):
return self.format_message(email_msg, False)
def get_status_msg(self):
return self.get_type() + "://" + self.login + "@" + self.host + ":" + \
unicode(self.port)
def connect(self):
pass
def disconnect(self):
pass
def get_mail_list(self):
return 0
def get_mail(self, index):
return None
def get_mail_summary(self, index):
return None
def get_next_mail_index(self, mail_list):
pass
# Does not modify server state but just internal JMC state
def mark_all_as_read(self):
pass
def get_action(self):
mapping = {"online": self.online_action,
"chat": self.chat_action,
"away": self.away_action,
"xa": self.xa_action,
"dnd": self.dnd_action,
"offline": self.offline_action}
if mapping.has_key(self.status):
return mapping[self.status]
return DO_NOTHING
action = property(get_action)
class IMAPConnection(MailConnection):
_logger = logging.getLogger("jabber.IMAPConnection")
def __init__(self, login = "", password = "", host = "", \
port = None, ssl = False, mailbox = "INBOX"):
if not port:
if ssl:
port = 993
else:
port = 143
MailConnection.__init__(self, login, password, host, port, ssl)
self.mailbox = mailbox
def __eq__(self, other):
return MailConnection.__eq__(self, other) \
and self.mailbox == other.mailbox
def __str__(self):
return MailConnection.__str__(self) + "#" + self.mailbox
def get_type(self):
if self.ssl:
return "imaps"
return "imap"
def get_status(self):
return MailConnection.get_status(self) + "/" + self.mailbox
def connect(self):
IMAPConnection._logger.debug("Connecting to IMAP server " \
+ self.login + "@" + self.host + ":" + str(self.port) \
+ " (" + self.mailbox + "). SSL=" \
+ str(self.ssl))
if self.ssl:
self.connection = MYIMAP4_SSL(self.host, self.port)
else:
self.connection = MYIMAP4(self.host, self.port)
self.connection.login(self.login, self.password)
def disconnect(self):
IMAPConnection._logger.debug("Disconnecting from IMAP server " \
+ self.host)
self.connection.logout()
def get_mail_list(self):
IMAPConnection._logger.debug("Getting mail list")
typ, data = self.connection.select(self.mailbox)
typ, data = self.connection.search(None, 'RECENT')
if typ == 'OK':
return data[0].split(' ')
return None
def get_mail(self, index):
IMAPConnection._logger.debug("Getting mail " + str(index))
typ, data = self.connection.select(self.mailbox, True)
typ, data = self.connection.fetch(index, '(RFC822)')
if typ == 'OK':
return self.format_message(email.message_from_string(data[0][1]))
return u"Error while fetching mail " + str(index)
def get_mail_summary(self, index):
IMAPConnection._logger.debug("Getting mail summary " + str(index))
typ, data = self.connection.select(self.mailbox, True)
typ, data = self.connection.fetch(index, '(RFC822)')
if typ == 'OK':
return self.format_message_summary(email.message_from_string(data[0][1]))
return u"Error while fetching mail " + str(index)
def get_next_mail_index(self, mail_list):
if not mail_list or mail_list[0] == '':
return None
return mail_list.pop(0)
def mark_all_as_read(self):
self.get_mail_list()
type = property(get_type)
class POP3Connection(MailConnection):
_logger = logging.getLogger("jabber.POP3Connection")
def __init__(self, login = "", password = "", host = "", \
port = None, ssl = False):
if not port:
if ssl:
port = 995
else:
port = 110
MailConnection.__init__(self, login, password, host, port, ssl)
self.__nb_mail = 0
self.__lastmail = 0
def get_type(self):
if self.ssl:
return "pop3s"
return "pop3"
def connect(self):
POP3Connection._logger.debug("Connecting to POP3 server " \
+ self.login + "@" + self.host + ":" + str(self.port)\
+ ". SSL=" + str(self.ssl))
if self.ssl:
self.connection = MYPOP3_SSL(self.host, self.port)
else:
self.connection = MYPOP3(self.host, self.port)
try:
self.connection.apop(self.login, self.password)
except:
self.connection.user(self.login)
self.connection.pass_(self.password)
def disconnect(self):
POP3Connection._logger.debug("Disconnecting from POP3 server " \
+ self.host)
self.connection.quit()
def get_mail_list(self):
POP3Connection._logger.debug("Getting mail list")
count, size = self.connection.stat()
result = [str(i) for i in range(1, count + 1)]
if not result or result[0] == '':
self.__nb_mail = 0
else:
self.__nb_mail = len(result)
return result
def get_mail(self, index):
POP3Connection._logger.debug("Getting mail " + str(index))
ret, data, size = self.connection.retr(index)
if ret[0:3] == '+OK':
return self.format_message(email.message_from_string('\n'.join(data)))
return u"Error while fetching mail " + str(index)
def get_mail_summary(self, index):
POP3Connection._logger.debug("Getting mail summary " + str(index))
ret, data, size = self.connection.retr(index)
if ret[0:3] == '+OK':
return self.format_message_summary(email.message_from_string('\n'.join(data)))
return u"Error while fetching mail " + str(index)
def get_next_mail_index(self, mail_list):
if self.__nb_mail == self.__lastmail:
return None
if self.__nb_mail < self.__lastmail:
self.__lastmail = 0
result = self.__lastmail
self.__lastmail += 1
return result
def mark_all_as_read(self):
self.get_mail_list()
self.__lastmail = self.__nb_mail
type = property(get_type)