blob: ed785ed3d42c409002ca416239a15bd0595ba3d2 [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 Lauderfe6accf2001-10-21 22:37:28 +000019__version__ = "2.49"
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))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000210 self.file = self.sock.makefile('rb')
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."""
Guido van Rossumd6bebce2001-10-22 00:42:26 +0000225 bytes = len(data)
226 while bytes > 0:
227 sent = self.sock.send(data)
228 if sent == bytes:
Tim Peters1633a2e2001-10-30 05:56:40 +0000229 break # avoid copy
Guido van Rossumd6bebce2001-10-22 00:42:26 +0000230 data = data[sent:]
231 bytes = bytes - sent
Piers Lauder15e5d532001-07-20 10:52:06 +0000232
233
234 def shutdown(self):
235 """Close I/O established in "open"."""
236 self.file.close()
237 self.sock.close()
238
239
240 def socket(self):
241 """Return socket instance used to connect to IMAP4 server.
242
243 socket = <instance>.socket()
244 """
245 return self.sock
246
247
248
249 # Utility methods
250
251
Tim Peters07e99cb2001-01-14 23:47:14 +0000252 def recent(self):
253 """Return most recent 'RECENT' responses if any exist,
254 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000255
Tim Peters07e99cb2001-01-14 23:47:14 +0000256 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000257
Tim Peters07e99cb2001-01-14 23:47:14 +0000258 'data' is None if no new messages,
259 else list of RECENT responses, most recent last.
260 """
261 name = 'RECENT'
262 typ, dat = self._untagged_response('OK', [None], name)
263 if dat[-1]:
264 return typ, dat
265 typ, dat = self.noop() # Prod server for response
266 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000267
268
Tim Peters07e99cb2001-01-14 23:47:14 +0000269 def response(self, code):
270 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000271
Tim Peters07e99cb2001-01-14 23:47:14 +0000272 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000273
Tim Peters07e99cb2001-01-14 23:47:14 +0000274 (code, [data]) = <instance>.response(code)
275 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000276 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000277
278
Guido van Rossum26367a01998-09-28 15:34:46 +0000279
Tim Peters07e99cb2001-01-14 23:47:14 +0000280 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000281
282
Tim Peters07e99cb2001-01-14 23:47:14 +0000283 def append(self, mailbox, flags, date_time, message):
284 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000285
Tim Peters07e99cb2001-01-14 23:47:14 +0000286 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000287
Tim Peters07e99cb2001-01-14 23:47:14 +0000288 All args except `message' can be None.
289 """
290 name = 'APPEND'
291 if not mailbox:
292 mailbox = 'INBOX'
293 if flags:
294 if (flags[0],flags[-1]) != ('(',')'):
295 flags = '(%s)' % flags
296 else:
297 flags = None
298 if date_time:
299 date_time = Time2Internaldate(date_time)
300 else:
301 date_time = None
302 self.literal = message
303 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000304
305
Tim Peters07e99cb2001-01-14 23:47:14 +0000306 def authenticate(self, mechanism, authobject):
307 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 'mechanism' specifies which authentication mechanism is to
310 be used - it must appear in <instance>.capabilities in the
311 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000312
Tim Peters07e99cb2001-01-14 23:47:14 +0000313 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000314
Tim Peters07e99cb2001-01-14 23:47:14 +0000315 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000316
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 It will be called to process server continuation responses.
318 It should return data that will be encoded and sent to server.
319 It should return None if the client abort response '*' should
320 be sent instead.
321 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000322 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000323 cap = 'AUTH=%s' % mech
324 if not cap in self.capabilities:
325 raise self.error("Server doesn't allow %s authentication." % mech)
326 self.literal = _Authenticator(authobject).process
327 typ, dat = self._simple_command('AUTHENTICATE', mech)
328 if typ != 'OK':
329 raise self.error(dat[-1])
330 self.state = 'AUTH'
331 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000332
333
Tim Peters07e99cb2001-01-14 23:47:14 +0000334 def check(self):
335 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000336
Tim Peters07e99cb2001-01-14 23:47:14 +0000337 (typ, [data]) = <instance>.check()
338 """
339 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000340
341
Tim Peters07e99cb2001-01-14 23:47:14 +0000342 def close(self):
343 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 Deleted messages are removed from writable mailbox.
346 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000347
Tim Peters07e99cb2001-01-14 23:47:14 +0000348 (typ, [data]) = <instance>.close()
349 """
350 try:
351 typ, dat = self._simple_command('CLOSE')
352 finally:
353 self.state = 'AUTH'
354 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000355
356
Tim Peters07e99cb2001-01-14 23:47:14 +0000357 def copy(self, message_set, new_mailbox):
358 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000359
Tim Peters07e99cb2001-01-14 23:47:14 +0000360 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
361 """
362 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000363
364
Tim Peters07e99cb2001-01-14 23:47:14 +0000365 def create(self, mailbox):
366 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000367
Tim Peters07e99cb2001-01-14 23:47:14 +0000368 (typ, [data]) = <instance>.create(mailbox)
369 """
370 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000371
372
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 def delete(self, mailbox):
374 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000375
Tim Peters07e99cb2001-01-14 23:47:14 +0000376 (typ, [data]) = <instance>.delete(mailbox)
377 """
378 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000379
380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 def expunge(self):
382 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
Tim Peters07e99cb2001-01-14 23:47:14 +0000386 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 'data' is list of 'EXPUNGE'd message numbers in order received.
389 """
390 name = 'EXPUNGE'
391 typ, dat = self._simple_command(name)
392 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 def fetch(self, message_set, message_parts):
396 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
Tim Peters07e99cb2001-01-14 23:47:14 +0000400 'message_parts' should be a string of selected parts
401 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000402
Tim Peters07e99cb2001-01-14 23:47:14 +0000403 'data' are tuples of message part envelope and data.
404 """
405 name = 'FETCH'
406 typ, dat = self._simple_command(name, message_set, message_parts)
407 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000408
409
Piers Lauder15e5d532001-07-20 10:52:06 +0000410 def getacl(self, mailbox):
411 """Get the ACLs for a mailbox.
412
413 (typ, [data]) = <instance>.getacl(mailbox)
414 """
415 typ, dat = self._simple_command('GETACL', mailbox)
416 return self._untagged_response(typ, dat, 'ACL')
417
418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 def list(self, directory='""', pattern='*'):
420 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000421
Tim Peters07e99cb2001-01-14 23:47:14 +0000422 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000423
Tim Peters07e99cb2001-01-14 23:47:14 +0000424 'data' is list of LIST responses.
425 """
426 name = 'LIST'
427 typ, dat = self._simple_command(name, directory, pattern)
428 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 def login(self, user, password):
432 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000433
Tim Peters07e99cb2001-01-14 23:47:14 +0000434 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000435
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 NB: 'password' will be quoted.
437 """
438 #if not 'AUTH=LOGIN' in self.capabilities:
439 # raise self.error("Server doesn't allow LOGIN authentication." % mech)
440 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
441 if typ != 'OK':
442 raise self.error(dat[-1])
443 self.state = 'AUTH'
444 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000445
446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 def logout(self):
448 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 Returns server 'BYE' response.
453 """
454 self.state = 'LOGOUT'
455 try: typ, dat = self._simple_command('LOGOUT')
456 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000457 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000458 if self.untagged_responses.has_key('BYE'):
459 return 'BYE', self.untagged_responses['BYE']
460 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 def lsub(self, directory='""', pattern='*'):
464 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
Tim Peters07e99cb2001-01-14 23:47:14 +0000466 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000467
Tim Peters07e99cb2001-01-14 23:47:14 +0000468 'data' are tuples of message part envelope and data.
469 """
470 name = 'LSUB'
471 typ, dat = self._simple_command(name, directory, pattern)
472 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000473
474
Piers Lauder15e5d532001-07-20 10:52:06 +0000475 def namespace(self):
476 """ Returns IMAP namespaces ala rfc2342
477
478 (typ, [data, ...]) = <instance>.namespace()
479 """
480 name = 'NAMESPACE'
481 typ, dat = self._simple_command(name)
482 return self._untagged_response(typ, dat, name)
483
484
Tim Peters07e99cb2001-01-14 23:47:14 +0000485 def noop(self):
486 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000487
Tim Peters07e99cb2001-01-14 23:47:14 +0000488 (typ, data) = <instance>.noop()
489 """
490 if __debug__:
491 if self.debug >= 3:
492 _dump_ur(self.untagged_responses)
493 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000494
495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 def partial(self, message_num, message_part, start, length):
497 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000498
Tim Peters07e99cb2001-01-14 23:47:14 +0000499 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000500
Tim Peters07e99cb2001-01-14 23:47:14 +0000501 'data' is tuple of message part envelope and data.
502 """
503 name = 'PARTIAL'
504 typ, dat = self._simple_command(name, message_num, message_part, start, length)
505 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000506
507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 def rename(self, oldmailbox, newmailbox):
509 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000510
Tim Peters07e99cb2001-01-14 23:47:14 +0000511 (typ, data) = <instance>.rename(oldmailbox, newmailbox)
512 """
513 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000514
515
Tim Peters07e99cb2001-01-14 23:47:14 +0000516 def search(self, charset, *criteria):
517 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000518
Tim Peters07e99cb2001-01-14 23:47:14 +0000519 (typ, [data]) = <instance>.search(charset, criterium, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000520
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 'data' is space separated list of matching message numbers.
522 """
523 name = 'SEARCH'
524 if charset:
Piers Lauder15e5d532001-07-20 10:52:06 +0000525 typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria)
526 else:
527 typ, dat = apply(self._simple_command, (name,) + criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000528 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000529
530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 def select(self, mailbox='INBOX', readonly=None):
532 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000533
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000535
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000537
Tim Peters07e99cb2001-01-14 23:47:14 +0000538 'data' is count of messages in mailbox ('EXISTS' response).
539 """
540 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
541 self.untagged_responses = {} # Flush old responses.
542 self.is_readonly = readonly
543 if readonly:
544 name = 'EXAMINE'
545 else:
546 name = 'SELECT'
547 typ, dat = self._simple_command(name, mailbox)
548 if typ != 'OK':
549 self.state = 'AUTH' # Might have been 'SELECTED'
550 return typ, dat
551 self.state = 'SELECTED'
552 if self.untagged_responses.has_key('READ-ONLY') \
553 and not readonly:
554 if __debug__:
555 if self.debug >= 1:
556 _dump_ur(self.untagged_responses)
557 raise self.readonly('%s is not writable' % mailbox)
558 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000559
560
Piers Lauder15e5d532001-07-20 10:52:06 +0000561 def setacl(self, mailbox, who, what):
562 """Set a mailbox acl.
563
564 (typ, [data]) = <instance>.create(mailbox, who, what)
565 """
566 return self._simple_command('SETACL', mailbox, who, what)
567
568
569 def sort(self, sort_criteria, charset, *search_criteria):
570 """IMAP4rev1 extension SORT command.
571
572 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
573 """
574 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000575 #if not name in self.capabilities: # Let the server decide!
576 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000577 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000578 sort_criteria = '(%s)' % sort_criteria
Piers Lauder15e5d532001-07-20 10:52:06 +0000579 typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria)
580 return self._untagged_response(typ, dat, name)
581
582
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 def status(self, mailbox, names):
584 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000585
Tim Peters07e99cb2001-01-14 23:47:14 +0000586 (typ, [data]) = <instance>.status(mailbox, names)
587 """
588 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000589 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000590 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 typ, dat = self._simple_command(name, mailbox, names)
592 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000593
594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 def store(self, message_set, command, flags):
596 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000597
Tim Peters07e99cb2001-01-14 23:47:14 +0000598 (typ, [data]) = <instance>.store(message_set, command, flags)
599 """
600 if (flags[0],flags[-1]) != ('(',')'):
601 flags = '(%s)' % flags # Avoid quoting the flags
602 typ, dat = self._simple_command('STORE', message_set, command, flags)
603 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000604
605
Tim Peters07e99cb2001-01-14 23:47:14 +0000606 def subscribe(self, mailbox):
607 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000608
Tim Peters07e99cb2001-01-14 23:47:14 +0000609 (typ, [data]) = <instance>.subscribe(mailbox)
610 """
611 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000612
613
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 def uid(self, command, *args):
615 """Execute "command arg ..." with messages identified by UID,
616 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000617
Tim Peters07e99cb2001-01-14 23:47:14 +0000618 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000619
Tim Peters07e99cb2001-01-14 23:47:14 +0000620 Returns response appropriate to 'command'.
621 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000622 command = command.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000623 if not Commands.has_key(command):
624 raise self.error("Unknown IMAP4 UID command: %s" % command)
625 if self.state not in Commands[command]:
626 raise self.error('command %s illegal in state %s'
627 % (command, self.state))
628 name = 'UID'
629 typ, dat = apply(self._simple_command, (name, command) + args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000630 if command in ('SEARCH', 'SORT'):
631 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000632 else:
633 name = 'FETCH'
634 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
636
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 def unsubscribe(self, mailbox):
638 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000639
Tim Peters07e99cb2001-01-14 23:47:14 +0000640 (typ, [data]) = <instance>.unsubscribe(mailbox)
641 """
642 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000643
644
Tim Peters07e99cb2001-01-14 23:47:14 +0000645 def xatom(self, name, *args):
646 """Allow simple extension commands
647 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000648
Piers Lauder15e5d532001-07-20 10:52:06 +0000649 Assumes command is legal in current state.
650
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000652
653 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000655 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000656 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000657 # raise self.error('unknown extension command: %s' % name)
658 if not Commands.has_key(name):
659 Commands[name] = (self.state,)
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 return apply(self._simple_command, (name,) + args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000661
662
663
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000665
666
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000668
Tim Peters07e99cb2001-01-14 23:47:14 +0000669 if dat is None: dat = ''
670 ur = self.untagged_responses
671 if __debug__:
672 if self.debug >= 5:
673 _mesg('untagged_responses[%s] %s += ["%s"]' %
674 (typ, len(ur.get(typ,'')), dat))
675 if ur.has_key(typ):
676 ur[typ].append(dat)
677 else:
678 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000679
680
Tim Peters07e99cb2001-01-14 23:47:14 +0000681 def _check_bye(self):
682 bye = self.untagged_responses.get('BYE')
683 if bye:
684 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000685
686
Tim Peters07e99cb2001-01-14 23:47:14 +0000687 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000688
Tim Peters07e99cb2001-01-14 23:47:14 +0000689 if self.state not in Commands[name]:
690 self.literal = None
691 raise self.error(
692 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000693
Tim Peters07e99cb2001-01-14 23:47:14 +0000694 for typ in ('OK', 'NO', 'BAD'):
695 if self.untagged_responses.has_key(typ):
696 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000697
Tim Peters07e99cb2001-01-14 23:47:14 +0000698 if self.untagged_responses.has_key('READ-ONLY') \
699 and not self.is_readonly:
700 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000701
Tim Peters07e99cb2001-01-14 23:47:14 +0000702 tag = self._new_tag()
703 data = '%s %s' % (tag, name)
704 for arg in args:
705 if arg is None: continue
706 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000707
Tim Peters07e99cb2001-01-14 23:47:14 +0000708 literal = self.literal
709 if literal is not None:
710 self.literal = None
711 if type(literal) is type(self._command):
712 literator = literal
713 else:
714 literator = None
715 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000716
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 if __debug__:
718 if self.debug >= 4:
719 _mesg('> %s' % data)
720 else:
721 _log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000722
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000724 self.send('%s%s' % (data, CRLF))
725 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
Tim Peters07e99cb2001-01-14 23:47:14 +0000728 if literal is None:
729 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000730
Tim Peters07e99cb2001-01-14 23:47:14 +0000731 while 1:
732 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000733
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 while self._get_response():
735 if self.tagged_commands[tag]: # BAD/NO?
736 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000737
Tim Peters07e99cb2001-01-14 23:47:14 +0000738 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000739
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 if literator:
741 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000742
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 if __debug__:
744 if self.debug >= 4:
745 _mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000746
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000748 self.send(literal)
749 self.send(CRLF)
750 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000751 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000752
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 if not literator:
754 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000757
758
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 def _command_complete(self, name, tag):
760 self._check_bye()
761 try:
762 typ, data = self._get_tagged_response(tag)
763 except self.abort, val:
764 raise self.abort('command: %s => %s' % (name, val))
765 except self.error, val:
766 raise self.error('command: %s => %s' % (name, val))
767 self._check_bye()
768 if typ == 'BAD':
769 raise self.error('%s command error: %s %s' % (name, typ, data))
770 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000771
772
Tim Peters07e99cb2001-01-14 23:47:14 +0000773 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000774
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 # Read response and store.
776 #
777 # Returns None for continuation responses,
778 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000779
Tim Peters07e99cb2001-01-14 23:47:14 +0000780 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000781
Tim Peters07e99cb2001-01-14 23:47:14 +0000782 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000783
Tim Peters07e99cb2001-01-14 23:47:14 +0000784 if self._match(self.tagre, resp):
785 tag = self.mo.group('tag')
786 if not self.tagged_commands.has_key(tag):
787 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000788
Tim Peters07e99cb2001-01-14 23:47:14 +0000789 typ = self.mo.group('type')
790 dat = self.mo.group('data')
791 self.tagged_commands[tag] = (typ, [dat])
792 else:
793 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000794
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000796
Tim Peters07e99cb2001-01-14 23:47:14 +0000797 if not self._match(Untagged_response, resp):
798 if self._match(Untagged_status, resp):
799 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000800
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 if self.mo is None:
802 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000803
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 if self._match(Continuation, resp):
805 self.continuation_response = self.mo.group('data')
806 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000807
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000809
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 typ = self.mo.group('type')
811 dat = self.mo.group('data')
812 if dat is None: dat = '' # Null untagged response
813 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000814
Tim Peters07e99cb2001-01-14 23:47:14 +0000815 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000820
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000821 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 if __debug__:
823 if self.debug >= 4:
824 _mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000825 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000828
Tim Peters07e99cb2001-01-14 23:47:14 +0000829 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000830
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000836
Tim Peters07e99cb2001-01-14 23:47:14 +0000837 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
840 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 if __debug__:
843 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
844 _mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000847
848
Tim Peters07e99cb2001-01-14 23:47:14 +0000849 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000850
Tim Peters07e99cb2001-01-14 23:47:14 +0000851 while 1:
852 result = self.tagged_commands[tag]
853 if result is not None:
854 del self.tagged_commands[tag]
855 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000856
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 # Some have reported "unexpected response" exceptions.
858 # Note that ignoring them here causes loops.
859 # Instead, send me details of the unexpected response and
860 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000861
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 try:
863 self._get_response()
864 except self.abort, val:
865 if __debug__:
866 if self.debug >= 1:
867 print_log()
868 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000872
Piers Lauder15e5d532001-07-20 10:52:06 +0000873 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 if not line:
875 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 line = line[:-2]
880 if __debug__:
881 if self.debug >= 4:
882 _mesg('< %s' % line)
883 else:
884 _log('< %s' % line)
885 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
887
Tim Peters07e99cb2001-01-14 23:47:14 +0000888 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000889
Tim Peters07e99cb2001-01-14 23:47:14 +0000890 # Run compiled regular expression match method on 's'.
891 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 self.mo = cre.match(s)
894 if __debug__:
895 if self.mo is not None and self.debug >= 5:
896 _mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
897 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000898
899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
Tim Peters07e99cb2001-01-14 23:47:14 +0000902 tag = '%s%s' % (self.tagpre, self.tagnum)
903 self.tagnum = self.tagnum + 1
904 self.tagged_commands[tag] = None
905 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000906
907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 # Must quote command args if non-alphanumeric chars present,
911 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000912
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 if type(arg) is not type(''):
914 return arg
915 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
916 return arg
917 if self.mustquote.search(arg) is None:
918 return arg
919 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000920
921
Tim Peters07e99cb2001-01-14 23:47:14 +0000922 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000923
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000924 arg = arg.replace('\\', '\\\\')
925 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000928
929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 return self._command_complete(name, apply(self._command, (name,) + args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 if typ == 'NO':
938 return typ, dat
939 if not self.untagged_responses.has_key(name):
940 return typ, [None]
941 data = self.untagged_responses[name]
942 if __debug__:
943 if self.debug >= 5:
944 _mesg('untagged_responses[%s] => %s' % (name, data))
945 del self.untagged_responses[name]
946 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000947
948
949
Guido van Rossumeda960a1998-06-18 14:24:28 +0000950class _Authenticator:
951
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 """Private class to provide en/decoding
953 for base64-based authentication conversation.
954 """
Guido van Rossumeda960a1998-06-18 14:24:28 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 def __init__(self, mechinst):
957 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 def process(self, data):
960 ret = self.mech(self.decode(data))
961 if ret is None:
962 return '*' # Abort conversation
963 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 def encode(self, inp):
966 #
967 # Invoke binascii.b2a_base64 iteratively with
968 # short even length buffers, strip the trailing
969 # line feed from the result and append. "Even"
970 # means a number that factors to both 6 and 8,
971 # so when it gets to the end of the 8-bit input
972 # there's no partial 6-bit output.
973 #
974 oup = ''
975 while inp:
976 if len(inp) > 48:
977 t = inp[:48]
978 inp = inp[48:]
979 else:
980 t = inp
981 inp = ''
982 e = binascii.b2a_base64(t)
983 if e:
984 oup = oup + e[:-1]
985 return oup
986
987 def decode(self, inp):
988 if not inp:
989 return ''
990 return binascii.a2b_base64(inp)
991
Guido van Rossumeda960a1998-06-18 14:24:28 +0000992
993
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000996
997def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 Returns Python time module tuple.
1001 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 mo = InternalDate.match(resp)
1004 if not mo:
1005 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001006
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 mon = Mon2num[mo.group('mon')]
1008 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001009
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001010 day = int(mo.group('day'))
1011 year = int(mo.group('year'))
1012 hour = int(mo.group('hour'))
1013 min = int(mo.group('min'))
1014 sec = int(mo.group('sec'))
1015 zoneh = int(mo.group('zoneh'))
1016 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001019
Tim Peters07e99cb2001-01-14 23:47:14 +00001020 zone = (zoneh*60 + zonem)*60
1021 if zonen == '-':
1022 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 # Following is necessary because the time module has no 'mkgmtime'.
1029 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001030
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 lt = time.localtime(utc)
1032 if time.daylight and lt[-1]:
1033 zone = zone + time.altzone
1034 else:
1035 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001036
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001038
1039
1040
1041def Int2AP(num):
1042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
Tim Peters07e99cb2001-01-14 23:47:14 +00001045 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1046 num = int(abs(num))
1047 while num:
1048 num, mod = divmod(num, 16)
1049 val = AP[mod] + val
1050 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
1052
1053
1054def ParseFlags(resp):
1055
Tim Peters07e99cb2001-01-14 23:47:14 +00001056 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 mo = Flags.match(resp)
1059 if not mo:
1060 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001061
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001062 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001063
1064
1065def Time2Internaldate(date_time):
1066
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001068
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1070 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001071
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001072 if isinstance(date_time, int) or isinstance(date_time, float):
Tim Peters07e99cb2001-01-14 23:47:14 +00001073 tt = time.localtime(date_time)
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001074 elif isinstance(date_time, tuple) or \
1075 isinstance(date_time, time.struct_time):
Tim Peters07e99cb2001-01-14 23:47:14 +00001076 tt = date_time
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001077 elif isinstance(date_time, str):
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 return date_time # Assume in correct format
1079 else: raise ValueError
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001080
Tim Peters07e99cb2001-01-14 23:47:14 +00001081 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1082 if dt[0] == '0':
1083 dt = ' ' + dt[1:]
1084 if time.daylight and tt[-1]:
1085 zone = -time.altzone
1086 else:
1087 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001088 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001089
1090
1091
Guido van Rossumeda960a1998-06-18 14:24:28 +00001092if __debug__:
1093
Tim Peters07e99cb2001-01-14 23:47:14 +00001094 def _mesg(s, secs=None):
1095 if secs is None:
1096 secs = time.time()
1097 tm = time.strftime('%M:%S', time.localtime(secs))
1098 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1099 sys.stderr.flush()
Guido van Rossum26367a01998-09-28 15:34:46 +00001100
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 def _dump_ur(dict):
1102 # Dump untagged responses (in `dict').
1103 l = dict.items()
1104 if not l: return
1105 t = '\n\t\t'
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001106 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
Piers Lauder34d97052001-07-20 10:28:51 +00001107 _mesg('untagged responses dump:%s%s' % (t, t.join(l)))
Guido van Rossumeda960a1998-06-18 14:24:28 +00001108
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 _cmd_log = [] # Last `_cmd_log_len' interactions
1110 _cmd_log_len = 10
Guido van Rossum8c062211999-12-13 23:27:45 +00001111
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 def _log(line):
1113 # Keep log of last `_cmd_log_len' interactions for debugging.
1114 if len(_cmd_log) == _cmd_log_len:
1115 del _cmd_log[0]
1116 _cmd_log.append((time.time(), line))
Guido van Rossum8c062211999-12-13 23:27:45 +00001117
Tim Peters07e99cb2001-01-14 23:47:14 +00001118 def print_log():
1119 _mesg('last %d IMAP4 interactions:' % len(_cmd_log))
1120 for secs,line in _cmd_log:
1121 _mesg(line, secs)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001122
1123
Guido van Rossum8c062211999-12-13 23:27:45 +00001124
1125if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001126
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001127 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001128
Tim Peters07e99cb2001-01-14 23:47:14 +00001129 try:
1130 optlist, args = getopt.getopt(sys.argv[1:], 'd:')
1131 except getopt.error, val:
1132 pass
Guido van Rossum66d45132000-03-28 20:20:53 +00001133
Tim Peters07e99cb2001-01-14 23:47:14 +00001134 for opt,val in optlist:
1135 if opt == '-d':
1136 Debug = int(val)
Guido van Rossum66d45132000-03-28 20:20:53 +00001137
Tim Peters07e99cb2001-01-14 23:47:14 +00001138 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001139
Tim Peters07e99cb2001-01-14 23:47:14 +00001140 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001141
Tim Peters07e99cb2001-01-14 23:47:14 +00001142 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001143 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001144
Piers Laudere02f9042001-08-05 10:43:03 +00001145 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 +00001146 test_seq1 = (
1147 ('login', (USER, PASSWD)),
1148 ('create', ('/tmp/xxx 1',)),
1149 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1150 ('CREATE', ('/tmp/yyz 2',)),
1151 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1152 ('list', ('/tmp', 'yy*')),
1153 ('select', ('/tmp/yyz 2',)),
1154 ('search', (None, 'SUBJECT', 'test')),
1155 ('partial', ('1', 'RFC822', 1, 1024)),
1156 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001157 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001158 ('expunge', ()),
1159 ('recent', ()),
1160 ('close', ()),
1161 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001162
Tim Peters07e99cb2001-01-14 23:47:14 +00001163 test_seq2 = (
1164 ('select', ()),
1165 ('response',('UIDVALIDITY',)),
1166 ('uid', ('SEARCH', 'ALL')),
1167 ('response', ('EXISTS',)),
1168 ('append', (None, None, None, test_mesg)),
1169 ('recent', ()),
1170 ('logout', ()),
1171 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001172
Tim Peters07e99cb2001-01-14 23:47:14 +00001173 def run(cmd, args):
1174 _mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001175 typ, dat = apply(getattr(M, cmd), args)
Tim Peters07e99cb2001-01-14 23:47:14 +00001176 _mesg('%s => %s %s' % (cmd, typ, dat))
1177 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001178
Tim Peters07e99cb2001-01-14 23:47:14 +00001179 try:
1180 M = IMAP4(host)
1181 _mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Piers Lauder15e5d532001-07-20 10:52:06 +00001182 _mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001183
Tim Peters07e99cb2001-01-14 23:47:14 +00001184 for cmd,args in test_seq1:
1185 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001186
Tim Peters07e99cb2001-01-14 23:47:14 +00001187 for ml in run('list', ('/tmp/', 'yy%')):
1188 mo = re.match(r'.*"([^"]+)"$', ml)
1189 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001190 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001191 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001192
Tim Peters07e99cb2001-01-14 23:47:14 +00001193 for cmd,args in test_seq2:
1194 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001195
Tim Peters07e99cb2001-01-14 23:47:14 +00001196 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1197 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001198
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001199 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001200 if not uid: continue
1201 run('uid', ('FETCH', '%s' % uid[-1],
1202 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001203
Tim Peters07e99cb2001-01-14 23:47:14 +00001204 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001205
Tim Peters07e99cb2001-01-14 23:47:14 +00001206 except:
1207 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001208
Tim Peters07e99cb2001-01-14 23:47:14 +00001209 if not Debug:
1210 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001211If you would like to see debugging output,
1212try: %s -d5
1213''' % sys.argv[0]
1214
Tim Peters07e99cb2001-01-14 23:47:14 +00001215 raise