blob: 7c28d037804d6721882ae5754a832d31293798ea [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.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000018
Piers Lauder15e5d532001-07-20 10:52:06 +000019__version__ = "2.47"
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000020
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +000021import binascii, re, socket, time, random, sys
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000022
Barry Warsawf4493912001-01-24 04:16:09 +000023__all__ = ["IMAP4", "Internaldate2tuple",
24 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000025
Tim Peters07e99cb2001-01-14 23:47:14 +000026# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000027
28CRLF = '\r\n'
29Debug = 0
30IMAP4_PORT = 143
Tim Peters07e99cb2001-01-14 23:47:14 +000031AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000032
Tim Peters07e99cb2001-01-14 23:47:14 +000033# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000034
35Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000036 # name valid states
37 'APPEND': ('AUTH', 'SELECTED'),
38 'AUTHENTICATE': ('NONAUTH',),
39 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
40 'CHECK': ('SELECTED',),
41 'CLOSE': ('SELECTED',),
42 'COPY': ('SELECTED',),
43 'CREATE': ('AUTH', 'SELECTED'),
44 'DELETE': ('AUTH', 'SELECTED'),
45 'EXAMINE': ('AUTH', 'SELECTED'),
46 'EXPUNGE': ('SELECTED',),
47 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000048 'GETACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000049 'LIST': ('AUTH', 'SELECTED'),
50 'LOGIN': ('NONAUTH',),
51 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
52 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000053 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000054 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
55 'PARTIAL': ('SELECTED',),
56 'RENAME': ('AUTH', 'SELECTED'),
57 'SEARCH': ('SELECTED',),
58 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000059 'SETACL': ('AUTH', 'SELECTED'),
60 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000061 'STATUS': ('AUTH', 'SELECTED'),
62 'STORE': ('SELECTED',),
63 'SUBSCRIBE': ('AUTH', 'SELECTED'),
64 'UID': ('SELECTED',),
65 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
66 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000067
Tim Peters07e99cb2001-01-14 23:47:14 +000068# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000069
Guido van Rossumeda960a1998-06-18 14:24:28 +000070Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000071Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
72InternalDate = re.compile(r'.*INTERNALDATE "'
Tim Peters07e99cb2001-01-14 23:47:14 +000073 r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
74 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
75 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
76 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000077Literal = re.compile(r'.*{(?P<size>\d+)}$')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000078Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000079Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000080Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
81
82
83
84class IMAP4:
85
Tim Peters07e99cb2001-01-14 23:47:14 +000086 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000087
Tim Peters07e99cb2001-01-14 23:47:14 +000088 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000089
Tim Peters07e99cb2001-01-14 23:47:14 +000090 host - host's name (default: localhost);
91 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000092
Tim Peters07e99cb2001-01-14 23:47:14 +000093 All IMAP4rev1 commands are supported by methods of the same
94 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +000095
Tim Peters07e99cb2001-01-14 23:47:14 +000096 All arguments to commands are converted to strings, except for
97 AUTHENTICATE, and the last argument to APPEND which is passed as
98 an IMAP4 literal. If necessary (the string contains any
99 non-printing characters or white-space and isn't enclosed with
100 either parentheses or double quotes) each string is quoted.
101 However, the 'password' argument to the LOGIN command is always
102 quoted. If you want to avoid having an argument string quoted
103 (eg: the 'flags' argument to STORE) then enclose the string in
104 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000105
Tim Peters07e99cb2001-01-14 23:47:14 +0000106 Each command returns a tuple: (type, [data, ...]) where 'type'
107 is usually 'OK' or 'NO', and 'data' is either the text from the
108 tagged response, or untagged results from command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000109
Tim Peters07e99cb2001-01-14 23:47:14 +0000110 Errors raise the exception class <instance>.error("<reason>").
111 IMAP4 server errors raise <instance>.abort("<reason>"),
112 which is a sub-class of 'error'. Mailbox status changes
113 from READ-WRITE to READ-ONLY raise the exception class
114 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000115
Tim Peters07e99cb2001-01-14 23:47:14 +0000116 "error" exceptions imply a program error.
117 "abort" exceptions imply the connection should be reset, and
118 the command re-tried.
119 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000120
Tim Peters07e99cb2001-01-14 23:47:14 +0000121 Note: to use this module, you must read the RFCs pertaining
122 to the IMAP4 protocol, as the semantics of the arguments to
123 each IMAP4 command are left to the invoker, not to mention
124 the results.
125 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000126
Tim Peters07e99cb2001-01-14 23:47:14 +0000127 class error(Exception): pass # Logical errors - debug required
128 class abort(error): pass # Service errors - close and retry
129 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000130
Tim Peters07e99cb2001-01-14 23:47:14 +0000131 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000132
Tim Peters07e99cb2001-01-14 23:47:14 +0000133 def __init__(self, host = '', port = IMAP4_PORT):
134 self.host = host
135 self.port = port
136 self.debug = Debug
137 self.state = 'LOGOUT'
138 self.literal = None # A literal argument to a command
139 self.tagged_commands = {} # Tagged commands awaiting response
140 self.untagged_responses = {} # {typ: [data, ...], ...}
141 self.continuation_response = '' # Last continuation response
142 self.is_readonly = None # READ-ONLY desired state
143 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000144
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000146
Tim Peters07e99cb2001-01-14 23:47:14 +0000147 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000148
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 # Create unique tag for this session,
150 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000151
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 self.tagpre = Int2AP(random.randint(0, 31999))
153 self.tagre = re.compile(r'(?P<tag>'
154 + self.tagpre
155 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000156
Tim Peters07e99cb2001-01-14 23:47:14 +0000157 # Get server welcome message,
158 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 if __debug__:
161 if self.debug >= 1:
Piers Lauder15e5d532001-07-20 10:52:06 +0000162 _mesg('imaplib version %s' % __version__)
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 _mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000164
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 self.welcome = self._get_response()
166 if self.untagged_responses.has_key('PREAUTH'):
167 self.state = 'AUTH'
168 elif self.untagged_responses.has_key('OK'):
169 self.state = 'NONAUTH'
170 else:
171 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000172
Tim Peters07e99cb2001-01-14 23:47:14 +0000173 cap = 'CAPABILITY'
174 self._simple_command(cap)
175 if not self.untagged_responses.has_key(cap):
176 raise self.error('no CAPABILITY response from server')
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000177 self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000178
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 if __debug__:
180 if self.debug >= 3:
181 _mesg('CAPABILITIES: %s' % `self.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000182
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 for version in AllowedVersions:
184 if not version in self.capabilities:
185 continue
186 self.PROTOCOL_VERSION = version
187 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000188
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000190
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000191
Tim Peters07e99cb2001-01-14 23:47:14 +0000192 def __getattr__(self, attr):
193 # Allow UPPERCASE variants of IMAP4 command methods.
194 if Commands.has_key(attr):
Piers Lauder15e5d532001-07-20 10:52:06 +0000195 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000196 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000197
198
199
Piers Lauder15e5d532001-07-20 10:52:06 +0000200 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000201
202
Tim Peters07e99cb2001-01-14 23:47:14 +0000203 def open(self, host, port):
Piers Lauder15e5d532001-07-20 10:52:06 +0000204 """Setup connection to remote server on "host:port".
205 This connection will be used by the routines:
206 read, readline, send, shutdown.
207 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
209 self.sock.connect((self.host, self.port))
210 self.file = self.sock.makefile('r')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000211
212
Piers Lauder15e5d532001-07-20 10:52:06 +0000213 def read(self, size):
214 """Read 'size' bytes from remote."""
215 return self.file.read(size)
216
217
218 def readline(self):
219 """Read line from remote."""
220 return self.file.readline()
221
222
223 def send(self, data):
224 """Send data to remote."""
225 self.sock.send(data)
226
227
228 def shutdown(self):
229 """Close I/O established in "open"."""
230 self.file.close()
231 self.sock.close()
232
233
234 def socket(self):
235 """Return socket instance used to connect to IMAP4 server.
236
237 socket = <instance>.socket()
238 """
239 return self.sock
240
241
242
243 # Utility methods
244
245
Tim Peters07e99cb2001-01-14 23:47:14 +0000246 def recent(self):
247 """Return most recent 'RECENT' responses if any exist,
248 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000249
Tim Peters07e99cb2001-01-14 23:47:14 +0000250 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000251
Tim Peters07e99cb2001-01-14 23:47:14 +0000252 'data' is None if no new messages,
253 else list of RECENT responses, most recent last.
254 """
255 name = 'RECENT'
256 typ, dat = self._untagged_response('OK', [None], name)
257 if dat[-1]:
258 return typ, dat
259 typ, dat = self.noop() # Prod server for response
260 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000261
262
Tim Peters07e99cb2001-01-14 23:47:14 +0000263 def response(self, code):
264 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000265
Tim Peters07e99cb2001-01-14 23:47:14 +0000266 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000267
Tim Peters07e99cb2001-01-14 23:47:14 +0000268 (code, [data]) = <instance>.response(code)
269 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000270 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000271
272
Guido van Rossum26367a01998-09-28 15:34:46 +0000273
Tim Peters07e99cb2001-01-14 23:47:14 +0000274 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000275
276
Tim Peters07e99cb2001-01-14 23:47:14 +0000277 def append(self, mailbox, flags, date_time, message):
278 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000279
Tim Peters07e99cb2001-01-14 23:47:14 +0000280 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000281
Tim Peters07e99cb2001-01-14 23:47:14 +0000282 All args except `message' can be None.
283 """
284 name = 'APPEND'
285 if not mailbox:
286 mailbox = 'INBOX'
287 if flags:
288 if (flags[0],flags[-1]) != ('(',')'):
289 flags = '(%s)' % flags
290 else:
291 flags = None
292 if date_time:
293 date_time = Time2Internaldate(date_time)
294 else:
295 date_time = None
296 self.literal = message
297 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000298
299
Tim Peters07e99cb2001-01-14 23:47:14 +0000300 def authenticate(self, mechanism, authobject):
301 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000302
Tim Peters07e99cb2001-01-14 23:47:14 +0000303 'mechanism' specifies which authentication mechanism is to
304 be used - it must appear in <instance>.capabilities in the
305 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000306
Tim Peters07e99cb2001-01-14 23:47:14 +0000307 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000310
Tim Peters07e99cb2001-01-14 23:47:14 +0000311 It will be called to process server continuation responses.
312 It should return data that will be encoded and sent to server.
313 It should return None if the client abort response '*' should
314 be sent instead.
315 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000316 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 cap = 'AUTH=%s' % mech
318 if not cap in self.capabilities:
319 raise self.error("Server doesn't allow %s authentication." % mech)
320 self.literal = _Authenticator(authobject).process
321 typ, dat = self._simple_command('AUTHENTICATE', mech)
322 if typ != 'OK':
323 raise self.error(dat[-1])
324 self.state = 'AUTH'
325 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000326
327
Tim Peters07e99cb2001-01-14 23:47:14 +0000328 def check(self):
329 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000330
Tim Peters07e99cb2001-01-14 23:47:14 +0000331 (typ, [data]) = <instance>.check()
332 """
333 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000334
335
Tim Peters07e99cb2001-01-14 23:47:14 +0000336 def close(self):
337 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000338
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 Deleted messages are removed from writable mailbox.
340 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000341
Tim Peters07e99cb2001-01-14 23:47:14 +0000342 (typ, [data]) = <instance>.close()
343 """
344 try:
345 typ, dat = self._simple_command('CLOSE')
346 finally:
347 self.state = 'AUTH'
348 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000349
350
Tim Peters07e99cb2001-01-14 23:47:14 +0000351 def copy(self, message_set, new_mailbox):
352 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000353
Tim Peters07e99cb2001-01-14 23:47:14 +0000354 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
355 """
356 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000357
358
Tim Peters07e99cb2001-01-14 23:47:14 +0000359 def create(self, mailbox):
360 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000361
Tim Peters07e99cb2001-01-14 23:47:14 +0000362 (typ, [data]) = <instance>.create(mailbox)
363 """
364 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000365
366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 def delete(self, mailbox):
368 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000369
Tim Peters07e99cb2001-01-14 23:47:14 +0000370 (typ, [data]) = <instance>.delete(mailbox)
371 """
372 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000373
374
Tim Peters07e99cb2001-01-14 23:47:14 +0000375 def expunge(self):
376 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000377
Tim Peters07e99cb2001-01-14 23:47:14 +0000378 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 'data' is list of 'EXPUNGE'd message numbers in order received.
383 """
384 name = 'EXPUNGE'
385 typ, dat = self._simple_command(name)
386 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000387
388
Tim Peters07e99cb2001-01-14 23:47:14 +0000389 def fetch(self, message_set, message_parts):
390 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000391
Tim Peters07e99cb2001-01-14 23:47:14 +0000392 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
Tim Peters07e99cb2001-01-14 23:47:14 +0000394 'message_parts' should be a string of selected parts
395 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 'data' are tuples of message part envelope and data.
398 """
399 name = 'FETCH'
400 typ, dat = self._simple_command(name, message_set, message_parts)
401 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000402
403
Piers Lauder15e5d532001-07-20 10:52:06 +0000404 def getacl(self, mailbox):
405 """Get the ACLs for a mailbox.
406
407 (typ, [data]) = <instance>.getacl(mailbox)
408 """
409 typ, dat = self._simple_command('GETACL', mailbox)
410 return self._untagged_response(typ, dat, 'ACL')
411
412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 def list(self, directory='""', pattern='*'):
414 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 'data' is list of LIST responses.
419 """
420 name = 'LIST'
421 typ, dat = self._simple_command(name, directory, pattern)
422 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000423
424
Tim Peters07e99cb2001-01-14 23:47:14 +0000425 def login(self, user, password):
426 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 NB: 'password' will be quoted.
431 """
432 #if not 'AUTH=LOGIN' in self.capabilities:
433 # raise self.error("Server doesn't allow LOGIN authentication." % mech)
434 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
435 if typ != 'OK':
436 raise self.error(dat[-1])
437 self.state = 'AUTH'
438 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000439
440
Tim Peters07e99cb2001-01-14 23:47:14 +0000441 def logout(self):
442 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000445
Tim Peters07e99cb2001-01-14 23:47:14 +0000446 Returns server 'BYE' response.
447 """
448 self.state = 'LOGOUT'
449 try: typ, dat = self._simple_command('LOGOUT')
450 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000451 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 if self.untagged_responses.has_key('BYE'):
453 return 'BYE', self.untagged_responses['BYE']
454 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000455
456
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 def lsub(self, directory='""', pattern='*'):
458 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000459
Tim Peters07e99cb2001-01-14 23:47:14 +0000460 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
Tim Peters07e99cb2001-01-14 23:47:14 +0000462 'data' are tuples of message part envelope and data.
463 """
464 name = 'LSUB'
465 typ, dat = self._simple_command(name, directory, pattern)
466 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000467
468
Piers Lauder15e5d532001-07-20 10:52:06 +0000469 def namespace(self):
470 """ Returns IMAP namespaces ala rfc2342
471
472 (typ, [data, ...]) = <instance>.namespace()
473 """
474 name = 'NAMESPACE'
475 typ, dat = self._simple_command(name)
476 return self._untagged_response(typ, dat, name)
477
478
Tim Peters07e99cb2001-01-14 23:47:14 +0000479 def noop(self):
480 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000481
Tim Peters07e99cb2001-01-14 23:47:14 +0000482 (typ, data) = <instance>.noop()
483 """
484 if __debug__:
485 if self.debug >= 3:
486 _dump_ur(self.untagged_responses)
487 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000488
489
Tim Peters07e99cb2001-01-14 23:47:14 +0000490 def partial(self, message_num, message_part, start, length):
491 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000492
Tim Peters07e99cb2001-01-14 23:47:14 +0000493 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000494
Tim Peters07e99cb2001-01-14 23:47:14 +0000495 'data' is tuple of message part envelope and data.
496 """
497 name = 'PARTIAL'
498 typ, dat = self._simple_command(name, message_num, message_part, start, length)
499 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000500
501
Tim Peters07e99cb2001-01-14 23:47:14 +0000502 def rename(self, oldmailbox, newmailbox):
503 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000504
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 (typ, data) = <instance>.rename(oldmailbox, newmailbox)
506 """
507 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000508
509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 def search(self, charset, *criteria):
511 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000512
Tim Peters07e99cb2001-01-14 23:47:14 +0000513 (typ, [data]) = <instance>.search(charset, criterium, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 'data' is space separated list of matching message numbers.
516 """
517 name = 'SEARCH'
518 if charset:
Piers Lauder15e5d532001-07-20 10:52:06 +0000519 typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria)
520 else:
521 typ, dat = apply(self._simple_command, (name,) + criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000522 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000523
524
Tim Peters07e99cb2001-01-14 23:47:14 +0000525 def select(self, mailbox='INBOX', readonly=None):
526 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000527
Tim Peters07e99cb2001-01-14 23:47:14 +0000528 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000529
Tim Peters07e99cb2001-01-14 23:47:14 +0000530 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000531
Tim Peters07e99cb2001-01-14 23:47:14 +0000532 'data' is count of messages in mailbox ('EXISTS' response).
533 """
534 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
535 self.untagged_responses = {} # Flush old responses.
536 self.is_readonly = readonly
537 if readonly:
538 name = 'EXAMINE'
539 else:
540 name = 'SELECT'
541 typ, dat = self._simple_command(name, mailbox)
542 if typ != 'OK':
543 self.state = 'AUTH' # Might have been 'SELECTED'
544 return typ, dat
545 self.state = 'SELECTED'
546 if self.untagged_responses.has_key('READ-ONLY') \
547 and not readonly:
548 if __debug__:
549 if self.debug >= 1:
550 _dump_ur(self.untagged_responses)
551 raise self.readonly('%s is not writable' % mailbox)
552 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000553
554
Piers Lauder15e5d532001-07-20 10:52:06 +0000555 def setacl(self, mailbox, who, what):
556 """Set a mailbox acl.
557
558 (typ, [data]) = <instance>.create(mailbox, who, what)
559 """
560 return self._simple_command('SETACL', mailbox, who, what)
561
562
563 def sort(self, sort_criteria, charset, *search_criteria):
564 """IMAP4rev1 extension SORT command.
565
566 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
567 """
568 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000569 #if not name in self.capabilities: # Let the server decide!
570 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000571 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000572 sort_criteria = '(%s)' % sort_criteria
Piers Lauder15e5d532001-07-20 10:52:06 +0000573 typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria)
574 return self._untagged_response(typ, dat, name)
575
576
Tim Peters07e99cb2001-01-14 23:47:14 +0000577 def status(self, mailbox, names):
578 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000579
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 (typ, [data]) = <instance>.status(mailbox, names)
581 """
582 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000583 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000584 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 typ, dat = self._simple_command(name, mailbox, names)
586 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000587
588
Tim Peters07e99cb2001-01-14 23:47:14 +0000589 def store(self, message_set, command, flags):
590 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000591
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 (typ, [data]) = <instance>.store(message_set, command, flags)
593 """
594 if (flags[0],flags[-1]) != ('(',')'):
595 flags = '(%s)' % flags # Avoid quoting the flags
596 typ, dat = self._simple_command('STORE', message_set, command, flags)
597 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000598
599
Tim Peters07e99cb2001-01-14 23:47:14 +0000600 def subscribe(self, mailbox):
601 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000602
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 (typ, [data]) = <instance>.subscribe(mailbox)
604 """
605 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000606
607
Tim Peters07e99cb2001-01-14 23:47:14 +0000608 def uid(self, command, *args):
609 """Execute "command arg ..." with messages identified by UID,
610 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000611
Tim Peters07e99cb2001-01-14 23:47:14 +0000612 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000613
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 Returns response appropriate to 'command'.
615 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000616 command = command.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 if not Commands.has_key(command):
618 raise self.error("Unknown IMAP4 UID command: %s" % command)
619 if self.state not in Commands[command]:
620 raise self.error('command %s illegal in state %s'
621 % (command, self.state))
622 name = 'UID'
623 typ, dat = apply(self._simple_command, (name, command) + args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000624 if command in ('SEARCH', 'SORT'):
625 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000626 else:
627 name = 'FETCH'
628 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000629
630
Tim Peters07e99cb2001-01-14 23:47:14 +0000631 def unsubscribe(self, mailbox):
632 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000633
Tim Peters07e99cb2001-01-14 23:47:14 +0000634 (typ, [data]) = <instance>.unsubscribe(mailbox)
635 """
636 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000637
638
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 def xatom(self, name, *args):
640 """Allow simple extension commands
641 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
Piers Lauder15e5d532001-07-20 10:52:06 +0000643 Assumes command is legal in current state.
644
Tim Peters07e99cb2001-01-14 23:47:14 +0000645 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000646
647 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000648 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000649 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000650 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000651 # raise self.error('unknown extension command: %s' % name)
652 if not Commands.has_key(name):
653 Commands[name] = (self.state,)
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 return apply(self._simple_command, (name,) + args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000655
656
657
Tim Peters07e99cb2001-01-14 23:47:14 +0000658 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000659
660
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000662
Tim Peters07e99cb2001-01-14 23:47:14 +0000663 if dat is None: dat = ''
664 ur = self.untagged_responses
665 if __debug__:
666 if self.debug >= 5:
667 _mesg('untagged_responses[%s] %s += ["%s"]' %
668 (typ, len(ur.get(typ,'')), dat))
669 if ur.has_key(typ):
670 ur[typ].append(dat)
671 else:
672 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000673
674
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 def _check_bye(self):
676 bye = self.untagged_responses.get('BYE')
677 if bye:
678 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000679
680
Tim Peters07e99cb2001-01-14 23:47:14 +0000681 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000682
Tim Peters07e99cb2001-01-14 23:47:14 +0000683 if self.state not in Commands[name]:
684 self.literal = None
685 raise self.error(
686 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000687
Tim Peters07e99cb2001-01-14 23:47:14 +0000688 for typ in ('OK', 'NO', 'BAD'):
689 if self.untagged_responses.has_key(typ):
690 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000691
Tim Peters07e99cb2001-01-14 23:47:14 +0000692 if self.untagged_responses.has_key('READ-ONLY') \
693 and not self.is_readonly:
694 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000695
Tim Peters07e99cb2001-01-14 23:47:14 +0000696 tag = self._new_tag()
697 data = '%s %s' % (tag, name)
698 for arg in args:
699 if arg is None: continue
700 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000701
Tim Peters07e99cb2001-01-14 23:47:14 +0000702 literal = self.literal
703 if literal is not None:
704 self.literal = None
705 if type(literal) is type(self._command):
706 literator = literal
707 else:
708 literator = None
709 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000710
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 if __debug__:
712 if self.debug >= 4:
713 _mesg('> %s' % data)
714 else:
715 _log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000716
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000718 self.send('%s%s' % (data, CRLF))
719 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000721
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 if literal is None:
723 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000724
Tim Peters07e99cb2001-01-14 23:47:14 +0000725 while 1:
726 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
Tim Peters07e99cb2001-01-14 23:47:14 +0000728 while self._get_response():
729 if self.tagged_commands[tag]: # BAD/NO?
730 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000731
Tim Peters07e99cb2001-01-14 23:47:14 +0000732 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000733
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 if literator:
735 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 if __debug__:
738 if self.debug >= 4:
739 _mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000740
Tim Peters07e99cb2001-01-14 23:47:14 +0000741 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000742 self.send(literal)
743 self.send(CRLF)
744 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000745 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000746
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 if not literator:
748 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000749
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000751
752
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 def _command_complete(self, name, tag):
754 self._check_bye()
755 try:
756 typ, data = self._get_tagged_response(tag)
757 except self.abort, val:
758 raise self.abort('command: %s => %s' % (name, val))
759 except self.error, val:
760 raise self.error('command: %s => %s' % (name, val))
761 self._check_bye()
762 if typ == 'BAD':
763 raise self.error('%s command error: %s %s' % (name, typ, data))
764 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000765
766
Tim Peters07e99cb2001-01-14 23:47:14 +0000767 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000768
Tim Peters07e99cb2001-01-14 23:47:14 +0000769 # Read response and store.
770 #
771 # Returns None for continuation responses,
772 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000773
Tim Peters07e99cb2001-01-14 23:47:14 +0000774 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000775
Tim Peters07e99cb2001-01-14 23:47:14 +0000776 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000777
Tim Peters07e99cb2001-01-14 23:47:14 +0000778 if self._match(self.tagre, resp):
779 tag = self.mo.group('tag')
780 if not self.tagged_commands.has_key(tag):
781 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000782
Tim Peters07e99cb2001-01-14 23:47:14 +0000783 typ = self.mo.group('type')
784 dat = self.mo.group('data')
785 self.tagged_commands[tag] = (typ, [dat])
786 else:
787 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000788
Tim Peters07e99cb2001-01-14 23:47:14 +0000789 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000790
Tim Peters07e99cb2001-01-14 23:47:14 +0000791 if not self._match(Untagged_response, resp):
792 if self._match(Untagged_status, resp):
793 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000794
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 if self.mo is None:
796 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000797
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 if self._match(Continuation, resp):
799 self.continuation_response = self.mo.group('data')
800 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000801
Tim Peters07e99cb2001-01-14 23:47:14 +0000802 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000803
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 typ = self.mo.group('type')
805 dat = self.mo.group('data')
806 if dat is None: dat = '' # Null untagged response
807 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000808
Tim Peters07e99cb2001-01-14 23:47:14 +0000809 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000810
Tim Peters07e99cb2001-01-14 23:47:14 +0000811 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000814
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000815 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 if __debug__:
817 if self.debug >= 4:
818 _mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000819 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000820
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000822
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000828
Tim Peters07e99cb2001-01-14 23:47:14 +0000829 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000830
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
834 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 if __debug__:
837 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
838 _mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000839
Tim Peters07e99cb2001-01-14 23:47:14 +0000840 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000844
Tim Peters07e99cb2001-01-14 23:47:14 +0000845 while 1:
846 result = self.tagged_commands[tag]
847 if result is not None:
848 del self.tagged_commands[tag]
849 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000850
Tim Peters07e99cb2001-01-14 23:47:14 +0000851 # Some have reported "unexpected response" exceptions.
852 # Note that ignoring them here causes loops.
853 # Instead, send me details of the unexpected response and
854 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000855
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 try:
857 self._get_response()
858 except self.abort, val:
859 if __debug__:
860 if self.debug >= 1:
861 print_log()
862 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000863
864
Tim Peters07e99cb2001-01-14 23:47:14 +0000865 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000866
Piers Lauder15e5d532001-07-20 10:52:06 +0000867 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 if not line:
869 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000872
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 line = line[:-2]
874 if __debug__:
875 if self.debug >= 4:
876 _mesg('< %s' % line)
877 else:
878 _log('< %s' % line)
879 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000880
881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 # Run compiled regular expression match method on 's'.
885 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 self.mo = cre.match(s)
888 if __debug__:
889 if self.mo is not None and self.debug >= 5:
890 _mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
891 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000892
893
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000895
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 tag = '%s%s' % (self.tagpre, self.tagnum)
897 self.tagnum = self.tagnum + 1
898 self.tagged_commands[tag] = None
899 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000900
901
Tim Peters07e99cb2001-01-14 23:47:14 +0000902 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000903
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 # Must quote command args if non-alphanumeric chars present,
905 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000906
Tim Peters07e99cb2001-01-14 23:47:14 +0000907 if type(arg) is not type(''):
908 return arg
909 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
910 return arg
911 if self.mustquote.search(arg) is None:
912 return arg
913 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000914
915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000917
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000918 arg = arg.replace('\\', '\\\\')
919 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000922
923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 return self._command_complete(name, apply(self._command, (name,) + args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000927
928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 if typ == 'NO':
932 return typ, dat
933 if not self.untagged_responses.has_key(name):
934 return typ, [None]
935 data = self.untagged_responses[name]
936 if __debug__:
937 if self.debug >= 5:
938 _mesg('untagged_responses[%s] => %s' % (name, data))
939 del self.untagged_responses[name]
940 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000941
942
943
Guido van Rossumeda960a1998-06-18 14:24:28 +0000944class _Authenticator:
945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 """Private class to provide en/decoding
947 for base64-based authentication conversation.
948 """
Guido van Rossumeda960a1998-06-18 14:24:28 +0000949
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 def __init__(self, mechinst):
951 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 def process(self, data):
954 ret = self.mech(self.decode(data))
955 if ret is None:
956 return '*' # Abort conversation
957 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 def encode(self, inp):
960 #
961 # Invoke binascii.b2a_base64 iteratively with
962 # short even length buffers, strip the trailing
963 # line feed from the result and append. "Even"
964 # means a number that factors to both 6 and 8,
965 # so when it gets to the end of the 8-bit input
966 # there's no partial 6-bit output.
967 #
968 oup = ''
969 while inp:
970 if len(inp) > 48:
971 t = inp[:48]
972 inp = inp[48:]
973 else:
974 t = inp
975 inp = ''
976 e = binascii.b2a_base64(t)
977 if e:
978 oup = oup + e[:-1]
979 return oup
980
981 def decode(self, inp):
982 if not inp:
983 return ''
984 return binascii.a2b_base64(inp)
985
Guido van Rossumeda960a1998-06-18 14:24:28 +0000986
987
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000990
991def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +0000992 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 Returns Python time module tuple.
995 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 mo = InternalDate.match(resp)
998 if not mo:
999 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001000
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 mon = Mon2num[mo.group('mon')]
1002 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001003
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001004 day = int(mo.group('day'))
1005 year = int(mo.group('year'))
1006 hour = int(mo.group('hour'))
1007 min = int(mo.group('min'))
1008 sec = int(mo.group('sec'))
1009 zoneh = int(mo.group('zoneh'))
1010 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001011
Tim Peters07e99cb2001-01-14 23:47:14 +00001012 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001013
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 zone = (zoneh*60 + zonem)*60
1015 if zonen == '-':
1016 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001019
Tim Peters07e99cb2001-01-14 23:47:14 +00001020 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 # Following is necessary because the time module has no 'mkgmtime'.
1023 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001024
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 lt = time.localtime(utc)
1026 if time.daylight and lt[-1]:
1027 zone = zone + time.altzone
1028 else:
1029 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001030
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001032
1033
1034
1035def Int2AP(num):
1036
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001038
Tim Peters07e99cb2001-01-14 23:47:14 +00001039 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1040 num = int(abs(num))
1041 while num:
1042 num, mod = divmod(num, 16)
1043 val = AP[mod] + val
1044 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001045
1046
1047
1048def ParseFlags(resp):
1049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 mo = Flags.match(resp)
1053 if not mo:
1054 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001055
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001056 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001057
1058
1059def Time2Internaldate(date_time):
1060
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001062
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1064 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 dttype = type(date_time)
1067 if dttype is type(1) or dttype is type(1.1):
1068 tt = time.localtime(date_time)
1069 elif dttype is type(()):
1070 tt = date_time
1071 elif dttype is type(""):
1072 return date_time # Assume in correct format
1073 else: raise ValueError
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001074
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1076 if dt[0] == '0':
1077 dt = ' ' + dt[1:]
1078 if time.daylight and tt[-1]:
1079 zone = -time.altzone
1080 else:
1081 zone = -time.timezone
1082 return '"' + dt + " %+02d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001083
1084
1085
Guido van Rossumeda960a1998-06-18 14:24:28 +00001086if __debug__:
1087
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 def _mesg(s, secs=None):
1089 if secs is None:
1090 secs = time.time()
1091 tm = time.strftime('%M:%S', time.localtime(secs))
1092 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1093 sys.stderr.flush()
Guido van Rossum26367a01998-09-28 15:34:46 +00001094
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 def _dump_ur(dict):
1096 # Dump untagged responses (in `dict').
1097 l = dict.items()
1098 if not l: return
1099 t = '\n\t\t'
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001100 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
Piers Lauder34d97052001-07-20 10:28:51 +00001101 _mesg('untagged responses dump:%s%s' % (t, t.join(l)))
Guido van Rossumeda960a1998-06-18 14:24:28 +00001102
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 _cmd_log = [] # Last `_cmd_log_len' interactions
1104 _cmd_log_len = 10
Guido van Rossum8c062211999-12-13 23:27:45 +00001105
Tim Peters07e99cb2001-01-14 23:47:14 +00001106 def _log(line):
1107 # Keep log of last `_cmd_log_len' interactions for debugging.
1108 if len(_cmd_log) == _cmd_log_len:
1109 del _cmd_log[0]
1110 _cmd_log.append((time.time(), line))
Guido van Rossum8c062211999-12-13 23:27:45 +00001111
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 def print_log():
1113 _mesg('last %d IMAP4 interactions:' % len(_cmd_log))
1114 for secs,line in _cmd_log:
1115 _mesg(line, secs)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001116
1117
Guido van Rossum8c062211999-12-13 23:27:45 +00001118
1119if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001120
Tim Peters07e99cb2001-01-14 23:47:14 +00001121 import getopt, getpass, sys
Guido van Rossumd6596931998-05-29 18:08:48 +00001122
Tim Peters07e99cb2001-01-14 23:47:14 +00001123 try:
1124 optlist, args = getopt.getopt(sys.argv[1:], 'd:')
1125 except getopt.error, val:
1126 pass
Guido van Rossum66d45132000-03-28 20:20:53 +00001127
Tim Peters07e99cb2001-01-14 23:47:14 +00001128 for opt,val in optlist:
1129 if opt == '-d':
1130 Debug = int(val)
Guido van Rossum66d45132000-03-28 20:20:53 +00001131
Tim Peters07e99cb2001-01-14 23:47:14 +00001132 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001133
Tim Peters07e99cb2001-01-14 23:47:14 +00001134 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001135
Tim Peters07e99cb2001-01-14 23:47:14 +00001136 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001137 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001138
Piers Laudere02f9042001-08-05 10:43:03 +00001139 test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)sdata...%(lf)s' % {'user':USER, 'lf':CRLF}
Tim Peters07e99cb2001-01-14 23:47:14 +00001140 test_seq1 = (
1141 ('login', (USER, PASSWD)),
1142 ('create', ('/tmp/xxx 1',)),
1143 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1144 ('CREATE', ('/tmp/yyz 2',)),
1145 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1146 ('list', ('/tmp', 'yy*')),
1147 ('select', ('/tmp/yyz 2',)),
1148 ('search', (None, 'SUBJECT', 'test')),
1149 ('partial', ('1', 'RFC822', 1, 1024)),
1150 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001151 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001152 ('expunge', ()),
1153 ('recent', ()),
1154 ('close', ()),
1155 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001156
Tim Peters07e99cb2001-01-14 23:47:14 +00001157 test_seq2 = (
1158 ('select', ()),
1159 ('response',('UIDVALIDITY',)),
1160 ('uid', ('SEARCH', 'ALL')),
1161 ('response', ('EXISTS',)),
1162 ('append', (None, None, None, test_mesg)),
1163 ('recent', ()),
1164 ('logout', ()),
1165 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001166
Tim Peters07e99cb2001-01-14 23:47:14 +00001167 def run(cmd, args):
1168 _mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001169 typ, dat = apply(getattr(M, cmd), args)
Tim Peters07e99cb2001-01-14 23:47:14 +00001170 _mesg('%s => %s %s' % (cmd, typ, dat))
1171 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001172
Tim Peters07e99cb2001-01-14 23:47:14 +00001173 try:
1174 M = IMAP4(host)
1175 _mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Piers Lauder15e5d532001-07-20 10:52:06 +00001176 _mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001177
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 for cmd,args in test_seq1:
1179 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001180
Tim Peters07e99cb2001-01-14 23:47:14 +00001181 for ml in run('list', ('/tmp/', 'yy%')):
1182 mo = re.match(r'.*"([^"]+)"$', ml)
1183 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001184 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001185 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001186
Tim Peters07e99cb2001-01-14 23:47:14 +00001187 for cmd,args in test_seq2:
1188 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001189
Tim Peters07e99cb2001-01-14 23:47:14 +00001190 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1191 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001192
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001193 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001194 if not uid: continue
1195 run('uid', ('FETCH', '%s' % uid[-1],
1196 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001197
Tim Peters07e99cb2001-01-14 23:47:14 +00001198 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001199
Tim Peters07e99cb2001-01-14 23:47:14 +00001200 except:
1201 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001202
Tim Peters07e99cb2001-01-14 23:47:14 +00001203 if not Debug:
1204 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001205If you would like to see debugging output,
1206try: %s -d5
1207''' % sys.argv[0]
1208
Tim Peters07e99cb2001-01-14 23:47:14 +00001209 raise