mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
V 2.16 from Piers:
I've changed the login command to force proper quoting of the password argument. I've also added some extra debugging code, which is removed when __debug__ is false.
This commit is contained in:
parent
97798b1d47
commit
8c0622114b
1 changed files with 123 additions and 50 deletions
173
Lib/imaplib.py
173
Lib/imaplib.py
|
@ -15,7 +15,7 @@ Public functions: Internaldate2tuple
|
|||
Time2Internaldate
|
||||
"""
|
||||
|
||||
__version__ = "2.15"
|
||||
__version__ = "2.16"
|
||||
|
||||
import binascii, re, socket, string, time, random, sys
|
||||
|
||||
|
@ -89,7 +89,8 @@ class IMAP4:
|
|||
AUTHENTICATE, and the last argument to APPEND which is passed as
|
||||
an IMAP4 literal. If necessary (the string contains
|
||||
white-space and isn't enclosed with either parentheses or
|
||||
double quotes) each string is quoted.
|
||||
double quotes) each string is quoted. However, the 'password'
|
||||
argument to the LOGIN command is always quoted.
|
||||
|
||||
Each command returns a tuple: (type, [data, ...]) where 'type'
|
||||
is usually 'OK' or 'NO', and 'data' is either the text from the
|
||||
|
@ -101,6 +102,11 @@ class IMAP4:
|
|||
from READ-WRITE to READ-ONLY raise the exception class
|
||||
<instance>.readonly("<reason>"), which is a sub-class of 'abort'.
|
||||
|
||||
"error" exceptions imply a program error.
|
||||
"abort" exceptions imply the connection should be reset, and
|
||||
the command re-tried.
|
||||
"readonly" exceptions imply the command should be re-tried.
|
||||
|
||||
Note: to use this module, you must read the RFCs pertaining
|
||||
to the IMAP4 protocol, as the semantics of the arguments to
|
||||
each IMAP4 command are left to the invoker, not to mention
|
||||
|
@ -111,6 +117,7 @@ class IMAP4:
|
|||
class abort(error): pass # Service errors - close and retry
|
||||
class readonly(abort): pass # Mailbox status changed to READ-ONLY
|
||||
|
||||
mustquote = re.compile(r'\W') # Match any non-alphanumeric character
|
||||
|
||||
def __init__(self, host = '', port = IMAP4_PORT):
|
||||
self.host = host
|
||||
|
@ -138,15 +145,15 @@ class IMAP4:
|
|||
# Get server welcome message,
|
||||
# request and store CAPABILITY response.
|
||||
|
||||
if __debug__ and self.debug >= 1:
|
||||
_mesg('new IMAP4 connection, tag=%s' % self.tagpre)
|
||||
if __debug__:
|
||||
if self.debug >= 1:
|
||||
_mesg('new IMAP4 connection, tag=%s' % self.tagpre)
|
||||
|
||||
self.welcome = self._get_response()
|
||||
if self.untagged_responses.has_key('PREAUTH'):
|
||||
self.state = 'AUTH'
|
||||
elif self.untagged_responses.has_key('OK'):
|
||||
self.state = 'NONAUTH'
|
||||
# elif self.untagged_responses.has_key('BYE'):
|
||||
else:
|
||||
raise self.error(self.welcome)
|
||||
|
||||
|
@ -156,8 +163,9 @@ class IMAP4:
|
|||
raise self.error('no CAPABILITY response from server')
|
||||
self.capabilities = tuple(string.split(string.upper(self.untagged_responses[cap][-1])))
|
||||
|
||||
if __debug__ and self.debug >= 3:
|
||||
_mesg('CAPABILITIES: %s' % `self.capabilities`)
|
||||
if __debug__:
|
||||
if self.debug >= 3:
|
||||
_mesg('CAPABILITIES: %s' % `self.capabilities`)
|
||||
|
||||
for version in AllowedVersions:
|
||||
if not version in self.capabilities:
|
||||
|
@ -229,8 +237,12 @@ class IMAP4:
|
|||
"""Append message to named mailbox.
|
||||
|
||||
(typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
|
||||
|
||||
All args except `message' can be None.
|
||||
"""
|
||||
name = 'APPEND'
|
||||
if not mailbox:
|
||||
mailbox = 'INBOX'
|
||||
if flags:
|
||||
if (flags[0],flags[-1]) != ('(',')'):
|
||||
flags = '(%s)' % flags
|
||||
|
@ -360,9 +372,13 @@ class IMAP4:
|
|||
def login(self, user, password):
|
||||
"""Identify client using plaintext password.
|
||||
|
||||
(typ, [data]) = <instance>.list(user, password)
|
||||
(typ, [data]) = <instance>.login(user, password)
|
||||
|
||||
NB: 'password' will be quoted.
|
||||
"""
|
||||
typ, dat = self._simple_command('LOGIN', user, password)
|
||||
#if not 'AUTH=LOGIN' in self.capabilities:
|
||||
# raise self.error("Server doesn't allow LOGIN authentication." % mech)
|
||||
typ, dat = self._simple_command('LOGIN', user, self._quote(password))
|
||||
if typ != 'OK':
|
||||
raise self.error(dat[-1])
|
||||
self.state = 'AUTH'
|
||||
|
@ -403,8 +419,9 @@ class IMAP4:
|
|||
|
||||
(typ, data) = <instance>.noop()
|
||||
"""
|
||||
if __debug__ and self.debug >= 3:
|
||||
_dump_ur(self.untagged_responses)
|
||||
if __debug__:
|
||||
if self.debug >= 3:
|
||||
_dump_ur(self.untagged_responses)
|
||||
return self._simple_command('NOOP')
|
||||
|
||||
|
||||
|
@ -464,7 +481,9 @@ class IMAP4:
|
|||
self.state = 'SELECTED'
|
||||
if not self.untagged_responses.has_key('READ-WRITE') \
|
||||
and not readonly:
|
||||
if __debug__ and self.debug >= 1: _dump_ur(self.untagged_responses)
|
||||
if __debug__:
|
||||
if self.debug >= 1:
|
||||
_dump_ur(self.untagged_responses)
|
||||
raise self.readonly('%s is not writable' % mailbox)
|
||||
return typ, self.untagged_responses.get('EXISTS', [None])
|
||||
|
||||
|
@ -546,16 +565,24 @@ class IMAP4:
|
|||
|
||||
def _append_untagged(self, typ, dat):
|
||||
|
||||
if dat is None: dat = ''
|
||||
ur = self.untagged_responses
|
||||
if __debug__ and self.debug >= 5:
|
||||
_mesg('untagged_responses[%s] %s += %s' %
|
||||
(typ, len(ur.get(typ,'')), dat))
|
||||
if __debug__:
|
||||
if self.debug >= 5:
|
||||
_mesg('untagged_responses[%s] %s += ["%s"]' %
|
||||
(typ, len(ur.get(typ,'')), dat))
|
||||
if ur.has_key(typ):
|
||||
ur[typ].append(dat)
|
||||
else:
|
||||
ur[typ] = [dat]
|
||||
|
||||
|
||||
def _check_bye(self):
|
||||
bye = self.untagged_responses.get('BYE')
|
||||
if bye:
|
||||
raise self.abort(bye[-1])
|
||||
|
||||
|
||||
def _command(self, name, *args):
|
||||
|
||||
if self.state not in Commands[name]:
|
||||
|
@ -574,16 +601,9 @@ class IMAP4:
|
|||
|
||||
tag = self._new_tag()
|
||||
data = '%s %s' % (tag, name)
|
||||
for d in args:
|
||||
if d is None: continue
|
||||
if type(d) is type(''):
|
||||
l = len(string.split(d))
|
||||
else:
|
||||
l = 1
|
||||
if l == 0 or l > 1 and (d[0],d[-1]) not in (('(',')'),('"','"')):
|
||||
data = '%s "%s"' % (data, d)
|
||||
else:
|
||||
data = '%s %s' % (data, d)
|
||||
for arg in args:
|
||||
if arg is None: continue
|
||||
data = '%s %s' % (data, self._checkquote(arg))
|
||||
|
||||
literal = self.literal
|
||||
if literal is not None:
|
||||
|
@ -594,14 +614,17 @@ class IMAP4:
|
|||
literator = None
|
||||
data = '%s {%s}' % (data, len(literal))
|
||||
|
||||
if __debug__:
|
||||
if self.debug >= 4:
|
||||
_mesg('> %s' % data)
|
||||
else:
|
||||
_log('> %s' % data)
|
||||
|
||||
try:
|
||||
self.sock.send('%s%s' % (data, CRLF))
|
||||
except socket.error, val:
|
||||
raise self.abort('socket error: %s' % val)
|
||||
|
||||
if __debug__ and self.debug >= 4:
|
||||
_mesg('> %s' % data)
|
||||
|
||||
if literal is None:
|
||||
return tag
|
||||
|
||||
|
@ -617,8 +640,9 @@ class IMAP4:
|
|||
if literator:
|
||||
literal = literator(self.continuation_response)
|
||||
|
||||
if __debug__ and self.debug >= 4:
|
||||
_mesg('write literal size %s' % len(literal))
|
||||
if __debug__:
|
||||
if self.debug >= 4:
|
||||
_mesg('write literal size %s' % len(literal))
|
||||
|
||||
try:
|
||||
self.sock.send(literal)
|
||||
|
@ -633,14 +657,14 @@ class IMAP4:
|
|||
|
||||
|
||||
def _command_complete(self, name, tag):
|
||||
self._check_bye()
|
||||
try:
|
||||
typ, data = self._get_tagged_response(tag)
|
||||
except self.abort, val:
|
||||
raise self.abort('command: %s => %s' % (name, val))
|
||||
except self.error, val:
|
||||
raise self.error('command: %s => %s' % (name, val))
|
||||
if self.untagged_responses.has_key('BYE') and name != 'LOGOUT':
|
||||
raise self.abort(self.untagged_responses['BYE'][-1])
|
||||
self._check_bye()
|
||||
if typ == 'BAD':
|
||||
raise self.error('%s command error: %s %s' % (name, typ, data))
|
||||
return typ, data
|
||||
|
@ -695,8 +719,9 @@ class IMAP4:
|
|||
# Read literal direct from connection.
|
||||
|
||||
size = string.atoi(self.mo.group('size'))
|
||||
if __debug__ and self.debug >= 4:
|
||||
_mesg('read literal size %s' % size)
|
||||
if __debug__:
|
||||
if self.debug >= 4:
|
||||
_mesg('read literal size %s' % size)
|
||||
data = self.file.read(size)
|
||||
|
||||
# Store response with literal as tuple
|
||||
|
@ -714,8 +739,9 @@ class IMAP4:
|
|||
if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
|
||||
self._append_untagged(self.mo.group('type'), self.mo.group('data'))
|
||||
|
||||
if __debug__ and self.debug >= 1 and typ in ('NO', 'BAD'):
|
||||
_mesg('%s response: %s' % (typ, dat))
|
||||
if __debug__:
|
||||
if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
|
||||
_mesg('%s response: %s' % (typ, dat))
|
||||
|
||||
return resp
|
||||
|
||||
|
@ -739,8 +765,11 @@ class IMAP4:
|
|||
# Protocol mandates all lines terminated by CRLF
|
||||
|
||||
line = line[:-2]
|
||||
if __debug__ and self.debug >= 4:
|
||||
_mesg('< %s' % line)
|
||||
if __debug__:
|
||||
if self.debug >= 4:
|
||||
_mesg('< %s' % line)
|
||||
else:
|
||||
_log('< %s' % line)
|
||||
return line
|
||||
|
||||
|
||||
|
@ -750,8 +779,9 @@ class IMAP4:
|
|||
# Save result, return success.
|
||||
|
||||
self.mo = cre.match(s)
|
||||
if __debug__ and self.mo is not None and self.debug >= 5:
|
||||
_mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
|
||||
if __debug__:
|
||||
if self.mo is not None and self.debug >= 5:
|
||||
_mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
|
||||
return self.mo is not None
|
||||
|
||||
|
||||
|
@ -763,6 +793,28 @@ class IMAP4:
|
|||
return tag
|
||||
|
||||
|
||||
def _checkquote(self, arg):
|
||||
|
||||
# Must quote command args if non-alphanumeric chars present,
|
||||
# and not already quoted.
|
||||
|
||||
if type(arg) is not type(''):
|
||||
return arg
|
||||
if (arg[0],arg[-1]) in (('(',')'),('"','"')):
|
||||
return arg
|
||||
if self.mustquote.search(arg) is None:
|
||||
return arg
|
||||
return self._quote(arg)
|
||||
|
||||
|
||||
def _quote(self, arg):
|
||||
|
||||
arg = string.replace(arg, '\\', '\\\\')
|
||||
arg = string.replace(arg, '"', '\\"')
|
||||
|
||||
return '"%s"' % arg
|
||||
|
||||
|
||||
def _simple_command(self, name, *args):
|
||||
|
||||
return self._command_complete(name, apply(self._command, (name,) + args))
|
||||
|
@ -775,8 +827,9 @@ class IMAP4:
|
|||
if not self.untagged_responses.has_key(name):
|
||||
return typ, [None]
|
||||
data = self.untagged_responses[name]
|
||||
if __debug__ and self.debug >= 5:
|
||||
_mesg('untagged_responses[%s] => %s' % (name, data))
|
||||
if __debug__:
|
||||
if self.debug >= 5:
|
||||
_mesg('untagged_responses[%s] => %s' % (name, data))
|
||||
del self.untagged_responses[name]
|
||||
return typ, data
|
||||
|
||||
|
@ -901,7 +954,7 @@ def Time2Internaldate(date_time):
|
|||
"""
|
||||
|
||||
dttype = type(date_time)
|
||||
if dttype is type(1):
|
||||
if dttype is type(1) or dttype is type(1.1):
|
||||
tt = time.localtime(date_time)
|
||||
elif dttype is type(()):
|
||||
tt = date_time
|
||||
|
@ -922,9 +975,11 @@ def Time2Internaldate(date_time):
|
|||
|
||||
if __debug__:
|
||||
|
||||
def _mesg(s):
|
||||
# if len(s) > 70: s = '%.70s..' % s
|
||||
sys.stderr.write('\t'+s+'\n')
|
||||
def _mesg(s, secs=None):
|
||||
if secs is None:
|
||||
secs = time.time()
|
||||
tm = time.strftime('%M:%S', time.localtime(secs))
|
||||
sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
|
||||
sys.stderr.flush()
|
||||
|
||||
def _dump_ur(dict):
|
||||
|
@ -936,9 +991,23 @@ if __debug__:
|
|||
l = map(lambda x,j=j:'%s: "%s"' % (x[0], x[1][0] and j(x[1], '" "') or ''), l)
|
||||
_mesg('untagged responses dump:%s%s' % (t, j(l, t)))
|
||||
|
||||
_cmd_log = [] # Last `_cmd_log_len' interactions
|
||||
_cmd_log_len = 10
|
||||
|
||||
def _log(line):
|
||||
# Keep log of last `_cmd_log_len' interactions for debugging.
|
||||
if len(_cmd_log) == _cmd_log_len:
|
||||
del _cmd_log[0]
|
||||
_cmd_log.append((time.time(), line))
|
||||
|
||||
def print_log():
|
||||
_mesg('last %d IMAP4 interactions:' % len(_cmd_log))
|
||||
for secs,line in _cmd_log:
|
||||
_mesg(line, secs)
|
||||
|
||||
|
||||
if __debug__ and __name__ == '__main__':
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import getpass, sys
|
||||
|
||||
|
@ -954,6 +1023,7 @@ if __debug__ and __name__ == '__main__':
|
|||
('rename', ('/tmp/xxx 1', '/tmp/yyy')),
|
||||
('CREATE', ('/tmp/yyz 2',)),
|
||||
('append', ('/tmp/yyz 2', None, None, 'From: anon@x.y.z\n\ndata...')),
|
||||
('list', ('/tmp', 'yy*')),
|
||||
('select', ('/tmp/yyz 2',)),
|
||||
('search', (None, '(TO zork)')),
|
||||
('partial', ('1', 'RFC822', 1, 1024)),
|
||||
|
@ -968,13 +1038,15 @@ if __debug__ and __name__ == '__main__':
|
|||
('response',('UIDVALIDITY',)),
|
||||
('uid', ('SEARCH', 'ALL')),
|
||||
('response', ('EXISTS',)),
|
||||
('append', (None, None, None, 'From: anon@x.y.z\n\ndata...')),
|
||||
('recent', ()),
|
||||
('logout', ()),
|
||||
)
|
||||
|
||||
def run(cmd, args):
|
||||
_mesg('%s %s' % (cmd, args))
|
||||
typ, dat = apply(eval('M.%s' % cmd), args)
|
||||
_mesg(' %s %s\n => %s %s' % (cmd, args, typ, dat))
|
||||
_mesg('%s => %s %s' % (cmd, typ, dat))
|
||||
return dat
|
||||
|
||||
Debug = 5
|
||||
|
@ -996,6 +1068,7 @@ if __debug__ and __name__ == '__main__':
|
|||
if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
|
||||
continue
|
||||
|
||||
uid = string.split(dat[-1])[-1]
|
||||
run('uid', ('FETCH', '%s' % uid,
|
||||
uid = string.split(dat[-1])
|
||||
if not uid: continue
|
||||
run('uid', ('FETCH', '%s' % uid[-1],
|
||||
'(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue