imaplib2: bump to v2.41

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
Nicolas Sebrecht 2015-03-18 21:54:36 +01:00
parent cf2a2c769c
commit 43dbe1578c
1 changed files with 140 additions and 107 deletions

View File

@ -17,9 +17,9 @@ Public functions: Internaldate2Time
__all__ = ("IMAP4", "IMAP4_SSL", "IMAP4_stream",
"Internaldate2Time", "ParseFlags", "Time2Internaldate")
__version__ = "2.37"
__version__ = "2.41"
__release__ = "2"
__revision__ = "37"
__revision__ = "41"
__credits__ = """
Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998.
String method conversion by ESR, February 2001.
@ -43,12 +43,22 @@ Single quoting introduced with the help of Vladimir Marek <vladimir.marek@oracle
Support for specifying SSL version by Ryan Kavanagh <rak@debian.org> July 2013.
Fix for gmail "read 0" error provided by Jim Greenleaf <james.a.greenleaf@gmail.com> August 2013.
Fix for offlineimap "indexerror: string index out of range" bug provided by Eygene Ryabinkin <rea@codelabs.ru> August 2013.
Fix for missing idle_lock in _handler() provided by Franklin Brook <franklin@brook.se> August 2014"""
Fix for missing idle_lock in _handler() provided by Franklin Brook <franklin@brook.se> August 2014.
Conversion to Python3 provided by F. Malina <fmalina@gmail.com> February 2015.
Fix for READ-ONLY error from multiple EXAMINE/SELECT calls for same mailbox by <piloub@users.sf.net> March 2015."""
__author__ = "Piers Lauder <piers@janeelix.com>"
__URL__ = "http://imaplib2.sourceforge.net"
__license__ = "Python License"
import binascii, errno, os, Queue, random, re, select, socket, sys, time, threading, traceback, zlib
import binascii, errno, os, random, re, select, socket, sys, time, threading, zlib
try:
import queue # py3
string_types = str
except ImportError:
import Queue as queue # py2
string_types = basestring
select_module = select
@ -160,13 +170,9 @@ class Request(object):
self.data = None
def abort_tb(self, typ, val, tb):
self.aborted = (typ, val, tb)
self.deliver(None)
def abort(self, typ, val):
self.abort_tb(typ, val, traceback.extract_stack())
self.aborted = (typ, val)
self.deliver(None)
def get_response(self, exc_fmt=None):
@ -175,10 +181,10 @@ class Request(object):
self.ready.wait()
if self.aborted is not None:
typ, val, tb = self.aborted
typ, val = self.aborted
if exc_fmt is None:
exc_fmt = '%s - %%s' % typ
raise typ, typ(exc_fmt % str(val)), tb
raise typ(exc_fmt % str(val))
return self.response
@ -256,7 +262,7 @@ class IMAP4(object):
containing the wildcard character '*', then enclose the argument
in single quotes: the quotes will be removed and the resulting
string passed unquoted. Note also that you can pass in an argument
with a type that doesn't evaluate to 'basestring' (eg: 'bytearray')
with a type that doesn't evaluate to 'string_types' (eg: 'bytearray')
and it will be converted to a string without quoting.
There is one instance variable, 'state', that is useful for tracking
@ -301,7 +307,6 @@ class IMAP4(object):
self.tagged_commands = {} # Tagged commands awaiting response
self.untagged_responses = [] # [[typ: [data, ...]], ...]
self.mailbox = None # Current mailbox selected
self.mailboxes = {} # Untagged responses state per mailbox
self.is_readonly = False # READ-ONLY desired state
self.idle_rqb = None # Server IDLE Request - see _IdleCont
self.idle_timeout = None # Must prod server occasionally
@ -357,8 +362,8 @@ class IMAP4(object):
self.commands_lock = threading.Lock()
self.idle_lock = threading.Lock()
self.ouq = Queue.Queue(10)
self.inq = Queue.Queue()
self.ouq = queue.Queue(10)
self.inq = queue.Queue()
self.wrth = threading.Thread(target=self._writer)
self.wrth.setDaemon(True)
@ -435,19 +440,19 @@ class IMAP4(object):
af, socktype, proto, canonname, sa = res
try:
s = socket.socket(af, socktype, proto)
except socket.error, msg:
except socket.error as msg:
continue
try:
for i in (0, 1):
try:
s.connect(sa)
break
except socket.error, msg:
except socket.error as msg:
if len(msg.args) < 2 or msg.args[0] != errno.EINTR:
raise
else:
raise socket.error(msg)
except socket.error, msg:
except socket.error as msg:
s.close()
continue
break
@ -527,7 +532,10 @@ class IMAP4(object):
data = self.compressor.compress(data)
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
self.sock.sendall(data)
if bytes != str:
self.sock.sendall(bytes(data, 'utf8'))
else:
self.sock.sendall(data)
def shutdown(self):
@ -813,7 +821,8 @@ class IMAP4(object):
data = kv_pairs[0] # Assume invoker passing correctly formatted string (back-compat)
else:
data = '(%s)' % ' '.join([(arg and self._quote(arg) or 'NIL') for arg in kv_pairs])
return self._simple_command(name, (data,), **kw)
return self._simple_command(name, data, **kw)
def idle(self, timeout=None, **kw):
@ -981,20 +990,14 @@ class IMAP4(object):
def select(self, mailbox='INBOX', readonly=False, **kw):
"""(typ, [data]) = select(mailbox='INBOX', readonly=False)
Select a mailbox. (Restores any previous untagged responses.)
Select a mailbox. (Flushes all untagged responses.)
'data' is count of messages in mailbox ('EXISTS' response).
Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
other responses should be obtained via "response('FLAGS')" etc."""
self.commands_lock.acquire()
# Save state of old mailbox, restore state for new...
self.mailboxes[self.mailbox] = self.untagged_responses
self.untagged_responses = self.mailboxes.setdefault(mailbox, [])
self.commands_lock.release()
self.mailbox = mailbox
self.is_readonly = readonly and True or False
self.is_readonly = bool(readonly)
if readonly:
name = 'EXAMINE'
else:
@ -1240,7 +1243,7 @@ class IMAP4(object):
# Must quote command args if "atom-specials" present,
# and not already quoted. NB: single quotes are removed.
if not isinstance(arg, basestring):
if not isinstance(arg, string_types):
return arg
if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
return arg
@ -1252,8 +1255,8 @@ class IMAP4(object):
def _choose_nonull_or_dflt(self, dflt, *args):
if isinstance(dflt, basestring):
dflttyp = basestring # Allow any string type
if isinstance(dflt, string_types):
dflttyp = string_types # Allow any string type
else:
dflttyp = type(dflt)
for arg in args:
@ -1303,12 +1306,16 @@ class IMAP4(object):
self._check_bye()
for typ in ('OK', 'NO', 'BAD'):
self._get_untagged_response(typ)
if name in ('EXAMINE', 'SELECT'):
self.untagged_responses = [] # Flush all untagged responses
else:
for typ in ('OK', 'NO', 'BAD'):
while self._get_untagged_response(typ):
continue
if self._get_untagged_response('READ-ONLY', leave=True) and not self.is_readonly:
self.literal = None
raise self.readonly('mailbox status changed to READ-ONLY')
if self._get_untagged_response('READ-ONLY', leave=True) and not self.is_readonly:
self.literal = None
raise self.readonly('mailbox status changed to READ-ONLY')
if self.Terminate:
raise self.abort('connection closed')
@ -1323,7 +1330,7 @@ class IMAP4(object):
literal = self.literal
if literal is not None:
self.literal = None
if isinstance(literal, basestring):
if isinstance(literal, string_types):
literator = None
data = '%s {%s}' % (data, len(literal))
else:
@ -1389,9 +1396,10 @@ class IMAP4(object):
return typ, dat
def _command_completer(self, (response, cb_arg, error)):
def _command_completer(self, cb_arg_list):
# Called for callback commands
(response, cb_arg, error) = cb_arg_list
rqb, kw = cb_arg
rqb.callback = kw['callback']
rqb.callback_arg = kw.get('cb_arg')
@ -1665,7 +1673,7 @@ class IMAP4(object):
if __debug__: self._log(1, 'starting')
typ, val, tb = self.abort, 'connection terminated', None
typ, val = self.abort, 'connection terminated'
while not self.Terminate:
@ -1683,12 +1691,11 @@ class IMAP4(object):
try:
line = self.inq.get(True, timeout)
except Queue.Empty:
except queue.Empty:
if self.idle_rqb is None:
if resp_timeout is not None and self.tagged_commands:
if __debug__: self._log(1, 'response timeout')
typ, val = self.abort, 'no response after %s secs' % resp_timeout
tb = traceback.extract_stack()
break
continue
if self.idle_timeout > time.time():
@ -1700,41 +1707,33 @@ class IMAP4(object):
if __debug__: self._log(1, 'inq None - terminating')
break
# self.inq can contain tuple, it means we got an exception.
# For example of code that produces tuple see IMAP4._reader()
# and IMAP4._writer().
#
# XXX: may be it will be more explicit to create own exception
# XXX: class and pass it instead of tuple.
if not isinstance(line, basestring):
typ, val, tb = line
if not isinstance(line, string_types):
typ, val = line
break
try:
self._put_response(line)
except:
typ, val = self.error, 'program error: %s - %s' % sys.exc_info()[:2]
tb = sys.exc_info()[2]
break
self.Terminate = True
if not tb:
tb = traceback.extract_stack()
if __debug__: self._log(1, 'terminating: %s' % repr(val))
while not self.ouq.empty():
try:
self.ouq.get_nowait().abort_tb(typ, val, tb)
except Queue.Empty:
qel = self.ouq.get_nowait()
if qel is not None:
qel.abort(typ, val)
except queue.Empty:
break
self.ouq.put(None)
self.commands_lock.acquire()
for name in self.tagged_commands.keys():
for name in list(self.tagged_commands.keys()):
rqb = self.tagged_commands.pop(name)
rqb.abort_tb(typ, val, tb)
rqb.abort(typ, val)
self.state_change_free.set()
self.commands_lock.release()
if __debug__: self._log(3, 'state_change_free.set')
@ -1795,13 +1794,22 @@ class IMAP4(object):
rxzero = 0
while True:
stop = data.find('\n', start)
if stop < 0:
line_part += data[start:]
break
stop += 1
line_part, start, line = \
'', stop, line_part + data[start:stop]
if bytes != str:
stop = data.find(b'\n', start)
if stop < 0:
line_part += data[start:].decode()
break
stop += 1
line_part, start, line = \
'', stop, line_part + data[start:stop].decode()
else:
stop = data.find('\n', start)
if stop < 0:
line_part += data[start:]
break
stop += 1
line_part, start, line = \
'', stop, line_part + data[start:stop]
if __debug__: self._log(4, '< %s' % line)
self.inq.put(line)
if self.TerminateReader:
@ -1816,7 +1824,7 @@ class IMAP4(object):
self._print_log()
if self.debug: self.debug += 4 # Output all
self._log(1, reason)
self.inq.put((self.abort, reason, sys.exc_info()[2]))
self.inq.put((self.abort, reason))
break
poll.unregister(self.read_fd)
@ -1862,13 +1870,22 @@ class IMAP4(object):
rxzero = 0
while True:
stop = data.find('\n', start)
if stop < 0:
line_part += data[start:]
break
stop += 1
line_part, start, line = \
'', stop, line_part + data[start:stop]
if bytes != str:
stop = data.find(b'\n', start)
if stop < 0:
line_part += data[start:].decode()
break
stop += 1
line_part, start, line = \
'', stop, line_part + data[start:stop].decode()
else:
stop = data.find('\n', start)
if stop < 0:
line_part += data[start:]
break
stop += 1
line_part, start, line = \
'', stop, line_part + data[start:stop]
if __debug__: self._log(4, '< %s' % line)
self.inq.put(line)
if self.TerminateReader:
@ -1880,7 +1897,7 @@ class IMAP4(object):
self._print_log()
if self.debug: self.debug += 4 # Output all
self._log(1, reason)
self.inq.put((self.abort, reason, sys.exc_info()[2]))
self.inq.put((self.abort, reason))
break
if __debug__: self._log(1, 'finished')
@ -1893,7 +1910,6 @@ class IMAP4(object):
if __debug__: self._log(1, 'starting')
reason = 'Terminated'
tb = None
while not self.Terminate:
rqb = self.ouq.get()
@ -1905,19 +1921,15 @@ class IMAP4(object):
if __debug__: self._log(4, '> %s' % rqb.data)
except:
reason = 'socket error: %s - %s' % sys.exc_info()[:2]
tb = sys.exc_info()[2]
if __debug__:
if not self.Terminate:
self._print_log()
if self.debug: self.debug += 4 # Output all
self._log(1, reason)
rqb.abort_tb(self.abort, reason, tb)
rqb.abort(self.abort, reason)
break
if not tb:
tb = traceback.extract_stack()
self.inq.put((self.abort, reason, tb))
self.inq.put((self.abort, reason))
if __debug__: self._log(1, 'finished')
@ -1952,7 +1964,7 @@ class IMAP4(object):
return
t = '\n\t\t'
l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
l = ['%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or '') for x in l]
self.debug_lock.acquire()
self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
self.debug_lock.release()
@ -2082,16 +2094,28 @@ class IMAP4_SSL(IMAP4):
data = self.compressor.compress(data)
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
if hasattr(self.sock, "sendall"):
self.sock.sendall(data)
if bytes != str:
if hasattr(self.sock, "sendall"):
self.sock.sendall(bytes(data, 'utf8'))
else:
dlen = len(data)
while dlen > 0:
sent = self.sock.write(bytes(data, 'utf8'))
if sent == dlen:
break # avoid copy
data = data[sent:]
dlen = dlen - sent
else:
bytes = len(data)
while bytes > 0:
sent = self.sock.write(data)
if sent == bytes:
break # avoid copy
data = data[sent:]
bytes = bytes - sent
if hasattr(self.sock, "sendall"):
self.sock.sendall(data)
else:
dlen = len(data)
while dlen > 0:
sent = self.sock.write(data)
if sent == dlen:
break # avoid copy
data = data[sent:]
dlen = dlen - sent
def ssl(self):
@ -2165,7 +2189,10 @@ class IMAP4_stream(IMAP4):
data = self.compressor.compress(data)
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
self.writefile.write(data)
if bytes != str:
self.writefile.write(bytes(data, 'utf8'))
else:
self.writefile.write(data)
self.writefile.flush()
@ -2243,7 +2270,7 @@ class _IdleCont(object):
MonthNames = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
Mon2num = dict(zip((x.encode() for x in MonthNames[1:]), range(1, 13)))
Mon2num = dict(list(zip((x.encode() for x in MonthNames[1:]), list(range(1, 13)))))
InternalDate = re.compile(r'.*INTERNALDATE "'
r'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
@ -2347,7 +2374,7 @@ if __name__ == '__main__':
try:
optlist, args = getopt.getopt(sys.argv[1:], 'd:il:s:p:')
except getopt.error, val:
except getopt.error as val:
optlist, args = (), ()
debug, debug_buf_lvl, port, stream_command, keyfile, certfile, idle_intr = (None,)*7
@ -2380,13 +2407,14 @@ if __name__ == '__main__':
% {'user':USER, 'lf':'\n', 'data':data}
test_seq1 = [
('list', ('""', '""')),
('list', ('""', '%')),
('create', ('/tmp/imaplib2_test.0',)),
('rename', ('/tmp/imaplib2_test.0', '/tmp/imaplib2_test.1')),
('CREATE', ('/tmp/imaplib2_test.2',)),
('append', ('/tmp/imaplib2_test.2', None, None, test_mesg)),
('list', ('/tmp', 'imaplib2_test*')),
('select', ('/tmp/imaplib2_test.2',)),
('create', ('imaplib2_test0',)),
('rename', ('imaplib2_test0', 'imaplib2_test1')),
('CREATE', ('imaplib2_test2',)),
('append', ('imaplib2_test2', None, None, test_mesg)),
('list', ('', 'imaplib2_test%')),
('select', ('imaplib2_test2',)),
('search', (None, 'SUBJECT', 'IMAP4 test')),
('fetch', ("'1:*'", '(FLAGS INTERNALDATE RFC822)')),
('store', ('1', 'FLAGS', '(\Deleted)')),
@ -2405,12 +2433,17 @@ if __name__ == '__main__':
('uid', ('SEARCH', 'ALL')),
('uid', ('THREAD', 'references', 'UTF-8', '(SEEN)')),
('recent', ()),
('examine', ()),
('select', ()),
('examine', ()),
('select', ()),
)
AsyncError = None
def responder((response, cb_arg, error)):
def responder(cb_arg_list):
(response, cb_arg, error) = cb_arg_list
global AsyncError
cmd, args = cb_arg
if error is not None:
@ -2467,7 +2500,7 @@ if __name__ == '__main__':
for cmd,args in test_seq1:
run(cmd, args)
for ml in run('list', ('/tmp/', 'imaplib2_test%'), cb=False):
for ml in run('list', ('', 'imaplib2_test%'), cb=False):
mo = re.match(r'.*"([^"]+)"$', ml)
if mo: path = mo.group(1)
else: path = ml.split()[-1]
@ -2475,7 +2508,7 @@ if __name__ == '__main__':
if 'ID' in M.capabilities:
run('id', ())
run('id', ('("name", "imaplib2")',))
run('id', ("(name imaplib2)",))
run('id', ("version", __version__, "os", os.uname()[0]))
for cmd,args in test_seq2:
@ -2527,16 +2560,16 @@ if __name__ == '__main__':
M._mesg('unused untagged responses in order, most recent last:')
for typ,dat in M.pop_untagged_responses(): M._mesg('\t%s %s' % (typ, dat))
print 'All tests OK.'
print('All tests OK.')
except:
if not idle_intr or not 'IDLE' in M.capabilities:
print 'Tests failed.'
print('Tests failed.')
if not debug:
print '''
print('''
If you would like to see debugging output,
try: %s -d5
''' % sys.argv[0]
''' % sys.argv[0])
raise