blob: 00a17fbf36d3de0d7e559a95eb07dd6232639a7b [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
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000026
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000027try:
28 import ssl
29 HAVE_SSL = True
30except ImportError:
31 HAVE_SSL = False
32
Thomas Wouters47b49bf2007-08-30 22:15:33 +000033__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000034 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000035
Tim Peters07e99cb2001-01-14 23:47:14 +000036# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000037
Christian Heimesfb5faf02008-11-05 19:39:50 +000038CRLF = b'\r\n'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000039Debug = 0
40IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000041IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000042AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000043
Tim Peters07e99cb2001-01-14 23:47:14 +000044# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000045
46Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000047 # name valid states
48 'APPEND': ('AUTH', 'SELECTED'),
49 'AUTHENTICATE': ('NONAUTH',),
50 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
51 'CHECK': ('SELECTED',),
52 'CLOSE': ('SELECTED',),
53 'COPY': ('SELECTED',),
54 'CREATE': ('AUTH', 'SELECTED'),
55 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000056 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000057 'EXAMINE': ('AUTH', 'SELECTED'),
58 'EXPUNGE': ('SELECTED',),
59 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000060 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000061 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000062 'GETQUOTA': ('AUTH', 'SELECTED'),
63 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000064 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000065 'LIST': ('AUTH', 'SELECTED'),
66 'LOGIN': ('NONAUTH',),
67 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
68 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000069 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000070 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000071 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000072 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000073 'RENAME': ('AUTH', 'SELECTED'),
74 'SEARCH': ('SELECTED',),
75 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000076 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000077 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000078 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000079 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000080 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000081 'STATUS': ('AUTH', 'SELECTED'),
82 'STORE': ('SELECTED',),
83 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000084 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000085 'UID': ('SELECTED',),
86 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
87 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000088
Tim Peters07e99cb2001-01-14 23:47:14 +000089# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000090
Christian Heimesfb5faf02008-11-05 19:39:50 +000091Continuation = re.compile(br'\+( (?P<data>.*))?')
92Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
93InternalDate = re.compile(br'.*INTERNALDATE "'
94 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
95 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
96 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
97 br'"')
98Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
99MapCRLF = re.compile(br'\r\n|\r|\n')
100Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
101Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +0000102Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000103 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104
105
106
107class IMAP4:
108
Tim Peters07e99cb2001-01-14 23:47:14 +0000109 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000110
Tim Peters07e99cb2001-01-14 23:47:14 +0000111 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000112
Tim Peters07e99cb2001-01-14 23:47:14 +0000113 host - host's name (default: localhost);
114 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000115
Tim Peters07e99cb2001-01-14 23:47:14 +0000116 All IMAP4rev1 commands are supported by methods of the same
117 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000118
Tim Peters07e99cb2001-01-14 23:47:14 +0000119 All arguments to commands are converted to strings, except for
120 AUTHENTICATE, and the last argument to APPEND which is passed as
121 an IMAP4 literal. If necessary (the string contains any
122 non-printing characters or white-space and isn't enclosed with
123 either parentheses or double quotes) each string is quoted.
124 However, the 'password' argument to the LOGIN command is always
125 quoted. If you want to avoid having an argument string quoted
126 (eg: the 'flags' argument to STORE) then enclose the string in
127 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 Each command returns a tuple: (type, [data, ...]) where 'type'
130 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000131 tagged response, or untagged results from command. Each 'data'
132 is either a string, or a tuple. If a tuple, then the first part
133 is the header of the response, and the second part contains
134 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000135
Tim Peters07e99cb2001-01-14 23:47:14 +0000136 Errors raise the exception class <instance>.error("<reason>").
137 IMAP4 server errors raise <instance>.abort("<reason>"),
138 which is a sub-class of 'error'. Mailbox status changes
139 from READ-WRITE to READ-ONLY raise the exception class
140 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000141
Tim Peters07e99cb2001-01-14 23:47:14 +0000142 "error" exceptions imply a program error.
143 "abort" exceptions imply the connection should be reset, and
144 the command re-tried.
145 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000146
Piers Lauderd80ef022005-06-01 23:50:52 +0000147 Note: to use this module, you must read the RFCs pertaining to the
148 IMAP4 protocol, as the semantics of the arguments to each IMAP4
149 command are left to the invoker, not to mention the results. Also,
150 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000152
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 class error(Exception): pass # Logical errors - debug required
154 class abort(error): pass # Service errors - close and retry
155 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000156
Tim Peters07e99cb2001-01-14 23:47:14 +0000157 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 self.debug = Debug
159 self.state = 'LOGOUT'
160 self.literal = None # A literal argument to a command
161 self.tagged_commands = {} # Tagged commands awaiting response
162 self.untagged_responses = {} # {typ: [data, ...], ...}
163 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000164 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000166 self._tls_established = False
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000167
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000169
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Victor Stinner33e649c2011-01-05 23:01:37 +0000172 try:
173 self._connect()
174 except Exception:
175 try:
176 self.shutdown()
177 except socket.error:
178 pass
179 raise
180
181
182 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 # Create unique tag for this session,
184 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000185
Piers Lauder2dfc1682005-07-05 04:20:07 +0000186 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000187 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000188 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000189 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000190
Tim Peters07e99cb2001-01-14 23:47:14 +0000191 # Get server welcome message,
192 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000193
Tim Peters07e99cb2001-01-14 23:47:14 +0000194 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000195 self._cmd_log_len = 10
196 self._cmd_log_idx = 0
197 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000199 self._mesg('imaplib version %s' % __version__)
200 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000201
Tim Peters07e99cb2001-01-14 23:47:14 +0000202 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000203 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000204 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000205 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 self.state = 'NONAUTH'
207 else:
208 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000209
Antoine Pitroudbe75192010-11-16 17:55:26 +0000210 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000211 if __debug__:
212 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000213 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000214
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 for version in AllowedVersions:
216 if not version in self.capabilities:
217 continue
218 self.PROTOCOL_VERSION = version
219 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000220
Tim Peters07e99cb2001-01-14 23:47:14 +0000221 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000222
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000223
Tim Peters07e99cb2001-01-14 23:47:14 +0000224 def __getattr__(self, attr):
225 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000226 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000227 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000228 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000229
230
231
Piers Lauder15e5d532001-07-20 10:52:06 +0000232 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000233
234
Christian Heimesfb5faf02008-11-05 19:39:50 +0000235 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000236 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000237
Piers Lauderf97b2d72002-06-05 22:31:57 +0000238 def open(self, host = '', port = IMAP4_PORT):
239 """Setup connection to remote server on "host:port"
240 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000241 This connection will be used by the routines:
242 read, readline, send, shutdown.
243 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000244 self.host = host
245 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000246 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000247 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000248
249
Piers Lauder15e5d532001-07-20 10:52:06 +0000250 def read(self, size):
251 """Read 'size' bytes from remote."""
Christian Heimesfb5faf02008-11-05 19:39:50 +0000252 chunks = []
253 read = 0
254 while read < size:
255 data = self.file.read(min(size-read, 4096))
256 if not data:
257 break
258 read += len(data)
259 chunks.append(data)
260 return b''.join(chunks)
Piers Lauder15e5d532001-07-20 10:52:06 +0000261
262
263 def readline(self):
264 """Read line from remote."""
265 return self.file.readline()
266
267
268 def send(self, data):
269 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000270 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000271
Piers Lauderf2d7d152002-02-22 01:15:17 +0000272
Piers Lauder15e5d532001-07-20 10:52:06 +0000273 def shutdown(self):
274 """Close I/O established in "open"."""
275 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000276 try:
277 self.sock.shutdown(socket.SHUT_RDWR)
278 except socket.error as e:
279 # The server might already have closed the connection
280 if e.errno != errno.ENOTCONN:
281 raise
282 finally:
283 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000284
285
286 def socket(self):
287 """Return socket instance used to connect to IMAP4 server.
288
289 socket = <instance>.socket()
290 """
291 return self.sock
292
293
294
295 # Utility methods
296
297
Tim Peters07e99cb2001-01-14 23:47:14 +0000298 def recent(self):
299 """Return most recent 'RECENT' responses if any exist,
300 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000301
Tim Peters07e99cb2001-01-14 23:47:14 +0000302 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000303
Tim Peters07e99cb2001-01-14 23:47:14 +0000304 'data' is None if no new messages,
305 else list of RECENT responses, most recent last.
306 """
307 name = 'RECENT'
308 typ, dat = self._untagged_response('OK', [None], name)
309 if dat[-1]:
310 return typ, dat
311 typ, dat = self.noop() # Prod server for response
312 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000313
314
Tim Peters07e99cb2001-01-14 23:47:14 +0000315 def response(self, code):
316 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000317
Tim Peters07e99cb2001-01-14 23:47:14 +0000318 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000319
Tim Peters07e99cb2001-01-14 23:47:14 +0000320 (code, [data]) = <instance>.response(code)
321 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000322 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000323
324
Guido van Rossum26367a01998-09-28 15:34:46 +0000325
Tim Peters07e99cb2001-01-14 23:47:14 +0000326 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000327
328
Tim Peters07e99cb2001-01-14 23:47:14 +0000329 def append(self, mailbox, flags, date_time, message):
330 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000331
Tim Peters07e99cb2001-01-14 23:47:14 +0000332 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000333
Tim Peters07e99cb2001-01-14 23:47:14 +0000334 All args except `message' can be None.
335 """
336 name = 'APPEND'
337 if not mailbox:
338 mailbox = 'INBOX'
339 if flags:
340 if (flags[0],flags[-1]) != ('(',')'):
341 flags = '(%s)' % flags
342 else:
343 flags = None
344 if date_time:
345 date_time = Time2Internaldate(date_time)
346 else:
347 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000348 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000350
351
Tim Peters07e99cb2001-01-14 23:47:14 +0000352 def authenticate(self, mechanism, authobject):
353 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000354
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 'mechanism' specifies which authentication mechanism is to
356 be used - it must appear in <instance>.capabilities in the
357 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000358
Tim Peters07e99cb2001-01-14 23:47:14 +0000359 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000360
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000362
R David Murray774a39f2013-02-19 12:17:31 -0500363 It will be called to process server continuation responses; the
364 response argument it is passed will be a bytes. It should return bytes
365 data that will be base64 encoded and sent to the server. It should
366 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000368 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000369 # XXX: shouldn't this code be removed, not commented out?
370 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000371 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000372 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 self.literal = _Authenticator(authobject).process
374 typ, dat = self._simple_command('AUTHENTICATE', mech)
375 if typ != 'OK':
376 raise self.error(dat[-1])
377 self.state = 'AUTH'
378 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000379
380
Piers Lauderd80ef022005-06-01 23:50:52 +0000381 def capability(self):
382 """(typ, [data]) = <instance>.capability()
383 Fetch capabilities list from server."""
384
385 name = 'CAPABILITY'
386 typ, dat = self._simple_command(name)
387 return self._untagged_response(typ, dat, name)
388
389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 def check(self):
391 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 (typ, [data]) = <instance>.check()
394 """
395 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 def close(self):
399 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000400
Tim Peters07e99cb2001-01-14 23:47:14 +0000401 Deleted messages are removed from writable mailbox.
402 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 (typ, [data]) = <instance>.close()
405 """
406 try:
407 typ, dat = self._simple_command('CLOSE')
408 finally:
409 self.state = 'AUTH'
410 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 def copy(self, message_set, new_mailbox):
414 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
417 """
418 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000419
420
Tim Peters07e99cb2001-01-14 23:47:14 +0000421 def create(self, mailbox):
422 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000423
Tim Peters07e99cb2001-01-14 23:47:14 +0000424 (typ, [data]) = <instance>.create(mailbox)
425 """
426 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000427
428
Tim Peters07e99cb2001-01-14 23:47:14 +0000429 def delete(self, mailbox):
430 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000431
Tim Peters07e99cb2001-01-14 23:47:14 +0000432 (typ, [data]) = <instance>.delete(mailbox)
433 """
434 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000435
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000436 def deleteacl(self, mailbox, who):
437 """Delete the ACLs (remove any rights) set for who on mailbox.
438
439 (typ, [data]) = <instance>.deleteacl(mailbox, who)
440 """
441 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
Tim Peters07e99cb2001-01-14 23:47:14 +0000443 def expunge(self):
444 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000445
Tim Peters07e99cb2001-01-14 23:47:14 +0000446 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000447
Tim Peters07e99cb2001-01-14 23:47:14 +0000448 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 'data' is list of 'EXPUNGE'd message numbers in order received.
451 """
452 name = 'EXPUNGE'
453 typ, dat = self._simple_command(name)
454 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000455
456
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 def fetch(self, message_set, message_parts):
458 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000459
Tim Peters07e99cb2001-01-14 23:47:14 +0000460 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
Tim Peters07e99cb2001-01-14 23:47:14 +0000462 'message_parts' should be a string of selected parts
463 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000464
Tim Peters07e99cb2001-01-14 23:47:14 +0000465 'data' are tuples of message part envelope and data.
466 """
467 name = 'FETCH'
468 typ, dat = self._simple_command(name, message_set, message_parts)
469 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000470
471
Piers Lauder15e5d532001-07-20 10:52:06 +0000472 def getacl(self, mailbox):
473 """Get the ACLs for a mailbox.
474
475 (typ, [data]) = <instance>.getacl(mailbox)
476 """
477 typ, dat = self._simple_command('GETACL', mailbox)
478 return self._untagged_response(typ, dat, 'ACL')
479
480
Piers Lauderd80ef022005-06-01 23:50:52 +0000481 def getannotation(self, mailbox, entry, attribute):
482 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
483 Retrieve ANNOTATIONs."""
484
485 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
486 return self._untagged_response(typ, dat, 'ANNOTATION')
487
488
Piers Lauder3fca2912002-06-17 07:07:20 +0000489 def getquota(self, root):
490 """Get the quota root's resource usage and limits.
491
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000492 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000493
494 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000495 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000496 typ, dat = self._simple_command('GETQUOTA', root)
497 return self._untagged_response(typ, dat, 'QUOTA')
498
499
500 def getquotaroot(self, mailbox):
501 """Get the list of quota roots for the named mailbox.
502
503 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000504 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000505 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000506 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
507 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000508 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000509
510
Tim Peters07e99cb2001-01-14 23:47:14 +0000511 def list(self, directory='""', pattern='*'):
512 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000515
Tim Peters07e99cb2001-01-14 23:47:14 +0000516 'data' is list of LIST responses.
517 """
518 name = 'LIST'
519 typ, dat = self._simple_command(name, directory, pattern)
520 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000521
522
Tim Peters07e99cb2001-01-14 23:47:14 +0000523 def login(self, user, password):
524 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000525
Tim Peters07e99cb2001-01-14 23:47:14 +0000526 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000527
Tim Peters07e99cb2001-01-14 23:47:14 +0000528 NB: 'password' will be quoted.
529 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000530 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
531 if typ != 'OK':
532 raise self.error(dat[-1])
533 self.state = 'AUTH'
534 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000535
536
Piers Laudere0273de2002-11-22 05:53:04 +0000537 def login_cram_md5(self, user, password):
538 """ Force use of CRAM-MD5 authentication.
539
540 (typ, [data]) = <instance>.login_cram_md5(user, password)
541 """
542 self.user, self.password = user, password
543 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
544
545
546 def _CRAM_MD5_AUTH(self, challenge):
547 """ Authobject to use with CRAM-MD5 authentication. """
548 import hmac
R David Murray774a39f2013-02-19 12:17:31 -0500549 pwd = (self.password.encode('ASCII') if isinstance(self.password, str)
550 else self.password)
551 return self.user + " " + hmac.HMAC(pwd, challenge).hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000552
553
Tim Peters07e99cb2001-01-14 23:47:14 +0000554 def logout(self):
555 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000556
Tim Peters07e99cb2001-01-14 23:47:14 +0000557 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000558
Tim Peters07e99cb2001-01-14 23:47:14 +0000559 Returns server 'BYE' response.
560 """
561 self.state = 'LOGOUT'
562 try: typ, dat = self._simple_command('LOGOUT')
563 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000564 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000565 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000566 return 'BYE', self.untagged_responses['BYE']
567 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000568
569
Tim Peters07e99cb2001-01-14 23:47:14 +0000570 def lsub(self, directory='""', pattern='*'):
571 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000572
Tim Peters07e99cb2001-01-14 23:47:14 +0000573 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000574
Tim Peters07e99cb2001-01-14 23:47:14 +0000575 'data' are tuples of message part envelope and data.
576 """
577 name = 'LSUB'
578 typ, dat = self._simple_command(name, directory, pattern)
579 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000580
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000581 def myrights(self, mailbox):
582 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
583
584 (typ, [data]) = <instance>.myrights(mailbox)
585 """
586 typ,dat = self._simple_command('MYRIGHTS', mailbox)
587 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000588
Piers Lauder15e5d532001-07-20 10:52:06 +0000589 def namespace(self):
590 """ Returns IMAP namespaces ala rfc2342
591
592 (typ, [data, ...]) = <instance>.namespace()
593 """
594 name = 'NAMESPACE'
595 typ, dat = self._simple_command(name)
596 return self._untagged_response(typ, dat, name)
597
598
Tim Peters07e99cb2001-01-14 23:47:14 +0000599 def noop(self):
600 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000601
Piers Laudere0273de2002-11-22 05:53:04 +0000602 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 """
604 if __debug__:
605 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000606 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000607 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000608
609
Tim Peters07e99cb2001-01-14 23:47:14 +0000610 def partial(self, message_num, message_part, start, length):
611 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000612
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000614
Tim Peters07e99cb2001-01-14 23:47:14 +0000615 'data' is tuple of message part envelope and data.
616 """
617 name = 'PARTIAL'
618 typ, dat = self._simple_command(name, message_num, message_part, start, length)
619 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000620
621
Piers Laudere0273de2002-11-22 05:53:04 +0000622 def proxyauth(self, user):
623 """Assume authentication as "user".
624
625 Allows an authorised administrator to proxy into any user's
626 mailbox.
627
628 (typ, [data]) = <instance>.proxyauth(user)
629 """
630
631 name = 'PROXYAUTH'
632 return self._simple_command('PROXYAUTH', user)
633
634
Tim Peters07e99cb2001-01-14 23:47:14 +0000635 def rename(self, oldmailbox, newmailbox):
636 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000637
Piers Laudere0273de2002-11-22 05:53:04 +0000638 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 """
640 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000641
642
Tim Peters07e99cb2001-01-14 23:47:14 +0000643 def search(self, charset, *criteria):
644 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000645
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000646 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000647
Tim Peters07e99cb2001-01-14 23:47:14 +0000648 'data' is space separated list of matching message numbers.
649 """
650 name = 'SEARCH'
651 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000652 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000653 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000654 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000656
657
Piers Lauder14f39402005-08-31 10:46:29 +0000658 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000660
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000662
Piers Lauder14f39402005-08-31 10:46:29 +0000663 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000664
Tim Peters07e99cb2001-01-14 23:47:14 +0000665 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000666
667 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
668 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000669 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000670 self.untagged_responses = {} # Flush old responses.
671 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000672 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000673 name = 'EXAMINE'
674 else:
675 name = 'SELECT'
676 typ, dat = self._simple_command(name, mailbox)
677 if typ != 'OK':
678 self.state = 'AUTH' # Might have been 'SELECTED'
679 return typ, dat
680 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000681 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000682 and not readonly:
683 if __debug__:
684 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000685 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000686 raise self.readonly('%s is not writable' % mailbox)
687 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000688
689
Piers Lauder15e5d532001-07-20 10:52:06 +0000690 def setacl(self, mailbox, who, what):
691 """Set a mailbox acl.
692
Piers Lauderf167dc32004-03-25 00:12:21 +0000693 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000694 """
695 return self._simple_command('SETACL', mailbox, who, what)
696
697
Piers Lauderd80ef022005-06-01 23:50:52 +0000698 def setannotation(self, *args):
699 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
700 Set ANNOTATIONs."""
701
702 typ, dat = self._simple_command('SETANNOTATION', *args)
703 return self._untagged_response(typ, dat, 'ANNOTATION')
704
705
Piers Lauder3fca2912002-06-17 07:07:20 +0000706 def setquota(self, root, limits):
707 """Set the quota root's resource limits.
708
709 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000710 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000711 typ, dat = self._simple_command('SETQUOTA', root, limits)
712 return self._untagged_response(typ, dat, 'QUOTA')
713
714
Piers Lauder15e5d532001-07-20 10:52:06 +0000715 def sort(self, sort_criteria, charset, *search_criteria):
716 """IMAP4rev1 extension SORT command.
717
718 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
719 """
720 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000721 #if not name in self.capabilities: # Let the server decide!
722 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000723 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000724 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000725 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000726 return self._untagged_response(typ, dat, name)
727
728
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000729 def starttls(self, ssl_context=None):
730 name = 'STARTTLS'
731 if not HAVE_SSL:
732 raise self.error('SSL support missing')
733 if self._tls_established:
734 raise self.abort('TLS session already established')
735 if name not in self.capabilities:
736 raise self.abort('TLS not supported by server')
737 # Generate a default SSL context if none was passed.
738 if ssl_context is None:
739 ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
740 # SSLv2 considered harmful.
741 ssl_context.options |= ssl.OP_NO_SSLv2
742 typ, dat = self._simple_command(name)
743 if typ == 'OK':
744 self.sock = ssl_context.wrap_socket(self.sock)
745 self.file = self.sock.makefile('rb')
746 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000747 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000748 else:
749 raise self.error("Couldn't establish TLS session")
750 return self._untagged_response(typ, dat, name)
751
752
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 def status(self, mailbox, names):
754 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 (typ, [data]) = <instance>.status(mailbox, names)
757 """
758 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000759 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000760 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000761 typ, dat = self._simple_command(name, mailbox, names)
762 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000763
764
Tim Peters07e99cb2001-01-14 23:47:14 +0000765 def store(self, message_set, command, flags):
766 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000767
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 (typ, [data]) = <instance>.store(message_set, command, flags)
769 """
770 if (flags[0],flags[-1]) != ('(',')'):
771 flags = '(%s)' % flags # Avoid quoting the flags
772 typ, dat = self._simple_command('STORE', message_set, command, flags)
773 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000774
775
Tim Peters07e99cb2001-01-14 23:47:14 +0000776 def subscribe(self, mailbox):
777 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000778
Tim Peters07e99cb2001-01-14 23:47:14 +0000779 (typ, [data]) = <instance>.subscribe(mailbox)
780 """
781 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000782
783
Martin v. Löwisd8921372003-11-10 06:44:44 +0000784 def thread(self, threading_algorithm, charset, *search_criteria):
785 """IMAPrev1 extension THREAD command.
786
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000787 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000788 """
789 name = 'THREAD'
790 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
791 return self._untagged_response(typ, dat, name)
792
793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 def uid(self, command, *args):
795 """Execute "command arg ..." with messages identified by UID,
796 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000797
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000799
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 Returns response appropriate to 'command'.
801 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000802 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000803 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 raise self.error("Unknown IMAP4 UID command: %s" % command)
805 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000806 raise self.error("command %s illegal in state %s, "
807 "only allowed in states %s" %
808 (command, self.state,
809 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000811 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000812 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000813 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 else:
815 name = 'FETCH'
816 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000817
818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 def unsubscribe(self, mailbox):
820 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000821
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 (typ, [data]) = <instance>.unsubscribe(mailbox)
823 """
824 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000825
826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 def xatom(self, name, *args):
828 """Allow simple extension commands
829 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000830
Piers Lauder15e5d532001-07-20 10:52:06 +0000831 Assumes command is legal in current state.
832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000834
835 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000837 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000838 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000839 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000840 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000841 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000842 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000843
844
845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000847
848
Tim Peters07e99cb2001-01-14 23:47:14 +0000849 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000850 if dat is None:
851 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 ur = self.untagged_responses
853 if __debug__:
854 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000855 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000857 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000858 ur[typ].append(dat)
859 else:
860 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000861
862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 def _check_bye(self):
864 bye = self.untagged_responses.get('BYE')
865 if bye:
Antoine Pitroudac47912010-11-10 00:18:40 +0000866 raise self.abort(bye[-1].decode('ascii', 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000867
868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 if self.state not in Commands[name]:
872 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000873 raise self.error("command %s illegal in state %s, "
874 "only allowed in states %s" %
875 (name, self.state,
876 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000879 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000881
Raymond Hettinger54f02222002-06-01 14:18:47 +0000882 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 and not self.is_readonly:
884 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000887 name = bytes(name, 'ASCII')
888 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 for arg in args:
890 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000891 if isinstance(arg, str):
892 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000893 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000894
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 literal = self.literal
896 if literal is not None:
897 self.literal = None
898 if type(literal) is type(self._command):
899 literator = literal
900 else:
901 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000902 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 if __debug__:
905 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000906 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000907 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000908 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000911 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000912 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000914
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 if literal is None:
916 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000917
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 while 1:
919 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 while self._get_response():
922 if self.tagged_commands[tag]: # BAD/NO?
923 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 if literator:
928 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 if __debug__:
931 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000932 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000935 self.send(literal)
936 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000937 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000939
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 if not literator:
941 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000944
945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000947 # BYE is expected after LOGOUT
948 if name != 'LOGOUT':
949 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 try:
951 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000952 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000954 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +0000956 if name != 'LOGOUT':
957 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 if typ == 'BAD':
959 raise self.error('%s command error: %s %s' % (name, typ, data))
960 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000961
962
Antoine Pitroudbe75192010-11-16 17:55:26 +0000963 def _get_capabilities(self):
964 typ, dat = self.capability()
965 if dat == [None]:
966 raise self.error('no CAPABILITY response from server')
967 dat = str(dat[-1], "ASCII")
968 dat = dat.upper()
969 self.capabilities = tuple(dat.split())
970
971
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000973
Tim Peters07e99cb2001-01-14 23:47:14 +0000974 # Read response and store.
975 #
976 # Returns None for continuation responses,
977 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 if self._match(self.tagre, resp):
984 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000985 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000989 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 dat = self.mo.group('data')
991 self.tagged_commands[tag] = (typ, [dat])
992 else:
993 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 if not self._match(Untagged_response, resp):
998 if self._match(Untagged_status, resp):
999 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001000
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 if self.mo is None:
1002 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001003
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 if self._match(Continuation, resp):
1005 self.continuation_response = self.mo.group('data')
1006 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001007
Tim Peters07e99cb2001-01-14 23:47:14 +00001008 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001011 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +00001012 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001013 if dat is None: dat = b'' # Null untagged response
1014 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001019
Tim Peters07e99cb2001-01-14 23:47:14 +00001020 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001022 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001023 if __debug__:
1024 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001025 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001026 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001031
Tim Peters07e99cb2001-01-14 23:47:14 +00001032 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001035
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001041 typ = self.mo.group('type')
1042 typ = str(typ, "ASCII")
1043 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
Tim Peters07e99cb2001-01-14 23:47:14 +00001045 if __debug__:
1046 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001047 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001048
Tim Peters07e99cb2001-01-14 23:47:14 +00001049 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001050
1051
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001053
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 while 1:
1055 result = self.tagged_commands[tag]
1056 if result is not None:
1057 del self.tagged_commands[tag]
1058 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 # Some have reported "unexpected response" exceptions.
1061 # Note that ignoring them here causes loops.
1062 # Instead, send me details of the unexpected response and
1063 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001064
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 try:
1066 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001067 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 if __debug__:
1069 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001070 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001071 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001072
1073
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001075
Piers Lauder15e5d532001-07-20 10:52:06 +00001076 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001077 if not line:
1078 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001081 if not line.endswith(b'\r\n'):
1082 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001083
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 line = line[:-2]
1085 if __debug__:
1086 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001087 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001089 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001090 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
1092
Tim Peters07e99cb2001-01-14 23:47:14 +00001093 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001094
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 # Run compiled regular expression match method on 's'.
1096 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001097
Tim Peters07e99cb2001-01-14 23:47:14 +00001098 self.mo = cre.match(s)
1099 if __debug__:
1100 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001101 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001102 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001103
1104
Tim Peters07e99cb2001-01-14 23:47:14 +00001105 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001106
Christian Heimesfb5faf02008-11-05 19:39:50 +00001107 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001108 self.tagnum = self.tagnum + 1
1109 self.tagged_commands[tag] = None
1110 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001111
1112
Tim Peters07e99cb2001-01-14 23:47:14 +00001113 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001114
Antoine Pitroub1436f12010-11-09 22:55:55 +00001115 arg = arg.replace('\\', '\\\\')
1116 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001117
Antoine Pitroub1436f12010-11-09 22:55:55 +00001118 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001119
1120
Tim Peters07e99cb2001-01-14 23:47:14 +00001121 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001122
Guido van Rossum68468eb2003-02-27 20:14:51 +00001123 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001124
1125
Tim Peters07e99cb2001-01-14 23:47:14 +00001126 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001127 if typ == 'NO':
1128 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001129 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001130 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001131 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001132 if __debug__:
1133 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001134 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001135 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001136
1137
Piers Lauderf2d7d152002-02-22 01:15:17 +00001138 if __debug__:
1139
1140 def _mesg(self, s, secs=None):
1141 if secs is None:
1142 secs = time.time()
1143 tm = time.strftime('%M:%S', time.localtime(secs))
1144 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1145 sys.stderr.flush()
1146
1147 def _dump_ur(self, dict):
1148 # Dump untagged responses (in `dict').
1149 l = dict.items()
1150 if not l: return
1151 t = '\n\t\t'
1152 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1153 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1154
1155 def _log(self, line):
1156 # Keep log of last `_cmd_log_len' interactions for debugging.
1157 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1158 self._cmd_log_idx += 1
1159 if self._cmd_log_idx >= self._cmd_log_len:
1160 self._cmd_log_idx = 0
1161
1162 def print_log(self):
1163 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1164 i, n = self._cmd_log_idx, self._cmd_log_len
1165 while n:
1166 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001167 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001168 except:
1169 pass
1170 i += 1
1171 if i >= self._cmd_log_len:
1172 i = 0
1173 n -= 1
1174
1175
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001176if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001177
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001178 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001179
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001180 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001181
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001182 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001183
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001184 host - host's name (default: localhost);
1185 port - port number (default: standard IMAP4 SSL port).
1186 keyfile - PEM formatted file that contains your private key (default: None);
1187 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001188
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001189 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001190 """
Piers Laudera4f83132002-03-08 01:53:24 +00001191
1192
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001193 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1194 self.keyfile = keyfile
1195 self.certfile = certfile
1196 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001197
Christian Heimesfb5faf02008-11-05 19:39:50 +00001198 def _create_socket(self):
1199 sock = IMAP4._create_socket(self)
1200 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001201
Christian Heimesfb5faf02008-11-05 19:39:50 +00001202 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001203 """Setup connection to remote server on "host:port".
1204 (default: localhost:standard IMAP4 SSL port).
1205 This connection will be used by the routines:
1206 read, readline, send, shutdown.
1207 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001208 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001209
1210 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001211
1212
Piers Laudere0273de2002-11-22 05:53:04 +00001213class IMAP4_stream(IMAP4):
1214
1215 """IMAP4 client class over a stream
1216
1217 Instantiate with: IMAP4_stream(command)
1218
Christian Heimesfb5faf02008-11-05 19:39:50 +00001219 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001220
1221 for more documentation see the docstring of the parent class IMAP4.
1222 """
1223
1224
1225 def __init__(self, command):
1226 self.command = command
1227 IMAP4.__init__(self)
1228
1229
1230 def open(self, host = None, port = None):
1231 """Setup a stream connection.
1232 This connection will be used by the routines:
1233 read, readline, send, shutdown.
1234 """
1235 self.host = None # For compatibility with parent class
1236 self.port = None
1237 self.sock = None
1238 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001239 self.process = subprocess.Popen(self.command,
1240 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1241 shell=True, close_fds=True)
1242 self.writefile = self.process.stdin
1243 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001244
1245 def read(self, size):
1246 """Read 'size' bytes from remote."""
1247 return self.readfile.read(size)
1248
1249
1250 def readline(self):
1251 """Read line from remote."""
1252 return self.readfile.readline()
1253
1254
1255 def send(self, data):
1256 """Send data to remote."""
1257 self.writefile.write(data)
1258 self.writefile.flush()
1259
1260
1261 def shutdown(self):
1262 """Close I/O established in "open"."""
1263 self.readfile.close()
1264 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001265 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001266
1267
1268
Guido van Rossumeda960a1998-06-18 14:24:28 +00001269class _Authenticator:
1270
Tim Peters07e99cb2001-01-14 23:47:14 +00001271 """Private class to provide en/decoding
1272 for base64-based authentication conversation.
1273 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001274
Tim Peters07e99cb2001-01-14 23:47:14 +00001275 def __init__(self, mechinst):
1276 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001277
Tim Peters07e99cb2001-01-14 23:47:14 +00001278 def process(self, data):
1279 ret = self.mech(self.decode(data))
1280 if ret is None:
1281 return '*' # Abort conversation
1282 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001283
Tim Peters07e99cb2001-01-14 23:47:14 +00001284 def encode(self, inp):
1285 #
1286 # Invoke binascii.b2a_base64 iteratively with
1287 # short even length buffers, strip the trailing
1288 # line feed from the result and append. "Even"
1289 # means a number that factors to both 6 and 8,
1290 # so when it gets to the end of the 8-bit input
1291 # there's no partial 6-bit output.
1292 #
R David Murray774a39f2013-02-19 12:17:31 -05001293 oup = b''
1294 if isinstance(inp, str):
1295 inp = inp.encode('ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001296 while inp:
1297 if len(inp) > 48:
1298 t = inp[:48]
1299 inp = inp[48:]
1300 else:
1301 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001302 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001303 e = binascii.b2a_base64(t)
1304 if e:
1305 oup = oup + e[:-1]
1306 return oup
1307
1308 def decode(self, inp):
1309 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001310 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 return binascii.a2b_base64(inp)
1312
Guido van Rossumeda960a1998-06-18 14:24:28 +00001313
1314
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001315Mon2num = {b'Jan': 1, b'Feb': 2, b'Mar': 3, b'Apr': 4, b'May': 5, b'Jun': 6,
1316 b'Jul': 7, b'Aug': 8, b'Sep': 9, b'Oct': 10, b'Nov': 11, b'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001317
1318def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001319 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001320
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001321 Return corresponding local time. The return value is a
1322 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001324
Tim Peters07e99cb2001-01-14 23:47:14 +00001325 mo = InternalDate.match(resp)
1326 if not mo:
1327 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001328
Tim Peters07e99cb2001-01-14 23:47:14 +00001329 mon = Mon2num[mo.group('mon')]
1330 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001331
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001332 day = int(mo.group('day'))
1333 year = int(mo.group('year'))
1334 hour = int(mo.group('hour'))
1335 min = int(mo.group('min'))
1336 sec = int(mo.group('sec'))
1337 zoneh = int(mo.group('zoneh'))
1338 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001339
Tim Peters07e99cb2001-01-14 23:47:14 +00001340 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001341
Tim Peters07e99cb2001-01-14 23:47:14 +00001342 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001343 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001345
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001347 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001348
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001349 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001350
1351
1352
1353def Int2AP(num):
1354
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001356
Christian Heimesfb5faf02008-11-05 19:39:50 +00001357 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001358 num = int(abs(num))
1359 while num:
1360 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001361 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001362 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001363
1364
1365
1366def ParseFlags(resp):
1367
Tim Peters07e99cb2001-01-14 23:47:14 +00001368 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001369
Tim Peters07e99cb2001-01-14 23:47:14 +00001370 mo = Flags.match(resp)
1371 if not mo:
1372 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001373
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001374 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001375
1376
1377def Time2Internaldate(date_time):
1378
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001379 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001380
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001381 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001382 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001383 seconds since epoch (as returned by time.time()), a 9-tuple
1384 representing local time (as returned by time.localtime()), or a
1385 double-quoted string. In the last case, it is assumed to already
1386 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001387 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001388
Fred Drakedb519202002-01-05 17:17:09 +00001389 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001390 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001391 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001392 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001393 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001394 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001395 else:
1396 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001397
Tim Peters07e99cb2001-01-14 23:47:14 +00001398 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1399 if dt[0] == '0':
1400 dt = ' ' + dt[1:]
1401 if time.daylight and tt[-1]:
1402 zone = -time.altzone
1403 else:
1404 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001405 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001406
1407
1408
Guido van Rossum8c062211999-12-13 23:27:45 +00001409if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001410
Piers Laudere0273de2002-11-22 05:53:04 +00001411 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1412 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1413 # to test the IMAP4_stream class
1414
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001415 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001416
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001418 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001419 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001420 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001421
Piers Laudere0273de2002-11-22 05:53:04 +00001422 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001423 for opt,val in optlist:
1424 if opt == '-d':
1425 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001426 elif opt == '-s':
1427 stream_command = val
1428 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001431
Tim Peters07e99cb2001-01-14 23:47:14 +00001432 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001433
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001435 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001436
Piers Lauder47404ff2003-04-29 23:40:59 +00001437 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 +00001438 test_seq1 = (
1439 ('login', (USER, PASSWD)),
1440 ('create', ('/tmp/xxx 1',)),
1441 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1442 ('CREATE', ('/tmp/yyz 2',)),
1443 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1444 ('list', ('/tmp', 'yy*')),
1445 ('select', ('/tmp/yyz 2',)),
1446 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001447 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001448 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001449 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001450 ('expunge', ()),
1451 ('recent', ()),
1452 ('close', ()),
1453 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001454
Tim Peters07e99cb2001-01-14 23:47:14 +00001455 test_seq2 = (
1456 ('select', ()),
1457 ('response',('UIDVALIDITY',)),
1458 ('uid', ('SEARCH', 'ALL')),
1459 ('response', ('EXISTS',)),
1460 ('append', (None, None, None, test_mesg)),
1461 ('recent', ()),
1462 ('logout', ()),
1463 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001464
Tim Peters07e99cb2001-01-14 23:47:14 +00001465 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001466 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001467 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001468 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001469 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001470 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001471
Tim Peters07e99cb2001-01-14 23:47:14 +00001472 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001473 if stream_command:
1474 M = IMAP4_stream(stream_command)
1475 else:
1476 M = IMAP4(host)
1477 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001478 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001479 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001480 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001481
Tim Peters07e99cb2001-01-14 23:47:14 +00001482 for cmd,args in test_seq1:
1483 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001484
Tim Peters07e99cb2001-01-14 23:47:14 +00001485 for ml in run('list', ('/tmp/', 'yy%')):
1486 mo = re.match(r'.*"([^"]+)"$', ml)
1487 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001488 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001489 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001490
Tim Peters07e99cb2001-01-14 23:47:14 +00001491 for cmd,args in test_seq2:
1492 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001493
Tim Peters07e99cb2001-01-14 23:47:14 +00001494 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1495 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001496
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001497 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001498 if not uid: continue
1499 run('uid', ('FETCH', '%s' % uid[-1],
1500 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001501
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001502 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001503
Tim Peters07e99cb2001-01-14 23:47:14 +00001504 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001505 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001506
Tim Peters07e99cb2001-01-14 23:47:14 +00001507 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001508 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001509If you would like to see debugging output,
1510try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001511''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001512
Tim Peters07e99cb2001-01-14 23:47:14 +00001513 raise