blob: a218ab0716e1a019c235da12d9a00483f0fc6e5d [file] [log] [blame]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001"""IMAP4 client.
2
3Based on RFC 2060.
4
Tim Peters07e99cb2001-01-14 23:47:14 +00005Public class: IMAP4
6Public variable: Debug
7Public functions: Internaldate2tuple
8 Int2AP
9 ParseFlags
10 Time2Internaldate
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000011"""
Guido van Rossumb1f08121998-06-25 02:22:16 +000012
Guido van Rossum98d9fd32000-02-28 15:12:25 +000013# Author: Piers Lauder <piers@cs.su.oz.au> December 1997.
Tim Peters07e99cb2001-01-14 23:47:14 +000014#
Guido van Rossum98d9fd32000-02-28 15:12:25 +000015# Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998.
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +000016# String method conversion by ESR, February 2001.
Piers Lauder15e5d532001-07-20 10:52:06 +000017# GET/SETACL contributed by Anthony Baxter <anthony@interlink.com.au> April 2001.
Piers Laudera4f83132002-03-08 01:53:24 +000018# IMAP4_SSL contributed by Tino Lange <Tino.Lange@isg.de> March 2002.
Piers Lauder3fca2912002-06-17 07:07:20 +000019# GET/SETQUOTA contributed by Andreas Zeidler <az@kreativkombinat.de> June 2002.
Piers Laudere0273de2002-11-22 05:53:04 +000020# PROXYAUTH contributed by Rick Holbert <holbert.13@osu.edu> November 2002.
Piers Lauderd80ef022005-06-01 23:50:52 +000021# GET/SETANNOTATION contributed by Tomas Lindroos <skitta@abo.fi> June 2005.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000022
Piers Lauderbe5615e2005-08-31 10:50:03 +000023__version__ = "2.58"
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000024
Alexander Belopolsky2420d832012-04-29 15:56:49 -040025import binascii, errno, random, re, socket, subprocess, sys, time, calendar
Alexander Belopolsky8141cc72012-06-22 21:03:39 -040026from datetime import datetime, timezone, timedelta
R David Murrayfcb6d6a2013-03-19 13:52:33 -040027from io import DEFAULT_BUFFER_SIZE
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000028
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000029try:
30 import ssl
31 HAVE_SSL = True
Brett Cannoncd171c82013-07-04 17:43:24 -040032except ImportError:
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000033 HAVE_SSL = False
34
Thomas Wouters47b49bf2007-08-30 22:15:33 +000035__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000036 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000037
Tim Peters07e99cb2001-01-14 23:47:14 +000038# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000039
Christian Heimesfb5faf02008-11-05 19:39:50 +000040CRLF = b'\r\n'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000041Debug = 0
42IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000043IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000044AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000045
Georg Brandlca580f42013-10-27 06:52:14 +010046# Maximal line length when calling readline(). This is to prevent
47# reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1)
R David Murray936da2a2015-03-22 16:17:46 -040048# don't specify a line length. RFC 2683 suggests limiting client
49# command lines to 1000 octets and that servers should be prepared
50# to accept command lines up to 8000 octets, so we used to use 10K here.
51# In the modern world (eg: gmail) the response to, for example, a
52# search command can be quite large, so we now use 1M.
53_MAXLINE = 1000000
Georg Brandlca580f42013-10-27 06:52:14 +010054
55
Tim Peters07e99cb2001-01-14 23:47:14 +000056# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000057
58Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000059 # name valid states
60 'APPEND': ('AUTH', 'SELECTED'),
61 'AUTHENTICATE': ('NONAUTH',),
62 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
63 'CHECK': ('SELECTED',),
64 'CLOSE': ('SELECTED',),
65 'COPY': ('SELECTED',),
66 'CREATE': ('AUTH', 'SELECTED'),
67 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000068 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000069 'EXAMINE': ('AUTH', 'SELECTED'),
70 'EXPUNGE': ('SELECTED',),
71 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000072 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000073 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000074 'GETQUOTA': ('AUTH', 'SELECTED'),
75 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000076 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000077 'LIST': ('AUTH', 'SELECTED'),
78 'LOGIN': ('NONAUTH',),
79 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
80 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000081 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000082 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000083 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000084 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000085 'RENAME': ('AUTH', 'SELECTED'),
86 'SEARCH': ('SELECTED',),
87 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000088 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000089 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000090 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000091 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000092 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000093 'STATUS': ('AUTH', 'SELECTED'),
94 'STORE': ('SELECTED',),
95 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000096 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000097 'UID': ('SELECTED',),
98 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
99 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000100
Tim Peters07e99cb2001-01-14 23:47:14 +0000101# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102
Christian Heimesfb5faf02008-11-05 19:39:50 +0000103Continuation = re.compile(br'\+( (?P<data>.*))?')
104Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
105InternalDate = re.compile(br'.*INTERNALDATE "'
106 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
107 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
108 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
109 br'"')
110Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
111MapCRLF = re.compile(br'\r\n|\r|\n')
112Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
113Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +0000114Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000115 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000116
117
118
119class IMAP4:
120
Tim Peters07e99cb2001-01-14 23:47:14 +0000121 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000122
Tim Peters07e99cb2001-01-14 23:47:14 +0000123 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000124
Tim Peters07e99cb2001-01-14 23:47:14 +0000125 host - host's name (default: localhost);
126 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000127
Tim Peters07e99cb2001-01-14 23:47:14 +0000128 All IMAP4rev1 commands are supported by methods of the same
129 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000130
Tim Peters07e99cb2001-01-14 23:47:14 +0000131 All arguments to commands are converted to strings, except for
132 AUTHENTICATE, and the last argument to APPEND which is passed as
133 an IMAP4 literal. If necessary (the string contains any
134 non-printing characters or white-space and isn't enclosed with
135 either parentheses or double quotes) each string is quoted.
136 However, the 'password' argument to the LOGIN command is always
137 quoted. If you want to avoid having an argument string quoted
138 (eg: the 'flags' argument to STORE) then enclose the string in
139 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000140
Tim Peters07e99cb2001-01-14 23:47:14 +0000141 Each command returns a tuple: (type, [data, ...]) where 'type'
142 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000143 tagged response, or untagged results from command. Each 'data'
144 is either a string, or a tuple. If a tuple, then the first part
145 is the header of the response, and the second part contains
146 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000147
Tim Peters07e99cb2001-01-14 23:47:14 +0000148 Errors raise the exception class <instance>.error("<reason>").
149 IMAP4 server errors raise <instance>.abort("<reason>"),
150 which is a sub-class of 'error'. Mailbox status changes
151 from READ-WRITE to READ-ONLY raise the exception class
152 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 "error" exceptions imply a program error.
155 "abort" exceptions imply the connection should be reset, and
156 the command re-tried.
157 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000158
Piers Lauderd80ef022005-06-01 23:50:52 +0000159 Note: to use this module, you must read the RFCs pertaining to the
160 IMAP4 protocol, as the semantics of the arguments to each IMAP4
161 command are left to the invoker, not to mention the results. Also,
162 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000164
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 class error(Exception): pass # Logical errors - debug required
166 class abort(error): pass # Service errors - close and retry
167 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000168
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 self.debug = Debug
171 self.state = 'LOGOUT'
172 self.literal = None # A literal argument to a command
173 self.tagged_commands = {} # Tagged commands awaiting response
174 self.untagged_responses = {} # {typ: [data, ...], ...}
175 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000176 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000177 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000178 self._tls_established = False
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000179
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000181
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
Victor Stinner33e649c2011-01-05 23:01:37 +0000184 try:
185 self._connect()
186 except Exception:
187 try:
188 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200189 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000190 pass
191 raise
192
193
194 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 # Create unique tag for this session,
196 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000197
Piers Lauder2dfc1682005-07-05 04:20:07 +0000198 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000199 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000200 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000201 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000202
Tim Peters07e99cb2001-01-14 23:47:14 +0000203 # Get server welcome message,
204 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000205
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000207 self._cmd_log_len = 10
208 self._cmd_log_idx = 0
209 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000211 self._mesg('imaplib version %s' % __version__)
212 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000213
Tim Peters07e99cb2001-01-14 23:47:14 +0000214 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000215 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000216 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000217 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000218 self.state = 'NONAUTH'
219 else:
220 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000221
Antoine Pitroudbe75192010-11-16 17:55:26 +0000222 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 if __debug__:
224 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000225 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000226
Tim Peters07e99cb2001-01-14 23:47:14 +0000227 for version in AllowedVersions:
228 if not version in self.capabilities:
229 continue
230 self.PROTOCOL_VERSION = version
231 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000232
Tim Peters07e99cb2001-01-14 23:47:14 +0000233 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000234
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000235
Tim Peters07e99cb2001-01-14 23:47:14 +0000236 def __getattr__(self, attr):
237 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000238 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000239 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000240 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000241
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300242 def __enter__(self):
243 return self
244
245 def __exit__(self, *args):
246 try:
247 self.logout()
248 except OSError:
249 pass
Guido van Rossum26367a01998-09-28 15:34:46 +0000250
251
Piers Lauder15e5d532001-07-20 10:52:06 +0000252 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000253
254
Christian Heimesfb5faf02008-11-05 19:39:50 +0000255 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000256 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000257
Piers Lauderf97b2d72002-06-05 22:31:57 +0000258 def open(self, host = '', port = IMAP4_PORT):
259 """Setup connection to remote server on "host:port"
260 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000261 This connection will be used by the routines:
262 read, readline, send, shutdown.
263 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000264 self.host = host
265 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000266 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000267 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000268
269
Piers Lauder15e5d532001-07-20 10:52:06 +0000270 def read(self, size):
271 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200272 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000273
274
275 def readline(self):
276 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100277 line = self.file.readline(_MAXLINE + 1)
278 if len(line) > _MAXLINE:
279 raise self.error("got more than %d bytes" % _MAXLINE)
280 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000281
282
283 def send(self, data):
284 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000285 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000286
Piers Lauderf2d7d152002-02-22 01:15:17 +0000287
Piers Lauder15e5d532001-07-20 10:52:06 +0000288 def shutdown(self):
289 """Close I/O established in "open"."""
290 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000291 try:
292 self.sock.shutdown(socket.SHUT_RDWR)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200293 except OSError as e:
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000294 # The server might already have closed the connection
295 if e.errno != errno.ENOTCONN:
296 raise
297 finally:
298 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000299
300
301 def socket(self):
302 """Return socket instance used to connect to IMAP4 server.
303
304 socket = <instance>.socket()
305 """
306 return self.sock
307
308
309
310 # Utility methods
311
312
Tim Peters07e99cb2001-01-14 23:47:14 +0000313 def recent(self):
314 """Return most recent 'RECENT' responses if any exist,
315 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000316
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000318
Tim Peters07e99cb2001-01-14 23:47:14 +0000319 'data' is None if no new messages,
320 else list of RECENT responses, most recent last.
321 """
322 name = 'RECENT'
323 typ, dat = self._untagged_response('OK', [None], name)
324 if dat[-1]:
325 return typ, dat
326 typ, dat = self.noop() # Prod server for response
327 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000328
329
Tim Peters07e99cb2001-01-14 23:47:14 +0000330 def response(self, code):
331 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000332
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000334
Tim Peters07e99cb2001-01-14 23:47:14 +0000335 (code, [data]) = <instance>.response(code)
336 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000337 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000338
339
Guido van Rossum26367a01998-09-28 15:34:46 +0000340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000342
343
Tim Peters07e99cb2001-01-14 23:47:14 +0000344 def append(self, mailbox, flags, date_time, message):
345 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000346
Tim Peters07e99cb2001-01-14 23:47:14 +0000347 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000348
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 All args except `message' can be None.
350 """
351 name = 'APPEND'
352 if not mailbox:
353 mailbox = 'INBOX'
354 if flags:
355 if (flags[0],flags[-1]) != ('(',')'):
356 flags = '(%s)' % flags
357 else:
358 flags = None
359 if date_time:
360 date_time = Time2Internaldate(date_time)
361 else:
362 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000363 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000364 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000365
366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 def authenticate(self, mechanism, authobject):
368 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000369
Tim Peters07e99cb2001-01-14 23:47:14 +0000370 'mechanism' specifies which authentication mechanism is to
371 be used - it must appear in <instance>.capabilities in the
372 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000375
Tim Peters07e99cb2001-01-14 23:47:14 +0000376 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000377
R David Murray774a39f2013-02-19 12:17:31 -0500378 It will be called to process server continuation responses; the
379 response argument it is passed will be a bytes. It should return bytes
380 data that will be base64 encoded and sent to the server. It should
381 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000383 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000384 # XXX: shouldn't this code be removed, not commented out?
385 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000386 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000387 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 self.literal = _Authenticator(authobject).process
389 typ, dat = self._simple_command('AUTHENTICATE', mech)
390 if typ != 'OK':
391 raise self.error(dat[-1])
392 self.state = 'AUTH'
393 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000394
395
Piers Lauderd80ef022005-06-01 23:50:52 +0000396 def capability(self):
397 """(typ, [data]) = <instance>.capability()
398 Fetch capabilities list from server."""
399
400 name = 'CAPABILITY'
401 typ, dat = self._simple_command(name)
402 return self._untagged_response(typ, dat, name)
403
404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 def check(self):
406 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 (typ, [data]) = <instance>.check()
409 """
410 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 def close(self):
414 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 Deleted messages are removed from writable mailbox.
417 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 (typ, [data]) = <instance>.close()
420 """
421 try:
422 typ, dat = self._simple_command('CLOSE')
423 finally:
424 self.state = 'AUTH'
425 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000426
427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 def copy(self, message_set, new_mailbox):
429 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
432 """
433 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000434
435
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 def create(self, mailbox):
437 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000438
Tim Peters07e99cb2001-01-14 23:47:14 +0000439 (typ, [data]) = <instance>.create(mailbox)
440 """
441 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 def delete(self, mailbox):
445 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 (typ, [data]) = <instance>.delete(mailbox)
448 """
449 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000450
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000451 def deleteacl(self, mailbox, who):
452 """Delete the ACLs (remove any rights) set for who on mailbox.
453
454 (typ, [data]) = <instance>.deleteacl(mailbox, who)
455 """
456 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000457
Tim Peters07e99cb2001-01-14 23:47:14 +0000458 def expunge(self):
459 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000460
Tim Peters07e99cb2001-01-14 23:47:14 +0000461 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000464
Tim Peters07e99cb2001-01-14 23:47:14 +0000465 'data' is list of 'EXPUNGE'd message numbers in order received.
466 """
467 name = 'EXPUNGE'
468 typ, dat = self._simple_command(name)
469 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000470
471
Tim Peters07e99cb2001-01-14 23:47:14 +0000472 def fetch(self, message_set, message_parts):
473 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000474
Tim Peters07e99cb2001-01-14 23:47:14 +0000475 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000476
Tim Peters07e99cb2001-01-14 23:47:14 +0000477 'message_parts' should be a string of selected parts
478 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000479
Tim Peters07e99cb2001-01-14 23:47:14 +0000480 'data' are tuples of message part envelope and data.
481 """
482 name = 'FETCH'
483 typ, dat = self._simple_command(name, message_set, message_parts)
484 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000485
486
Piers Lauder15e5d532001-07-20 10:52:06 +0000487 def getacl(self, mailbox):
488 """Get the ACLs for a mailbox.
489
490 (typ, [data]) = <instance>.getacl(mailbox)
491 """
492 typ, dat = self._simple_command('GETACL', mailbox)
493 return self._untagged_response(typ, dat, 'ACL')
494
495
Piers Lauderd80ef022005-06-01 23:50:52 +0000496 def getannotation(self, mailbox, entry, attribute):
497 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
498 Retrieve ANNOTATIONs."""
499
500 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
501 return self._untagged_response(typ, dat, 'ANNOTATION')
502
503
Piers Lauder3fca2912002-06-17 07:07:20 +0000504 def getquota(self, root):
505 """Get the quota root's resource usage and limits.
506
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000507 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000508
509 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000510 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000511 typ, dat = self._simple_command('GETQUOTA', root)
512 return self._untagged_response(typ, dat, 'QUOTA')
513
514
515 def getquotaroot(self, mailbox):
516 """Get the list of quota roots for the named mailbox.
517
518 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000519 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000520 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000521 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
522 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000523 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000524
525
Tim Peters07e99cb2001-01-14 23:47:14 +0000526 def list(self, directory='""', pattern='*'):
527 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000528
Tim Peters07e99cb2001-01-14 23:47:14 +0000529 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 'data' is list of LIST responses.
532 """
533 name = 'LIST'
534 typ, dat = self._simple_command(name, directory, pattern)
535 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
537
Tim Peters07e99cb2001-01-14 23:47:14 +0000538 def login(self, user, password):
539 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000540
Tim Peters07e99cb2001-01-14 23:47:14 +0000541 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000542
Tim Peters07e99cb2001-01-14 23:47:14 +0000543 NB: 'password' will be quoted.
544 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000545 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
546 if typ != 'OK':
547 raise self.error(dat[-1])
548 self.state = 'AUTH'
549 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000550
551
Piers Laudere0273de2002-11-22 05:53:04 +0000552 def login_cram_md5(self, user, password):
553 """ Force use of CRAM-MD5 authentication.
554
555 (typ, [data]) = <instance>.login_cram_md5(user, password)
556 """
557 self.user, self.password = user, password
558 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
559
560
561 def _CRAM_MD5_AUTH(self, challenge):
562 """ Authobject to use with CRAM-MD5 authentication. """
563 import hmac
R David Murray774a39f2013-02-19 12:17:31 -0500564 pwd = (self.password.encode('ASCII') if isinstance(self.password, str)
565 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100566 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000567
568
Tim Peters07e99cb2001-01-14 23:47:14 +0000569 def logout(self):
570 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000571
Tim Peters07e99cb2001-01-14 23:47:14 +0000572 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000573
Tim Peters07e99cb2001-01-14 23:47:14 +0000574 Returns server 'BYE' response.
575 """
576 self.state = 'LOGOUT'
577 try: typ, dat = self._simple_command('LOGOUT')
578 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000579 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000580 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 return 'BYE', self.untagged_responses['BYE']
582 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000583
584
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 def lsub(self, directory='""', pattern='*'):
586 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000587
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000589
Tim Peters07e99cb2001-01-14 23:47:14 +0000590 'data' are tuples of message part envelope and data.
591 """
592 name = 'LSUB'
593 typ, dat = self._simple_command(name, directory, pattern)
594 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000595
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000596 def myrights(self, mailbox):
597 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
598
599 (typ, [data]) = <instance>.myrights(mailbox)
600 """
601 typ,dat = self._simple_command('MYRIGHTS', mailbox)
602 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000603
Piers Lauder15e5d532001-07-20 10:52:06 +0000604 def namespace(self):
605 """ Returns IMAP namespaces ala rfc2342
606
607 (typ, [data, ...]) = <instance>.namespace()
608 """
609 name = 'NAMESPACE'
610 typ, dat = self._simple_command(name)
611 return self._untagged_response(typ, dat, name)
612
613
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 def noop(self):
615 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000616
Piers Laudere0273de2002-11-22 05:53:04 +0000617 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000618 """
619 if __debug__:
620 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000621 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000622 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000623
624
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 def partial(self, message_num, message_part, start, length):
626 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000627
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000629
Tim Peters07e99cb2001-01-14 23:47:14 +0000630 'data' is tuple of message part envelope and data.
631 """
632 name = 'PARTIAL'
633 typ, dat = self._simple_command(name, message_num, message_part, start, length)
634 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
636
Piers Laudere0273de2002-11-22 05:53:04 +0000637 def proxyauth(self, user):
638 """Assume authentication as "user".
639
640 Allows an authorised administrator to proxy into any user's
641 mailbox.
642
643 (typ, [data]) = <instance>.proxyauth(user)
644 """
645
646 name = 'PROXYAUTH'
647 return self._simple_command('PROXYAUTH', user)
648
649
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 def rename(self, oldmailbox, newmailbox):
651 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000652
Piers Laudere0273de2002-11-22 05:53:04 +0000653 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 """
655 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000656
657
Tim Peters07e99cb2001-01-14 23:47:14 +0000658 def search(self, charset, *criteria):
659 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000660
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000661 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000662
Tim Peters07e99cb2001-01-14 23:47:14 +0000663 'data' is space separated list of matching message numbers.
664 """
665 name = 'SEARCH'
666 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000667 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000668 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000669 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000670 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000671
672
Piers Lauder14f39402005-08-31 10:46:29 +0000673 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000674 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000675
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000677
Piers Lauder14f39402005-08-31 10:46:29 +0000678 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000679
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000681
682 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
683 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000684 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000685 self.untagged_responses = {} # Flush old responses.
686 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000687 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000688 name = 'EXAMINE'
689 else:
690 name = 'SELECT'
691 typ, dat = self._simple_command(name, mailbox)
692 if typ != 'OK':
693 self.state = 'AUTH' # Might have been 'SELECTED'
694 return typ, dat
695 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000696 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000697 and not readonly:
698 if __debug__:
699 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000700 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000701 raise self.readonly('%s is not writable' % mailbox)
702 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000703
704
Piers Lauder15e5d532001-07-20 10:52:06 +0000705 def setacl(self, mailbox, who, what):
706 """Set a mailbox acl.
707
Piers Lauderf167dc32004-03-25 00:12:21 +0000708 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000709 """
710 return self._simple_command('SETACL', mailbox, who, what)
711
712
Piers Lauderd80ef022005-06-01 23:50:52 +0000713 def setannotation(self, *args):
714 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
715 Set ANNOTATIONs."""
716
717 typ, dat = self._simple_command('SETANNOTATION', *args)
718 return self._untagged_response(typ, dat, 'ANNOTATION')
719
720
Piers Lauder3fca2912002-06-17 07:07:20 +0000721 def setquota(self, root, limits):
722 """Set the quota root's resource limits.
723
724 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000725 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000726 typ, dat = self._simple_command('SETQUOTA', root, limits)
727 return self._untagged_response(typ, dat, 'QUOTA')
728
729
Piers Lauder15e5d532001-07-20 10:52:06 +0000730 def sort(self, sort_criteria, charset, *search_criteria):
731 """IMAP4rev1 extension SORT command.
732
733 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
734 """
735 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000736 #if not name in self.capabilities: # Let the server decide!
737 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000738 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000739 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000740 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000741 return self._untagged_response(typ, dat, name)
742
743
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000744 def starttls(self, ssl_context=None):
745 name = 'STARTTLS'
746 if not HAVE_SSL:
747 raise self.error('SSL support missing')
748 if self._tls_established:
749 raise self.abort('TLS session already established')
750 if name not in self.capabilities:
751 raise self.abort('TLS not supported by server')
752 # Generate a default SSL context if none was passed.
753 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100754 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000755 typ, dat = self._simple_command(name)
756 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100757 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600758 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000759 self.file = self.sock.makefile('rb')
760 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000761 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000762 else:
763 raise self.error("Couldn't establish TLS session")
764 return self._untagged_response(typ, dat, name)
765
766
Tim Peters07e99cb2001-01-14 23:47:14 +0000767 def status(self, mailbox, names):
768 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000769
Tim Peters07e99cb2001-01-14 23:47:14 +0000770 (typ, [data]) = <instance>.status(mailbox, names)
771 """
772 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000773 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000774 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 typ, dat = self._simple_command(name, mailbox, names)
776 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000777
778
Tim Peters07e99cb2001-01-14 23:47:14 +0000779 def store(self, message_set, command, flags):
780 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000781
Tim Peters07e99cb2001-01-14 23:47:14 +0000782 (typ, [data]) = <instance>.store(message_set, command, flags)
783 """
784 if (flags[0],flags[-1]) != ('(',')'):
785 flags = '(%s)' % flags # Avoid quoting the flags
786 typ, dat = self._simple_command('STORE', message_set, command, flags)
787 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000788
789
Tim Peters07e99cb2001-01-14 23:47:14 +0000790 def subscribe(self, mailbox):
791 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000792
Tim Peters07e99cb2001-01-14 23:47:14 +0000793 (typ, [data]) = <instance>.subscribe(mailbox)
794 """
795 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000796
797
Martin v. Löwisd8921372003-11-10 06:44:44 +0000798 def thread(self, threading_algorithm, charset, *search_criteria):
799 """IMAPrev1 extension THREAD command.
800
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000801 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000802 """
803 name = 'THREAD'
804 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
805 return self._untagged_response(typ, dat, name)
806
807
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 def uid(self, command, *args):
809 """Execute "command arg ..." with messages identified by UID,
810 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000811
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000813
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 Returns response appropriate to 'command'.
815 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000816 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000817 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000818 raise self.error("Unknown IMAP4 UID command: %s" % command)
819 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000820 raise self.error("command %s illegal in state %s, "
821 "only allowed in states %s" %
822 (command, self.state,
823 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000825 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000826 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000827 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 else:
829 name = 'FETCH'
830 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 def unsubscribe(self, mailbox):
834 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 (typ, [data]) = <instance>.unsubscribe(mailbox)
837 """
838 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000839
840
Tim Peters07e99cb2001-01-14 23:47:14 +0000841 def xatom(self, name, *args):
842 """Allow simple extension commands
843 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000844
Piers Lauder15e5d532001-07-20 10:52:06 +0000845 Assumes command is legal in current state.
846
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000848
849 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000851 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000852 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000853 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000854 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000855 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000856 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000857
858
859
Tim Peters07e99cb2001-01-14 23:47:14 +0000860 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000861
862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000864 if dat is None:
865 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 ur = self.untagged_responses
867 if __debug__:
868 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000869 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000871 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 ur[typ].append(dat)
873 else:
874 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 def _check_bye(self):
878 bye = self.untagged_responses.get('BYE')
879 if bye:
Antoine Pitroudac47912010-11-10 00:18:40 +0000880 raise self.abort(bye[-1].decode('ascii', 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000881
882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 if self.state not in Commands[name]:
886 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000887 raise self.error("command %s illegal in state %s, "
888 "only allowed in states %s" %
889 (name, self.state,
890 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000891
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000893 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000895
Raymond Hettinger54f02222002-06-01 14:18:47 +0000896 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 and not self.is_readonly:
898 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000901 name = bytes(name, 'ASCII')
902 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 for arg in args:
904 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000905 if isinstance(arg, str):
906 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000907 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000908
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 literal = self.literal
910 if literal is not None:
911 self.literal = None
912 if type(literal) is type(self._command):
913 literator = literal
914 else:
915 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000916 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000917
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 if __debug__:
919 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000920 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000922 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000925 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200926 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 if literal is None:
930 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 while 1:
933 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 while self._get_response():
936 if self.tagged_commands[tag]: # BAD/NO?
937 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000938
Tim Peters07e99cb2001-01-14 23:47:14 +0000939 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 if literator:
942 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000943
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 if __debug__:
945 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000946 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000947
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000949 self.send(literal)
950 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200951 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000953
Tim Peters07e99cb2001-01-14 23:47:14 +0000954 if not literator:
955 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000958
959
Tim Peters07e99cb2001-01-14 23:47:14 +0000960 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000961 # BYE is expected after LOGOUT
962 if name != 'LOGOUT':
963 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 try:
965 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000966 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000968 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +0000970 if name != 'LOGOUT':
971 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 if typ == 'BAD':
973 raise self.error('%s command error: %s %s' % (name, typ, data))
974 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000975
976
Antoine Pitroudbe75192010-11-16 17:55:26 +0000977 def _get_capabilities(self):
978 typ, dat = self.capability()
979 if dat == [None]:
980 raise self.error('no CAPABILITY response from server')
981 dat = str(dat[-1], "ASCII")
982 dat = dat.upper()
983 self.capabilities = tuple(dat.split())
984
985
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 # Read response and store.
989 #
990 # Returns None for continuation responses,
991 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 if self._match(self.tagre, resp):
998 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000999 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001001
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001003 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 dat = self.mo.group('data')
1005 self.tagged_commands[tag] = (typ, [dat])
1006 else:
1007 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001008
Tim Peters07e99cb2001-01-14 23:47:14 +00001009 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001010
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 if not self._match(Untagged_response, resp):
1012 if self._match(Untagged_status, resp):
1013 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001014
Tim Peters07e99cb2001-01-14 23:47:14 +00001015 if self.mo is None:
1016 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 if self._match(Continuation, resp):
1019 self.continuation_response = self.mo.group('data')
1020 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001025 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001027 if dat is None: dat = b'' # Null untagged response
1028 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001031
Tim Peters07e99cb2001-01-14 23:47:14 +00001032 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001035
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001036 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 if __debug__:
1038 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001039 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001040 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001041
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001043
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001045
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001053
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001055 typ = self.mo.group('type')
1056 typ = str(typ, "ASCII")
1057 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001058
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 if __debug__:
1060 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001061 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001062
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001064
1065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 while 1:
1069 result = self.tagged_commands[tag]
1070 if result is not None:
1071 del self.tagged_commands[tag]
1072 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001073
R David Murray95ff7232014-02-07 13:44:57 -05001074 # If we've seen a BYE at this point, the socket will be
1075 # closed, so report the BYE now.
1076
1077 self._check_bye()
1078
Tim Peters07e99cb2001-01-14 23:47:14 +00001079 # Some have reported "unexpected response" exceptions.
1080 # Note that ignoring them here causes loops.
1081 # Instead, send me details of the unexpected response and
1082 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001083
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 try:
1085 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001086 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001087 if __debug__:
1088 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001089 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001090 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
1092
Tim Peters07e99cb2001-01-14 23:47:14 +00001093 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001094
Piers Lauder15e5d532001-07-20 10:52:06 +00001095 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 if not line:
1097 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001098
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001100 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001101 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001102
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 line = line[:-2]
1104 if __debug__:
1105 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001106 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001107 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001108 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001110
1111
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001113
Tim Peters07e99cb2001-01-14 23:47:14 +00001114 # Run compiled regular expression match method on 's'.
1115 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001116
Tim Peters07e99cb2001-01-14 23:47:14 +00001117 self.mo = cre.match(s)
1118 if __debug__:
1119 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001120 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001121 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001122
1123
Tim Peters07e99cb2001-01-14 23:47:14 +00001124 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001125
Christian Heimesfb5faf02008-11-05 19:39:50 +00001126 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001127 self.tagnum = self.tagnum + 1
1128 self.tagged_commands[tag] = None
1129 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001130
1131
Tim Peters07e99cb2001-01-14 23:47:14 +00001132 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001133
Antoine Pitroub1436f12010-11-09 22:55:55 +00001134 arg = arg.replace('\\', '\\\\')
1135 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001136
Antoine Pitroub1436f12010-11-09 22:55:55 +00001137 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001138
1139
Tim Peters07e99cb2001-01-14 23:47:14 +00001140 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001141
Guido van Rossum68468eb2003-02-27 20:14:51 +00001142 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001143
1144
Tim Peters07e99cb2001-01-14 23:47:14 +00001145 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001146 if typ == 'NO':
1147 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001148 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001149 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001150 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001151 if __debug__:
1152 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001153 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001154 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001155
1156
Piers Lauderf2d7d152002-02-22 01:15:17 +00001157 if __debug__:
1158
1159 def _mesg(self, s, secs=None):
1160 if secs is None:
1161 secs = time.time()
1162 tm = time.strftime('%M:%S', time.localtime(secs))
1163 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1164 sys.stderr.flush()
1165
1166 def _dump_ur(self, dict):
1167 # Dump untagged responses (in `dict').
1168 l = dict.items()
1169 if not l: return
1170 t = '\n\t\t'
1171 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1172 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1173
1174 def _log(self, line):
1175 # Keep log of last `_cmd_log_len' interactions for debugging.
1176 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1177 self._cmd_log_idx += 1
1178 if self._cmd_log_idx >= self._cmd_log_len:
1179 self._cmd_log_idx = 0
1180
1181 def print_log(self):
1182 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1183 i, n = self._cmd_log_idx, self._cmd_log_len
1184 while n:
1185 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001186 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001187 except:
1188 pass
1189 i += 1
1190 if i >= self._cmd_log_len:
1191 i = 0
1192 n -= 1
1193
1194
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001195if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001196
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001197 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001198
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001199 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001200
Antoine Pitrou08728162011-05-06 18:49:52 +02001201 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001202
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001203 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001204 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001205 keyfile - PEM formatted file that contains your private key (default: None);
1206 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001207 ssl_context - a SSLContext object that contains your certificate chain
1208 and private key (default: None)
1209 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001210 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001211
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001212 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001213 """
Piers Laudera4f83132002-03-08 01:53:24 +00001214
1215
Antoine Pitrou08728162011-05-06 18:49:52 +02001216 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None):
1217 if ssl_context is not None and keyfile is not None:
1218 raise ValueError("ssl_context and keyfile arguments are mutually "
1219 "exclusive")
1220 if ssl_context is not None and certfile is not None:
1221 raise ValueError("ssl_context and certfile arguments are mutually "
1222 "exclusive")
1223
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001224 self.keyfile = keyfile
1225 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001226 if ssl_context is None:
1227 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1228 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001229 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001230 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001231
Christian Heimesfb5faf02008-11-05 19:39:50 +00001232 def _create_socket(self):
1233 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001234 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001235 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001236
Christian Heimesfb5faf02008-11-05 19:39:50 +00001237 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001238 """Setup connection to remote server on "host:port".
1239 (default: localhost:standard IMAP4 SSL port).
1240 This connection will be used by the routines:
1241 read, readline, send, shutdown.
1242 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001243 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001244
1245 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001246
1247
Piers Laudere0273de2002-11-22 05:53:04 +00001248class IMAP4_stream(IMAP4):
1249
1250 """IMAP4 client class over a stream
1251
1252 Instantiate with: IMAP4_stream(command)
1253
Christian Heimesfb5faf02008-11-05 19:39:50 +00001254 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001255
1256 for more documentation see the docstring of the parent class IMAP4.
1257 """
1258
1259
1260 def __init__(self, command):
1261 self.command = command
1262 IMAP4.__init__(self)
1263
1264
1265 def open(self, host = None, port = None):
1266 """Setup a stream connection.
1267 This connection will be used by the routines:
1268 read, readline, send, shutdown.
1269 """
1270 self.host = None # For compatibility with parent class
1271 self.port = None
1272 self.sock = None
1273 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001274 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001275 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001276 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1277 shell=True, close_fds=True)
1278 self.writefile = self.process.stdin
1279 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001280
1281 def read(self, size):
1282 """Read 'size' bytes from remote."""
1283 return self.readfile.read(size)
1284
1285
1286 def readline(self):
1287 """Read line from remote."""
1288 return self.readfile.readline()
1289
1290
1291 def send(self, data):
1292 """Send data to remote."""
1293 self.writefile.write(data)
1294 self.writefile.flush()
1295
1296
1297 def shutdown(self):
1298 """Close I/O established in "open"."""
1299 self.readfile.close()
1300 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001301 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001302
1303
1304
Guido van Rossumeda960a1998-06-18 14:24:28 +00001305class _Authenticator:
1306
Tim Peters07e99cb2001-01-14 23:47:14 +00001307 """Private class to provide en/decoding
1308 for base64-based authentication conversation.
1309 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001310
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 def __init__(self, mechinst):
1312 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001313
Tim Peters07e99cb2001-01-14 23:47:14 +00001314 def process(self, data):
1315 ret = self.mech(self.decode(data))
1316 if ret is None:
1317 return '*' # Abort conversation
1318 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001319
Tim Peters07e99cb2001-01-14 23:47:14 +00001320 def encode(self, inp):
1321 #
1322 # Invoke binascii.b2a_base64 iteratively with
1323 # short even length buffers, strip the trailing
1324 # line feed from the result and append. "Even"
1325 # means a number that factors to both 6 and 8,
1326 # so when it gets to the end of the 8-bit input
1327 # there's no partial 6-bit output.
1328 #
R David Murray774a39f2013-02-19 12:17:31 -05001329 oup = b''
1330 if isinstance(inp, str):
1331 inp = inp.encode('ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001332 while inp:
1333 if len(inp) > 48:
1334 t = inp[:48]
1335 inp = inp[48:]
1336 else:
1337 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001338 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001339 e = binascii.b2a_base64(t)
1340 if e:
1341 oup = oup + e[:-1]
1342 return oup
1343
1344 def decode(self, inp):
1345 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001346 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 return binascii.a2b_base64(inp)
1348
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001349Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1350Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001351
1352def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001353 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001354
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001355 Return corresponding local time. The return value is a
1356 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
Tim Peters07e99cb2001-01-14 23:47:14 +00001359 mo = InternalDate.match(resp)
1360 if not mo:
1361 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001362
Tim Peters07e99cb2001-01-14 23:47:14 +00001363 mon = Mon2num[mo.group('mon')]
1364 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001365
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001366 day = int(mo.group('day'))
1367 year = int(mo.group('year'))
1368 hour = int(mo.group('hour'))
1369 min = int(mo.group('min'))
1370 sec = int(mo.group('sec'))
1371 zoneh = int(mo.group('zoneh'))
1372 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001373
Tim Peters07e99cb2001-01-14 23:47:14 +00001374 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001375
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001377 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001379
Tim Peters07e99cb2001-01-14 23:47:14 +00001380 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001381 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001382
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001383 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001384
1385
1386
1387def Int2AP(num):
1388
Tim Peters07e99cb2001-01-14 23:47:14 +00001389 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001390
Christian Heimesfb5faf02008-11-05 19:39:50 +00001391 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001392 num = int(abs(num))
1393 while num:
1394 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001395 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001397
1398
1399
1400def ParseFlags(resp):
1401
Tim Peters07e99cb2001-01-14 23:47:14 +00001402 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001403
Tim Peters07e99cb2001-01-14 23:47:14 +00001404 mo = Flags.match(resp)
1405 if not mo:
1406 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001407
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001408 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001409
1410
1411def Time2Internaldate(date_time):
1412
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001413 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001414
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001415 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001416 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001417 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001418 representing local time, an instance of time.struct_time (as
1419 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001420 double-quoted string. In the last case, it is assumed to already
1421 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001422 """
Fred Drakedb519202002-01-05 17:17:09 +00001423 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001424 dt = datetime.fromtimestamp(date_time,
1425 timezone.utc).astimezone()
1426 elif isinstance(date_time, tuple):
1427 try:
1428 gmtoff = date_time.tm_gmtoff
1429 except AttributeError:
1430 if time.daylight:
1431 dst = date_time[8]
1432 if dst == -1:
1433 dst = time.localtime(time.mktime(date_time))[8]
1434 gmtoff = -(time.timezone, time.altzone)[dst]
1435 else:
1436 gmtoff = -time.timezone
1437 delta = timedelta(seconds=gmtoff)
1438 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1439 elif isinstance(date_time, datetime):
1440 if date_time.tzinfo is None:
1441 raise ValueError("date_time must be aware")
1442 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001443 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001444 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001445 else:
1446 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001447 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1448 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001449
1450
1451
Guido van Rossum8c062211999-12-13 23:27:45 +00001452if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001453
Piers Laudere0273de2002-11-22 05:53:04 +00001454 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1455 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1456 # to test the IMAP4_stream class
1457
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001458 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001459
Tim Peters07e99cb2001-01-14 23:47:14 +00001460 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001461 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001462 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001463 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001464
Piers Laudere0273de2002-11-22 05:53:04 +00001465 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001466 for opt,val in optlist:
1467 if opt == '-d':
1468 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001469 elif opt == '-s':
1470 stream_command = val
1471 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001472
Tim Peters07e99cb2001-01-14 23:47:14 +00001473 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001474
Tim Peters07e99cb2001-01-14 23:47:14 +00001475 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001476
Tim Peters07e99cb2001-01-14 23:47:14 +00001477 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001478 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001479
Piers Lauder47404ff2003-04-29 23:40:59 +00001480 test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)sdata...%(lf)s' % {'user':USER, 'lf':'\n'}
Tim Peters07e99cb2001-01-14 23:47:14 +00001481 test_seq1 = (
1482 ('login', (USER, PASSWD)),
1483 ('create', ('/tmp/xxx 1',)),
1484 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1485 ('CREATE', ('/tmp/yyz 2',)),
1486 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1487 ('list', ('/tmp', 'yy*')),
1488 ('select', ('/tmp/yyz 2',)),
1489 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001490 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001491 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001492 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001493 ('expunge', ()),
1494 ('recent', ()),
1495 ('close', ()),
1496 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001497
Tim Peters07e99cb2001-01-14 23:47:14 +00001498 test_seq2 = (
1499 ('select', ()),
1500 ('response',('UIDVALIDITY',)),
1501 ('uid', ('SEARCH', 'ALL')),
1502 ('response', ('EXISTS',)),
1503 ('append', (None, None, None, test_mesg)),
1504 ('recent', ()),
1505 ('logout', ()),
1506 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001507
Tim Peters07e99cb2001-01-14 23:47:14 +00001508 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001509 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001510 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001511 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001512 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001513 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001514
Tim Peters07e99cb2001-01-14 23:47:14 +00001515 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001516 if stream_command:
1517 M = IMAP4_stream(stream_command)
1518 else:
1519 M = IMAP4(host)
1520 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001521 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001522 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001523 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001524
Tim Peters07e99cb2001-01-14 23:47:14 +00001525 for cmd,args in test_seq1:
1526 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001527
Tim Peters07e99cb2001-01-14 23:47:14 +00001528 for ml in run('list', ('/tmp/', 'yy%')):
1529 mo = re.match(r'.*"([^"]+)"$', ml)
1530 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001531 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001532 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001533
Tim Peters07e99cb2001-01-14 23:47:14 +00001534 for cmd,args in test_seq2:
1535 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001536
Tim Peters07e99cb2001-01-14 23:47:14 +00001537 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1538 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001539
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001540 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001541 if not uid: continue
1542 run('uid', ('FETCH', '%s' % uid[-1],
1543 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001544
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001545 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001546
Tim Peters07e99cb2001-01-14 23:47:14 +00001547 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001548 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001549
Tim Peters07e99cb2001-01-14 23:47:14 +00001550 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001551 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001552If you would like to see debugging output,
1553try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001554''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001555
Tim Peters07e99cb2001-01-14 23:47:14 +00001556 raise