mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +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
|
Time2Internaldate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "2.15"
|
__version__ = "2.16"
|
||||||
|
|
||||||
import binascii, re, socket, string, time, random, sys
|
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
|
AUTHENTICATE, and the last argument to APPEND which is passed as
|
||||||
an IMAP4 literal. If necessary (the string contains
|
an IMAP4 literal. If necessary (the string contains
|
||||||
white-space and isn't enclosed with either parentheses or
|
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'
|
Each command returns a tuple: (type, [data, ...]) where 'type'
|
||||||
is usually 'OK' or 'NO', and 'data' is either the text from the
|
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
|
from READ-WRITE to READ-ONLY raise the exception class
|
||||||
<instance>.readonly("<reason>"), which is a sub-class of 'abort'.
|
<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
|
Note: to use this module, you must read the RFCs pertaining
|
||||||
to the IMAP4 protocol, as the semantics of the arguments to
|
to the IMAP4 protocol, as the semantics of the arguments to
|
||||||
each IMAP4 command are left to the invoker, not to mention
|
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 abort(error): pass # Service errors - close and retry
|
||||||
class readonly(abort): pass # Mailbox status changed to READ-ONLY
|
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):
|
def __init__(self, host = '', port = IMAP4_PORT):
|
||||||
self.host = host
|
self.host = host
|
||||||
|
@ -138,15 +145,15 @@ class IMAP4:
|
||||||
# Get server welcome message,
|
# Get server welcome message,
|
||||||
# request and store CAPABILITY response.
|
# request and store CAPABILITY response.
|
||||||
|
|
||||||
if __debug__ and self.debug >= 1:
|
if __debug__:
|
||||||
_mesg('new IMAP4 connection, tag=%s' % self.tagpre)
|
if self.debug >= 1:
|
||||||
|
_mesg('new IMAP4 connection, tag=%s' % self.tagpre)
|
||||||
|
|
||||||
self.welcome = self._get_response()
|
self.welcome = self._get_response()
|
||||||
if self.untagged_responses.has_key('PREAUTH'):
|
if self.untagged_responses.has_key('PREAUTH'):
|
||||||
self.state = 'AUTH'
|
self.state = 'AUTH'
|
||||||
elif self.untagged_responses.has_key('OK'):
|
elif self.untagged_responses.has_key('OK'):
|
||||||
self.state = 'NONAUTH'
|
self.state = 'NONAUTH'
|
||||||
# elif self.untagged_responses.has_key('BYE'):
|
|
||||||
else:
|
else:
|
||||||
raise self.error(self.welcome)
|
raise self.error(self.welcome)
|
||||||
|
|
||||||
|
@ -156,8 +163,9 @@ class IMAP4:
|
||||||
raise self.error('no CAPABILITY response from server')
|
raise self.error('no CAPABILITY response from server')
|
||||||
self.capabilities = tuple(string.split(string.upper(self.untagged_responses[cap][-1])))
|
self.capabilities = tuple(string.split(string.upper(self.untagged_responses[cap][-1])))
|
||||||
|
|
||||||
if __debug__ and self.debug >= 3:
|
if __debug__:
|
||||||
_mesg('CAPABILITIES: %s' % `self.capabilities`)
|
if self.debug >= 3:
|
||||||
|
_mesg('CAPABILITIES: %s' % `self.capabilities`)
|
||||||
|
|
||||||
for version in AllowedVersions:
|
for version in AllowedVersions:
|
||||||
if not version in self.capabilities:
|
if not version in self.capabilities:
|
||||||
|
@ -229,8 +237,12 @@ class IMAP4:
|
||||||
"""Append message to named mailbox.
|
"""Append message to named mailbox.
|
||||||
|
|
||||||
(typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
|
(typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
|
||||||
|
|
||||||
|
All args except `message' can be None.
|
||||||
"""
|
"""
|
||||||
name = 'APPEND'
|
name = 'APPEND'
|
||||||
|
if not mailbox:
|
||||||
|
mailbox = 'INBOX'
|
||||||
if flags:
|
if flags:
|
||||||
if (flags[0],flags[-1]) != ('(',')'):
|
if (flags[0],flags[-1]) != ('(',')'):
|
||||||
flags = '(%s)' % flags
|
flags = '(%s)' % flags
|
||||||
|
@ -360,9 +372,13 @@ class IMAP4:
|
||||||
def login(self, user, password):
|
def login(self, user, password):
|
||||||
"""Identify client using plaintext 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':
|
if typ != 'OK':
|
||||||
raise self.error(dat[-1])
|
raise self.error(dat[-1])
|
||||||
self.state = 'AUTH'
|
self.state = 'AUTH'
|
||||||
|
@ -403,8 +419,9 @@ class IMAP4:
|
||||||
|
|
||||||
(typ, data) = <instance>.noop()
|
(typ, data) = <instance>.noop()
|
||||||
"""
|
"""
|
||||||
if __debug__ and self.debug >= 3:
|
if __debug__:
|
||||||
_dump_ur(self.untagged_responses)
|
if self.debug >= 3:
|
||||||
|
_dump_ur(self.untagged_responses)
|
||||||
return self._simple_command('NOOP')
|
return self._simple_command('NOOP')
|
||||||
|
|
||||||
|
|
||||||
|
@ -464,7 +481,9 @@ class IMAP4:
|
||||||
self.state = 'SELECTED'
|
self.state = 'SELECTED'
|
||||||
if not self.untagged_responses.has_key('READ-WRITE') \
|
if not self.untagged_responses.has_key('READ-WRITE') \
|
||||||
and not readonly:
|
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)
|
raise self.readonly('%s is not writable' % mailbox)
|
||||||
return typ, self.untagged_responses.get('EXISTS', [None])
|
return typ, self.untagged_responses.get('EXISTS', [None])
|
||||||
|
|
||||||
|
@ -546,16 +565,24 @@ class IMAP4:
|
||||||
|
|
||||||
def _append_untagged(self, typ, dat):
|
def _append_untagged(self, typ, dat):
|
||||||
|
|
||||||
|
if dat is None: dat = ''
|
||||||
ur = self.untagged_responses
|
ur = self.untagged_responses
|
||||||
if __debug__ and self.debug >= 5:
|
if __debug__:
|
||||||
_mesg('untagged_responses[%s] %s += %s' %
|
if self.debug >= 5:
|
||||||
(typ, len(ur.get(typ,'')), dat))
|
_mesg('untagged_responses[%s] %s += ["%s"]' %
|
||||||
|
(typ, len(ur.get(typ,'')), dat))
|
||||||
if ur.has_key(typ):
|
if ur.has_key(typ):
|
||||||
ur[typ].append(dat)
|
ur[typ].append(dat)
|
||||||
else:
|
else:
|
||||||
ur[typ] = [dat]
|
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):
|
def _command(self, name, *args):
|
||||||
|
|
||||||
if self.state not in Commands[name]:
|
if self.state not in Commands[name]:
|
||||||
|
@ -574,16 +601,9 @@ class IMAP4:
|
||||||
|
|
||||||
tag = self._new_tag()
|
tag = self._new_tag()
|
||||||
data = '%s %s' % (tag, name)
|
data = '%s %s' % (tag, name)
|
||||||
for d in args:
|
for arg in args:
|
||||||
if d is None: continue
|
if arg is None: continue
|
||||||
if type(d) is type(''):
|
data = '%s %s' % (data, self._checkquote(arg))
|
||||||
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)
|
|
||||||
|
|
||||||
literal = self.literal
|
literal = self.literal
|
||||||
if literal is not None:
|
if literal is not None:
|
||||||
|
@ -594,14 +614,17 @@ class IMAP4:
|
||||||
literator = None
|
literator = None
|
||||||
data = '%s {%s}' % (data, len(literal))
|
data = '%s {%s}' % (data, len(literal))
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
if self.debug >= 4:
|
||||||
|
_mesg('> %s' % data)
|
||||||
|
else:
|
||||||
|
_log('> %s' % data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.sock.send('%s%s' % (data, CRLF))
|
self.sock.send('%s%s' % (data, CRLF))
|
||||||
except socket.error, val:
|
except socket.error, val:
|
||||||
raise self.abort('socket error: %s' % val)
|
raise self.abort('socket error: %s' % val)
|
||||||
|
|
||||||
if __debug__ and self.debug >= 4:
|
|
||||||
_mesg('> %s' % data)
|
|
||||||
|
|
||||||
if literal is None:
|
if literal is None:
|
||||||
return tag
|
return tag
|
||||||
|
|
||||||
|
@ -617,8 +640,9 @@ class IMAP4:
|
||||||
if literator:
|
if literator:
|
||||||
literal = literator(self.continuation_response)
|
literal = literator(self.continuation_response)
|
||||||
|
|
||||||
if __debug__ and self.debug >= 4:
|
if __debug__:
|
||||||
_mesg('write literal size %s' % len(literal))
|
if self.debug >= 4:
|
||||||
|
_mesg('write literal size %s' % len(literal))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.sock.send(literal)
|
self.sock.send(literal)
|
||||||
|
@ -633,14 +657,14 @@ class IMAP4:
|
||||||
|
|
||||||
|
|
||||||
def _command_complete(self, name, tag):
|
def _command_complete(self, name, tag):
|
||||||
|
self._check_bye()
|
||||||
try:
|
try:
|
||||||
typ, data = self._get_tagged_response(tag)
|
typ, data = self._get_tagged_response(tag)
|
||||||
except self.abort, val:
|
except self.abort, val:
|
||||||
raise self.abort('command: %s => %s' % (name, val))
|
raise self.abort('command: %s => %s' % (name, val))
|
||||||
except self.error, val:
|
except self.error, val:
|
||||||
raise self.error('command: %s => %s' % (name, val))
|
raise self.error('command: %s => %s' % (name, val))
|
||||||
if self.untagged_responses.has_key('BYE') and name != 'LOGOUT':
|
self._check_bye()
|
||||||
raise self.abort(self.untagged_responses['BYE'][-1])
|
|
||||||
if typ == 'BAD':
|
if typ == 'BAD':
|
||||||
raise self.error('%s command error: %s %s' % (name, typ, data))
|
raise self.error('%s command error: %s %s' % (name, typ, data))
|
||||||
return typ, data
|
return typ, data
|
||||||
|
@ -695,8 +719,9 @@ class IMAP4:
|
||||||
# Read literal direct from connection.
|
# Read literal direct from connection.
|
||||||
|
|
||||||
size = string.atoi(self.mo.group('size'))
|
size = string.atoi(self.mo.group('size'))
|
||||||
if __debug__ and self.debug >= 4:
|
if __debug__:
|
||||||
_mesg('read literal size %s' % size)
|
if self.debug >= 4:
|
||||||
|
_mesg('read literal size %s' % size)
|
||||||
data = self.file.read(size)
|
data = self.file.read(size)
|
||||||
|
|
||||||
# Store response with literal as tuple
|
# Store response with literal as tuple
|
||||||
|
@ -714,8 +739,9 @@ class IMAP4:
|
||||||
if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
|
if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
|
||||||
self._append_untagged(self.mo.group('type'), self.mo.group('data'))
|
self._append_untagged(self.mo.group('type'), self.mo.group('data'))
|
||||||
|
|
||||||
if __debug__ and self.debug >= 1 and typ in ('NO', 'BAD'):
|
if __debug__:
|
||||||
_mesg('%s response: %s' % (typ, dat))
|
if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
|
||||||
|
_mesg('%s response: %s' % (typ, dat))
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
@ -739,8 +765,11 @@ class IMAP4:
|
||||||
# Protocol mandates all lines terminated by CRLF
|
# Protocol mandates all lines terminated by CRLF
|
||||||
|
|
||||||
line = line[:-2]
|
line = line[:-2]
|
||||||
if __debug__ and self.debug >= 4:
|
if __debug__:
|
||||||
_mesg('< %s' % line)
|
if self.debug >= 4:
|
||||||
|
_mesg('< %s' % line)
|
||||||
|
else:
|
||||||
|
_log('< %s' % line)
|
||||||
return line
|
return line
|
||||||
|
|
||||||
|
|
||||||
|
@ -750,8 +779,9 @@ class IMAP4:
|
||||||
# Save result, return success.
|
# Save result, return success.
|
||||||
|
|
||||||
self.mo = cre.match(s)
|
self.mo = cre.match(s)
|
||||||
if __debug__ and self.mo is not None and self.debug >= 5:
|
if __debug__:
|
||||||
_mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
|
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
|
return self.mo is not None
|
||||||
|
|
||||||
|
|
||||||
|
@ -763,6 +793,28 @@ class IMAP4:
|
||||||
return tag
|
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):
|
def _simple_command(self, name, *args):
|
||||||
|
|
||||||
return self._command_complete(name, apply(self._command, (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):
|
if not self.untagged_responses.has_key(name):
|
||||||
return typ, [None]
|
return typ, [None]
|
||||||
data = self.untagged_responses[name]
|
data = self.untagged_responses[name]
|
||||||
if __debug__ and self.debug >= 5:
|
if __debug__:
|
||||||
_mesg('untagged_responses[%s] => %s' % (name, data))
|
if self.debug >= 5:
|
||||||
|
_mesg('untagged_responses[%s] => %s' % (name, data))
|
||||||
del self.untagged_responses[name]
|
del self.untagged_responses[name]
|
||||||
return typ, data
|
return typ, data
|
||||||
|
|
||||||
|
@ -901,7 +954,7 @@ def Time2Internaldate(date_time):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dttype = type(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)
|
tt = time.localtime(date_time)
|
||||||
elif dttype is type(()):
|
elif dttype is type(()):
|
||||||
tt = date_time
|
tt = date_time
|
||||||
|
@ -922,9 +975,11 @@ def Time2Internaldate(date_time):
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
|
|
||||||
def _mesg(s):
|
def _mesg(s, secs=None):
|
||||||
# if len(s) > 70: s = '%.70s..' % s
|
if secs is None:
|
||||||
sys.stderr.write('\t'+s+'\n')
|
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()
|
sys.stderr.flush()
|
||||||
|
|
||||||
def _dump_ur(dict):
|
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)
|
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)))
|
_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
|
import getpass, sys
|
||||||
|
|
||||||
|
@ -954,6 +1023,7 @@ if __debug__ and __name__ == '__main__':
|
||||||
('rename', ('/tmp/xxx 1', '/tmp/yyy')),
|
('rename', ('/tmp/xxx 1', '/tmp/yyy')),
|
||||||
('CREATE', ('/tmp/yyz 2',)),
|
('CREATE', ('/tmp/yyz 2',)),
|
||||||
('append', ('/tmp/yyz 2', None, None, 'From: anon@x.y.z\n\ndata...')),
|
('append', ('/tmp/yyz 2', None, None, 'From: anon@x.y.z\n\ndata...')),
|
||||||
|
('list', ('/tmp', 'yy*')),
|
||||||
('select', ('/tmp/yyz 2',)),
|
('select', ('/tmp/yyz 2',)),
|
||||||
('search', (None, '(TO zork)')),
|
('search', (None, '(TO zork)')),
|
||||||
('partial', ('1', 'RFC822', 1, 1024)),
|
('partial', ('1', 'RFC822', 1, 1024)),
|
||||||
|
@ -968,13 +1038,15 @@ if __debug__ and __name__ == '__main__':
|
||||||
('response',('UIDVALIDITY',)),
|
('response',('UIDVALIDITY',)),
|
||||||
('uid', ('SEARCH', 'ALL')),
|
('uid', ('SEARCH', 'ALL')),
|
||||||
('response', ('EXISTS',)),
|
('response', ('EXISTS',)),
|
||||||
|
('append', (None, None, None, 'From: anon@x.y.z\n\ndata...')),
|
||||||
('recent', ()),
|
('recent', ()),
|
||||||
('logout', ()),
|
('logout', ()),
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(cmd, args):
|
def run(cmd, args):
|
||||||
|
_mesg('%s %s' % (cmd, args))
|
||||||
typ, dat = apply(eval('M.%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
|
return dat
|
||||||
|
|
||||||
Debug = 5
|
Debug = 5
|
||||||
|
@ -996,6 +1068,7 @@ if __debug__ and __name__ == '__main__':
|
||||||
if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
|
if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
uid = string.split(dat[-1])[-1]
|
uid = string.split(dat[-1])
|
||||||
run('uid', ('FETCH', '%s' % uid,
|
if not uid: continue
|
||||||
|
run('uid', ('FETCH', '%s' % uid[-1],
|
||||||
'(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
|
'(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue