blob: 4b8e898575fa9dafa1cfaccd380077329852b066 [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
Antoine Pitrou81c87c52010-11-10 08:59:25 +000025import binascii, errno, random, re, socket, subprocess, sys, time
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
Tim Peters07e99cb2001-01-14 23:47:14 +0000363 It will be called to process server continuation responses.
364 It should return data that will be encoded and sent to server.
365 It should return None if the client abort response '*' should
366 be sent instead.
367 """
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
549 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
550
551
Tim Peters07e99cb2001-01-14 23:47:14 +0000552 def logout(self):
553 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000554
Tim Peters07e99cb2001-01-14 23:47:14 +0000555 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000556
Tim Peters07e99cb2001-01-14 23:47:14 +0000557 Returns server 'BYE' response.
558 """
559 self.state = 'LOGOUT'
560 try: typ, dat = self._simple_command('LOGOUT')
561 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000562 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000563 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000564 return 'BYE', self.untagged_responses['BYE']
565 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000566
567
Tim Peters07e99cb2001-01-14 23:47:14 +0000568 def lsub(self, directory='""', pattern='*'):
569 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000570
Tim Peters07e99cb2001-01-14 23:47:14 +0000571 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000572
Tim Peters07e99cb2001-01-14 23:47:14 +0000573 'data' are tuples of message part envelope and data.
574 """
575 name = 'LSUB'
576 typ, dat = self._simple_command(name, directory, pattern)
577 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000578
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000579 def myrights(self, mailbox):
580 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
581
582 (typ, [data]) = <instance>.myrights(mailbox)
583 """
584 typ,dat = self._simple_command('MYRIGHTS', mailbox)
585 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000586
Piers Lauder15e5d532001-07-20 10:52:06 +0000587 def namespace(self):
588 """ Returns IMAP namespaces ala rfc2342
589
590 (typ, [data, ...]) = <instance>.namespace()
591 """
592 name = 'NAMESPACE'
593 typ, dat = self._simple_command(name)
594 return self._untagged_response(typ, dat, name)
595
596
Tim Peters07e99cb2001-01-14 23:47:14 +0000597 def noop(self):
598 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000599
Piers Laudere0273de2002-11-22 05:53:04 +0000600 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000601 """
602 if __debug__:
603 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000604 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000605 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000606
607
Tim Peters07e99cb2001-01-14 23:47:14 +0000608 def partial(self, message_num, message_part, start, length):
609 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000610
Tim Peters07e99cb2001-01-14 23:47:14 +0000611 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000612
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 'data' is tuple of message part envelope and data.
614 """
615 name = 'PARTIAL'
616 typ, dat = self._simple_command(name, message_num, message_part, start, length)
617 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000618
619
Piers Laudere0273de2002-11-22 05:53:04 +0000620 def proxyauth(self, user):
621 """Assume authentication as "user".
622
623 Allows an authorised administrator to proxy into any user's
624 mailbox.
625
626 (typ, [data]) = <instance>.proxyauth(user)
627 """
628
629 name = 'PROXYAUTH'
630 return self._simple_command('PROXYAUTH', user)
631
632
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 def rename(self, oldmailbox, newmailbox):
634 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
Piers Laudere0273de2002-11-22 05:53:04 +0000636 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 """
638 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000639
640
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 def search(self, charset, *criteria):
642 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000643
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000644 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000645
Tim Peters07e99cb2001-01-14 23:47:14 +0000646 'data' is space separated list of matching message numbers.
647 """
648 name = 'SEARCH'
649 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000650 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000651 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000652 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
655
Piers Lauder14f39402005-08-31 10:46:29 +0000656 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000657 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000658
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000660
Piers Lauder14f39402005-08-31 10:46:29 +0000661 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000662
Tim Peters07e99cb2001-01-14 23:47:14 +0000663 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000664
665 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
666 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000668 self.untagged_responses = {} # Flush old responses.
669 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000670 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 name = 'EXAMINE'
672 else:
673 name = 'SELECT'
674 typ, dat = self._simple_command(name, mailbox)
675 if typ != 'OK':
676 self.state = 'AUTH' # Might have been 'SELECTED'
677 return typ, dat
678 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000679 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 and not readonly:
681 if __debug__:
682 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000683 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000684 raise self.readonly('%s is not writable' % mailbox)
685 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000686
687
Piers Lauder15e5d532001-07-20 10:52:06 +0000688 def setacl(self, mailbox, who, what):
689 """Set a mailbox acl.
690
Piers Lauderf167dc32004-03-25 00:12:21 +0000691 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000692 """
693 return self._simple_command('SETACL', mailbox, who, what)
694
695
Piers Lauderd80ef022005-06-01 23:50:52 +0000696 def setannotation(self, *args):
697 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
698 Set ANNOTATIONs."""
699
700 typ, dat = self._simple_command('SETANNOTATION', *args)
701 return self._untagged_response(typ, dat, 'ANNOTATION')
702
703
Piers Lauder3fca2912002-06-17 07:07:20 +0000704 def setquota(self, root, limits):
705 """Set the quota root's resource limits.
706
707 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000708 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000709 typ, dat = self._simple_command('SETQUOTA', root, limits)
710 return self._untagged_response(typ, dat, 'QUOTA')
711
712
Piers Lauder15e5d532001-07-20 10:52:06 +0000713 def sort(self, sort_criteria, charset, *search_criteria):
714 """IMAP4rev1 extension SORT command.
715
716 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
717 """
718 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000719 #if not name in self.capabilities: # Let the server decide!
720 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000721 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000722 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000723 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000724 return self._untagged_response(typ, dat, name)
725
726
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000727 def starttls(self, ssl_context=None):
728 name = 'STARTTLS'
729 if not HAVE_SSL:
730 raise self.error('SSL support missing')
731 if self._tls_established:
732 raise self.abort('TLS session already established')
733 if name not in self.capabilities:
734 raise self.abort('TLS not supported by server')
735 # Generate a default SSL context if none was passed.
736 if ssl_context is None:
737 ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
738 # SSLv2 considered harmful.
739 ssl_context.options |= ssl.OP_NO_SSLv2
740 typ, dat = self._simple_command(name)
741 if typ == 'OK':
742 self.sock = ssl_context.wrap_socket(self.sock)
743 self.file = self.sock.makefile('rb')
744 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000745 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000746 else:
747 raise self.error("Couldn't establish TLS session")
748 return self._untagged_response(typ, dat, name)
749
750
Tim Peters07e99cb2001-01-14 23:47:14 +0000751 def status(self, mailbox, names):
752 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000753
Tim Peters07e99cb2001-01-14 23:47:14 +0000754 (typ, [data]) = <instance>.status(mailbox, names)
755 """
756 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000757 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000758 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 typ, dat = self._simple_command(name, mailbox, names)
760 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000761
762
Tim Peters07e99cb2001-01-14 23:47:14 +0000763 def store(self, message_set, command, flags):
764 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000765
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 (typ, [data]) = <instance>.store(message_set, command, flags)
767 """
768 if (flags[0],flags[-1]) != ('(',')'):
769 flags = '(%s)' % flags # Avoid quoting the flags
770 typ, dat = self._simple_command('STORE', message_set, command, flags)
771 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000772
773
Tim Peters07e99cb2001-01-14 23:47:14 +0000774 def subscribe(self, mailbox):
775 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000776
Tim Peters07e99cb2001-01-14 23:47:14 +0000777 (typ, [data]) = <instance>.subscribe(mailbox)
778 """
779 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000780
781
Martin v. Löwisd8921372003-11-10 06:44:44 +0000782 def thread(self, threading_algorithm, charset, *search_criteria):
783 """IMAPrev1 extension THREAD command.
784
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000785 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000786 """
787 name = 'THREAD'
788 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
789 return self._untagged_response(typ, dat, name)
790
791
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 def uid(self, command, *args):
793 """Execute "command arg ..." with messages identified by UID,
794 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000795
Tim Peters07e99cb2001-01-14 23:47:14 +0000796 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000797
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 Returns response appropriate to 'command'.
799 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000800 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000801 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000802 raise self.error("Unknown IMAP4 UID command: %s" % command)
803 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000804 raise self.error("command %s illegal in state %s, "
805 "only allowed in states %s" %
806 (command, self.state,
807 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000809 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000810 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000811 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 else:
813 name = 'FETCH'
814 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 def unsubscribe(self, mailbox):
818 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 (typ, [data]) = <instance>.unsubscribe(mailbox)
821 """
822 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 def xatom(self, name, *args):
826 """Allow simple extension commands
827 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000828
Piers Lauder15e5d532001-07-20 10:52:06 +0000829 Assumes command is legal in current state.
830
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000832
833 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000835 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000836 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000837 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000838 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000839 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000840 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
842
843
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000845
846
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000848 if dat is None:
849 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 ur = self.untagged_responses
851 if __debug__:
852 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000853 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000855 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 ur[typ].append(dat)
857 else:
858 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000859
860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 def _check_bye(self):
862 bye = self.untagged_responses.get('BYE')
863 if bye:
Antoine Pitroudac47912010-11-10 00:18:40 +0000864 raise self.abort(bye[-1].decode('ascii', 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000865
866
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 if self.state not in Commands[name]:
870 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000871 raise self.error("command %s illegal in state %s, "
872 "only allowed in states %s" %
873 (name, self.state,
874 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000877 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000879
Raymond Hettinger54f02222002-06-01 14:18:47 +0000880 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000881 and not self.is_readonly:
882 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000885 name = bytes(name, 'ASCII')
886 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 for arg in args:
888 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000889 if isinstance(arg, str):
890 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000891 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 literal = self.literal
894 if literal is not None:
895 self.literal = None
896 if type(literal) is type(self._command):
897 literator = literal
898 else:
899 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000900 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
Tim Peters07e99cb2001-01-14 23:47:14 +0000902 if __debug__:
903 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000904 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000906 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000909 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000910 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000911 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000912
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 if literal is None:
914 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 while 1:
917 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 while self._get_response():
920 if self.tagged_commands[tag]: # BAD/NO?
921 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 if literator:
926 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 if __debug__:
929 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000930 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000933 self.send(literal)
934 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000935 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000937
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 if not literator:
939 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
943
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000945 # BYE is expected after LOGOUT
946 if name != 'LOGOUT':
947 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 try:
949 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000950 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000951 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000952 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +0000954 if name != 'LOGOUT':
955 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 if typ == 'BAD':
957 raise self.error('%s command error: %s %s' % (name, typ, data))
958 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000959
960
Antoine Pitroudbe75192010-11-16 17:55:26 +0000961 def _get_capabilities(self):
962 typ, dat = self.capability()
963 if dat == [None]:
964 raise self.error('no CAPABILITY response from server')
965 dat = str(dat[-1], "ASCII")
966 dat = dat.upper()
967 self.capabilities = tuple(dat.split())
968
969
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000971
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 # Read response and store.
973 #
974 # Returns None for continuation responses,
975 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 if self._match(self.tagre, resp):
982 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000983 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000985
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000987 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 dat = self.mo.group('data')
989 self.tagged_commands[tag] = (typ, [dat])
990 else:
991 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 if not self._match(Untagged_response, resp):
996 if self._match(Untagged_status, resp):
997 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
Tim Peters07e99cb2001-01-14 23:47:14 +0000999 if self.mo is None:
1000 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001001
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 if self._match(Continuation, resp):
1003 self.continuation_response = self.mo.group('data')
1004 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001005
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001007
Tim Peters07e99cb2001-01-14 23:47:14 +00001008 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001009 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001011 if dat is None: dat = b'' # Null untagged response
1012 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001013
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001019
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001020 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 if __debug__:
1022 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001023 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001024 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001031
Tim Peters07e99cb2001-01-14 23:47:14 +00001032 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001035
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001039 typ = self.mo.group('type')
1040 typ = str(typ, "ASCII")
1041 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 if __debug__:
1044 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001045 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001046
Tim Peters07e99cb2001-01-14 23:47:14 +00001047 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001048
1049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 while 1:
1053 result = self.tagged_commands[tag]
1054 if result is not None:
1055 del self.tagged_commands[tag]
1056 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 # Some have reported "unexpected response" exceptions.
1059 # Note that ignoring them here causes loops.
1060 # Instead, send me details of the unexpected response and
1061 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001062
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 try:
1064 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001065 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 if __debug__:
1067 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001068 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001070
1071
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
Piers Lauder15e5d532001-07-20 10:52:06 +00001074 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 if not line:
1076 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001077
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001079 if not line.endswith(b'\r\n'):
1080 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001081
Tim Peters07e99cb2001-01-14 23:47:14 +00001082 line = line[:-2]
1083 if __debug__:
1084 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001085 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001086 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001087 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001089
1090
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001092
Tim Peters07e99cb2001-01-14 23:47:14 +00001093 # Run compiled regular expression match method on 's'.
1094 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001095
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 self.mo = cre.match(s)
1097 if __debug__:
1098 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001099 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001100 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001101
1102
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001104
Christian Heimesfb5faf02008-11-05 19:39:50 +00001105 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001106 self.tagnum = self.tagnum + 1
1107 self.tagged_commands[tag] = None
1108 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001109
1110
Tim Peters07e99cb2001-01-14 23:47:14 +00001111 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001112
Antoine Pitroub1436f12010-11-09 22:55:55 +00001113 arg = arg.replace('\\', '\\\\')
1114 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001115
Antoine Pitroub1436f12010-11-09 22:55:55 +00001116 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001117
1118
Tim Peters07e99cb2001-01-14 23:47:14 +00001119 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001120
Guido van Rossum68468eb2003-02-27 20:14:51 +00001121 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001122
1123
Tim Peters07e99cb2001-01-14 23:47:14 +00001124 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001125 if typ == 'NO':
1126 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001127 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001128 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001129 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001130 if __debug__:
1131 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001132 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001134
1135
Piers Lauderf2d7d152002-02-22 01:15:17 +00001136 if __debug__:
1137
1138 def _mesg(self, s, secs=None):
1139 if secs is None:
1140 secs = time.time()
1141 tm = time.strftime('%M:%S', time.localtime(secs))
1142 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1143 sys.stderr.flush()
1144
1145 def _dump_ur(self, dict):
1146 # Dump untagged responses (in `dict').
1147 l = dict.items()
1148 if not l: return
1149 t = '\n\t\t'
1150 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1151 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1152
1153 def _log(self, line):
1154 # Keep log of last `_cmd_log_len' interactions for debugging.
1155 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1156 self._cmd_log_idx += 1
1157 if self._cmd_log_idx >= self._cmd_log_len:
1158 self._cmd_log_idx = 0
1159
1160 def print_log(self):
1161 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1162 i, n = self._cmd_log_idx, self._cmd_log_len
1163 while n:
1164 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001165 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001166 except:
1167 pass
1168 i += 1
1169 if i >= self._cmd_log_len:
1170 i = 0
1171 n -= 1
1172
1173
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001174if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001175
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001176 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001177
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001178 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001179
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001180 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001181
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001182 host - host's name (default: localhost);
1183 port - port number (default: standard IMAP4 SSL port).
1184 keyfile - PEM formatted file that contains your private key (default: None);
1185 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001186
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001187 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001188 """
Piers Laudera4f83132002-03-08 01:53:24 +00001189
1190
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001191 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1192 self.keyfile = keyfile
1193 self.certfile = certfile
1194 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001195
Christian Heimesfb5faf02008-11-05 19:39:50 +00001196 def _create_socket(self):
1197 sock = IMAP4._create_socket(self)
1198 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001199
Christian Heimesfb5faf02008-11-05 19:39:50 +00001200 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001201 """Setup connection to remote server on "host:port".
1202 (default: localhost:standard IMAP4 SSL port).
1203 This connection will be used by the routines:
1204 read, readline, send, shutdown.
1205 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001206 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001207
1208 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001209
1210
Piers Laudere0273de2002-11-22 05:53:04 +00001211class IMAP4_stream(IMAP4):
1212
1213 """IMAP4 client class over a stream
1214
1215 Instantiate with: IMAP4_stream(command)
1216
Christian Heimesfb5faf02008-11-05 19:39:50 +00001217 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001218
1219 for more documentation see the docstring of the parent class IMAP4.
1220 """
1221
1222
1223 def __init__(self, command):
1224 self.command = command
1225 IMAP4.__init__(self)
1226
1227
1228 def open(self, host = None, port = None):
1229 """Setup a stream connection.
1230 This connection will be used by the routines:
1231 read, readline, send, shutdown.
1232 """
1233 self.host = None # For compatibility with parent class
1234 self.port = None
1235 self.sock = None
1236 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001237 self.process = subprocess.Popen(self.command,
1238 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1239 shell=True, close_fds=True)
1240 self.writefile = self.process.stdin
1241 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001242
1243 def read(self, size):
1244 """Read 'size' bytes from remote."""
1245 return self.readfile.read(size)
1246
1247
1248 def readline(self):
1249 """Read line from remote."""
1250 return self.readfile.readline()
1251
1252
1253 def send(self, data):
1254 """Send data to remote."""
1255 self.writefile.write(data)
1256 self.writefile.flush()
1257
1258
1259 def shutdown(self):
1260 """Close I/O established in "open"."""
1261 self.readfile.close()
1262 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001263 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001264
1265
1266
Guido van Rossumeda960a1998-06-18 14:24:28 +00001267class _Authenticator:
1268
Tim Peters07e99cb2001-01-14 23:47:14 +00001269 """Private class to provide en/decoding
1270 for base64-based authentication conversation.
1271 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001272
Tim Peters07e99cb2001-01-14 23:47:14 +00001273 def __init__(self, mechinst):
1274 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001275
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 def process(self, data):
1277 ret = self.mech(self.decode(data))
1278 if ret is None:
1279 return '*' # Abort conversation
1280 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001281
Tim Peters07e99cb2001-01-14 23:47:14 +00001282 def encode(self, inp):
1283 #
1284 # Invoke binascii.b2a_base64 iteratively with
1285 # short even length buffers, strip the trailing
1286 # line feed from the result and append. "Even"
1287 # means a number that factors to both 6 and 8,
1288 # so when it gets to the end of the 8-bit input
1289 # there's no partial 6-bit output.
1290 #
1291 oup = ''
1292 while inp:
1293 if len(inp) > 48:
1294 t = inp[:48]
1295 inp = inp[48:]
1296 else:
1297 t = inp
1298 inp = ''
1299 e = binascii.b2a_base64(t)
1300 if e:
1301 oup = oup + e[:-1]
1302 return oup
1303
1304 def decode(self, inp):
1305 if not inp:
1306 return ''
1307 return binascii.a2b_base64(inp)
1308
Guido van Rossumeda960a1998-06-18 14:24:28 +00001309
1310
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001311Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001312 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001313
1314def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001315 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001316
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001317 Return corresponding local time. The return value is a
1318 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001319 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001320
Tim Peters07e99cb2001-01-14 23:47:14 +00001321 mo = InternalDate.match(resp)
1322 if not mo:
1323 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001324
Tim Peters07e99cb2001-01-14 23:47:14 +00001325 mon = Mon2num[mo.group('mon')]
1326 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001327
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001328 day = int(mo.group('day'))
1329 year = int(mo.group('year'))
1330 hour = int(mo.group('hour'))
1331 min = int(mo.group('min'))
1332 sec = int(mo.group('sec'))
1333 zoneh = int(mo.group('zoneh'))
1334 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001335
Tim Peters07e99cb2001-01-14 23:47:14 +00001336 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Tim Peters07e99cb2001-01-14 23:47:14 +00001338 zone = (zoneh*60 + zonem)*60
1339 if zonen == '-':
1340 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001341
Tim Peters07e99cb2001-01-14 23:47:14 +00001342 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001345
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 # Following is necessary because the time module has no 'mkgmtime'.
1347 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001348
Tim Peters07e99cb2001-01-14 23:47:14 +00001349 lt = time.localtime(utc)
1350 if time.daylight and lt[-1]:
1351 zone = zone + time.altzone
1352 else:
1353 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001354
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001356
1357
1358
1359def Int2AP(num):
1360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001362
Christian Heimesfb5faf02008-11-05 19:39:50 +00001363 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001364 num = int(abs(num))
1365 while num:
1366 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001367 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001368 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001369
1370
1371
1372def ParseFlags(resp):
1373
Tim Peters07e99cb2001-01-14 23:47:14 +00001374 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001375
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 mo = Flags.match(resp)
1377 if not mo:
1378 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001379
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001380 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001381
1382
1383def Time2Internaldate(date_time):
1384
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001385 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001386
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001387 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
1388 date_time argument can be a number (int or float) represening
1389 seconds since epoch (as returned by time.time()), a 9-tuple
1390 representing local time (as returned by time.localtime()), or a
1391 double-quoted string. In the last case, it is assumed to already
1392 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001394
Fred Drakedb519202002-01-05 17:17:09 +00001395 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001397 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001398 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001399 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001400 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001401 else:
1402 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001403
Tim Peters07e99cb2001-01-14 23:47:14 +00001404 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1405 if dt[0] == '0':
1406 dt = ' ' + dt[1:]
1407 if time.daylight and tt[-1]:
1408 zone = -time.altzone
1409 else:
1410 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001411 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
1413
1414
Guido van Rossum8c062211999-12-13 23:27:45 +00001415if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001416
Piers Laudere0273de2002-11-22 05:53:04 +00001417 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1418 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1419 # to test the IMAP4_stream class
1420
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001421 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001422
Tim Peters07e99cb2001-01-14 23:47:14 +00001423 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001424 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001425 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001426 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001427
Piers Laudere0273de2002-11-22 05:53:04 +00001428 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001429 for opt,val in optlist:
1430 if opt == '-d':
1431 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001432 elif opt == '-s':
1433 stream_command = val
1434 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001435
Tim Peters07e99cb2001-01-14 23:47:14 +00001436 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001437
Tim Peters07e99cb2001-01-14 23:47:14 +00001438 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001439
Tim Peters07e99cb2001-01-14 23:47:14 +00001440 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001441 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001442
Piers Lauder47404ff2003-04-29 23:40:59 +00001443 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 +00001444 test_seq1 = (
1445 ('login', (USER, PASSWD)),
1446 ('create', ('/tmp/xxx 1',)),
1447 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1448 ('CREATE', ('/tmp/yyz 2',)),
1449 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1450 ('list', ('/tmp', 'yy*')),
1451 ('select', ('/tmp/yyz 2',)),
1452 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001453 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001455 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001456 ('expunge', ()),
1457 ('recent', ()),
1458 ('close', ()),
1459 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001460
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 test_seq2 = (
1462 ('select', ()),
1463 ('response',('UIDVALIDITY',)),
1464 ('uid', ('SEARCH', 'ALL')),
1465 ('response', ('EXISTS',)),
1466 ('append', (None, None, None, test_mesg)),
1467 ('recent', ()),
1468 ('logout', ()),
1469 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001470
Tim Peters07e99cb2001-01-14 23:47:14 +00001471 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001472 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001473 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001474 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001475 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001476 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001477
Tim Peters07e99cb2001-01-14 23:47:14 +00001478 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001479 if stream_command:
1480 M = IMAP4_stream(stream_command)
1481 else:
1482 M = IMAP4(host)
1483 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001484 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001485 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001486 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001487
Tim Peters07e99cb2001-01-14 23:47:14 +00001488 for cmd,args in test_seq1:
1489 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001490
Tim Peters07e99cb2001-01-14 23:47:14 +00001491 for ml in run('list', ('/tmp/', 'yy%')):
1492 mo = re.match(r'.*"([^"]+)"$', ml)
1493 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001494 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001495 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001496
Tim Peters07e99cb2001-01-14 23:47:14 +00001497 for cmd,args in test_seq2:
1498 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001499
Tim Peters07e99cb2001-01-14 23:47:14 +00001500 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1501 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001502
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001503 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001504 if not uid: continue
1505 run('uid', ('FETCH', '%s' % uid[-1],
1506 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001507
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001508 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001509
Tim Peters07e99cb2001-01-14 23:47:14 +00001510 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001511 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001512
Tim Peters07e99cb2001-01-14 23:47:14 +00001513 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001514 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001515If you would like to see debugging output,
1516try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001517''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001518
Tim Peters07e99cb2001-01-14 23:47:14 +00001519 raise