]> git.proxmox.com Git - mirror_edk2.git/blob - AppPkg/Applications/Python/Python-2.7.2/Lib/imaplib.py
EmbeddedPkg: Extend NvVarStoreFormattedLib LIBRARY_CLASS
[mirror_edk2.git] / AppPkg / Applications / Python / Python-2.7.2 / Lib / imaplib.py
1 """IMAP4 client.
2
3 Based on RFC 2060.
4
5 Public class: IMAP4
6 Public variable: Debug
7 Public functions: Internaldate2tuple
8 Int2AP
9 ParseFlags
10 Time2Internaldate
11 """
12
13 # Author: Piers Lauder <piers@cs.su.oz.au> December 1997.
14 #
15 # Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998.
16 # String method conversion by ESR, February 2001.
17 # GET/SETACL contributed by Anthony Baxter <anthony@interlink.com.au> April 2001.
18 # IMAP4_SSL contributed by Tino Lange <Tino.Lange@isg.de> March 2002.
19 # GET/SETQUOTA contributed by Andreas Zeidler <az@kreativkombinat.de> June 2002.
20 # PROXYAUTH contributed by Rick Holbert <holbert.13@osu.edu> November 2002.
21 # GET/SETANNOTATION contributed by Tomas Lindroos <skitta@abo.fi> June 2005.
22
23 __version__ = "2.58"
24
25 import binascii, errno, random, re, socket, subprocess, sys, time
26
27 __all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
28 "Int2AP", "ParseFlags", "Time2Internaldate"]
29
30 # Globals
31
32 CRLF = '\r\n'
33 Debug = 0
34 IMAP4_PORT = 143
35 IMAP4_SSL_PORT = 993
36 AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
37
38 # Commands
39
40 Commands = {
41 # name valid states
42 'APPEND': ('AUTH', 'SELECTED'),
43 'AUTHENTICATE': ('NONAUTH',),
44 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
45 'CHECK': ('SELECTED',),
46 'CLOSE': ('SELECTED',),
47 'COPY': ('SELECTED',),
48 'CREATE': ('AUTH', 'SELECTED'),
49 'DELETE': ('AUTH', 'SELECTED'),
50 'DELETEACL': ('AUTH', 'SELECTED'),
51 'EXAMINE': ('AUTH', 'SELECTED'),
52 'EXPUNGE': ('SELECTED',),
53 'FETCH': ('SELECTED',),
54 'GETACL': ('AUTH', 'SELECTED'),
55 'GETANNOTATION':('AUTH', 'SELECTED'),
56 'GETQUOTA': ('AUTH', 'SELECTED'),
57 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
58 'MYRIGHTS': ('AUTH', 'SELECTED'),
59 'LIST': ('AUTH', 'SELECTED'),
60 'LOGIN': ('NONAUTH',),
61 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
62 'LSUB': ('AUTH', 'SELECTED'),
63 'NAMESPACE': ('AUTH', 'SELECTED'),
64 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
65 'PARTIAL': ('SELECTED',), # NB: obsolete
66 'PROXYAUTH': ('AUTH',),
67 'RENAME': ('AUTH', 'SELECTED'),
68 'SEARCH': ('SELECTED',),
69 'SELECT': ('AUTH', 'SELECTED'),
70 'SETACL': ('AUTH', 'SELECTED'),
71 'SETANNOTATION':('AUTH', 'SELECTED'),
72 'SETQUOTA': ('AUTH', 'SELECTED'),
73 'SORT': ('SELECTED',),
74 'STATUS': ('AUTH', 'SELECTED'),
75 'STORE': ('SELECTED',),
76 'SUBSCRIBE': ('AUTH', 'SELECTED'),
77 'THREAD': ('SELECTED',),
78 'UID': ('SELECTED',),
79 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
80 }
81
82 # Patterns to match server responses
83
84 Continuation = re.compile(r'\+( (?P<data>.*))?')
85 Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
86 InternalDate = re.compile(r'.*INTERNALDATE "'
87 r'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
88 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
89 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
90 r'"')
91 Literal = re.compile(r'.*{(?P<size>\d+)}$')
92 MapCRLF = re.compile(r'\r\n|\r|\n')
93 Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
94 Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
95 Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
96
97
98
99 class IMAP4:
100
101 """IMAP4 client class.
102
103 Instantiate with: IMAP4([host[, port]])
104
105 host - host's name (default: localhost);
106 port - port number (default: standard IMAP4 port).
107
108 All IMAP4rev1 commands are supported by methods of the same
109 name (in lower-case).
110
111 All arguments to commands are converted to strings, except for
112 AUTHENTICATE, and the last argument to APPEND which is passed as
113 an IMAP4 literal. If necessary (the string contains any
114 non-printing characters or white-space and isn't enclosed with
115 either parentheses or double quotes) each string is quoted.
116 However, the 'password' argument to the LOGIN command is always
117 quoted. If you want to avoid having an argument string quoted
118 (eg: the 'flags' argument to STORE) then enclose the string in
119 parentheses (eg: "(\Deleted)").
120
121 Each command returns a tuple: (type, [data, ...]) where 'type'
122 is usually 'OK' or 'NO', and 'data' is either the text from the
123 tagged response, or untagged results from command. Each 'data'
124 is either a string, or a tuple. If a tuple, then the first part
125 is the header of the response, and the second part contains
126 the data (ie: 'literal' value).
127
128 Errors raise the exception class <instance>.error("<reason>").
129 IMAP4 server errors raise <instance>.abort("<reason>"),
130 which is a sub-class of 'error'. Mailbox status changes
131 from READ-WRITE to READ-ONLY raise the exception class
132 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
133
134 "error" exceptions imply a program error.
135 "abort" exceptions imply the connection should be reset, and
136 the command re-tried.
137 "readonly" exceptions imply the command should be re-tried.
138
139 Note: to use this module, you must read the RFCs pertaining to the
140 IMAP4 protocol, as the semantics of the arguments to each IMAP4
141 command are left to the invoker, not to mention the results. Also,
142 most IMAP servers implement a sub-set of the commands available here.
143 """
144
145 class error(Exception): pass # Logical errors - debug required
146 class abort(error): pass # Service errors - close and retry
147 class readonly(abort): pass # Mailbox status changed to READ-ONLY
148
149 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
150
151 def __init__(self, host = '', port = IMAP4_PORT):
152 self.debug = Debug
153 self.state = 'LOGOUT'
154 self.literal = None # A literal argument to a command
155 self.tagged_commands = {} # Tagged commands awaiting response
156 self.untagged_responses = {} # {typ: [data, ...], ...}
157 self.continuation_response = '' # Last continuation response
158 self.is_readonly = False # READ-ONLY desired state
159 self.tagnum = 0
160
161 # Open socket to server.
162
163 self.open(host, port)
164
165 # Create unique tag for this session,
166 # and compile tagged response matcher.
167
168 self.tagpre = Int2AP(random.randint(4096, 65535))
169 self.tagre = re.compile(r'(?P<tag>'
170 + self.tagpre
171 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
172
173 # Get server welcome message,
174 # request and store CAPABILITY response.
175
176 if __debug__:
177 self._cmd_log_len = 10
178 self._cmd_log_idx = 0
179 self._cmd_log = {} # Last `_cmd_log_len' interactions
180 if self.debug >= 1:
181 self._mesg('imaplib version %s' % __version__)
182 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
183
184 self.welcome = self._get_response()
185 if 'PREAUTH' in self.untagged_responses:
186 self.state = 'AUTH'
187 elif 'OK' in self.untagged_responses:
188 self.state = 'NONAUTH'
189 else:
190 raise self.error(self.welcome)
191
192 typ, dat = self.capability()
193 if dat == [None]:
194 raise self.error('no CAPABILITY response from server')
195 self.capabilities = tuple(dat[-1].upper().split())
196
197 if __debug__:
198 if self.debug >= 3:
199 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
200
201 for version in AllowedVersions:
202 if not version in self.capabilities:
203 continue
204 self.PROTOCOL_VERSION = version
205 return
206
207 raise self.error('server not IMAP4 compliant')
208
209
210 def __getattr__(self, attr):
211 # Allow UPPERCASE variants of IMAP4 command methods.
212 if attr in Commands:
213 return getattr(self, attr.lower())
214 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
215
216
217
218 # Overridable methods
219
220
221 def open(self, host = '', port = IMAP4_PORT):
222 """Setup connection to remote server on "host:port"
223 (default: localhost:standard IMAP4 port).
224 This connection will be used by the routines:
225 read, readline, send, shutdown.
226 """
227 self.host = host
228 self.port = port
229 self.sock = socket.create_connection((host, port))
230 self.file = self.sock.makefile('rb')
231
232
233 def read(self, size):
234 """Read 'size' bytes from remote."""
235 return self.file.read(size)
236
237
238 def readline(self):
239 """Read line from remote."""
240 return self.file.readline()
241
242
243 def send(self, data):
244 """Send data to remote."""
245 self.sock.sendall(data)
246
247
248 def shutdown(self):
249 """Close I/O established in "open"."""
250 self.file.close()
251 try:
252 self.sock.shutdown(socket.SHUT_RDWR)
253 except socket.error as e:
254 # The server might already have closed the connection
255 if e.errno != errno.ENOTCONN:
256 raise
257 finally:
258 self.sock.close()
259
260
261 def socket(self):
262 """Return socket instance used to connect to IMAP4 server.
263
264 socket = <instance>.socket()
265 """
266 return self.sock
267
268
269
270 # Utility methods
271
272
273 def recent(self):
274 """Return most recent 'RECENT' responses if any exist,
275 else prompt server for an update using the 'NOOP' command.
276
277 (typ, [data]) = <instance>.recent()
278
279 'data' is None if no new messages,
280 else list of RECENT responses, most recent last.
281 """
282 name = 'RECENT'
283 typ, dat = self._untagged_response('OK', [None], name)
284 if dat[-1]:
285 return typ, dat
286 typ, dat = self.noop() # Prod server for response
287 return self._untagged_response(typ, dat, name)
288
289
290 def response(self, code):
291 """Return data for response 'code' if received, or None.
292
293 Old value for response 'code' is cleared.
294
295 (code, [data]) = <instance>.response(code)
296 """
297 return self._untagged_response(code, [None], code.upper())
298
299
300
301 # IMAP4 commands
302
303
304 def append(self, mailbox, flags, date_time, message):
305 """Append message to named mailbox.
306
307 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
308
309 All args except `message' can be None.
310 """
311 name = 'APPEND'
312 if not mailbox:
313 mailbox = 'INBOX'
314 if flags:
315 if (flags[0],flags[-1]) != ('(',')'):
316 flags = '(%s)' % flags
317 else:
318 flags = None
319 if date_time:
320 date_time = Time2Internaldate(date_time)
321 else:
322 date_time = None
323 self.literal = MapCRLF.sub(CRLF, message)
324 return self._simple_command(name, mailbox, flags, date_time)
325
326
327 def authenticate(self, mechanism, authobject):
328 """Authenticate command - requires response processing.
329
330 'mechanism' specifies which authentication mechanism is to
331 be used - it must appear in <instance>.capabilities in the
332 form AUTH=<mechanism>.
333
334 'authobject' must be a callable object:
335
336 data = authobject(response)
337
338 It will be called to process server continuation responses.
339 It should return data that will be encoded and sent to server.
340 It should return None if the client abort response '*' should
341 be sent instead.
342 """
343 mech = mechanism.upper()
344 # XXX: shouldn't this code be removed, not commented out?
345 #cap = 'AUTH=%s' % mech
346 #if not cap in self.capabilities: # Let the server decide!
347 # raise self.error("Server doesn't allow %s authentication." % mech)
348 self.literal = _Authenticator(authobject).process
349 typ, dat = self._simple_command('AUTHENTICATE', mech)
350 if typ != 'OK':
351 raise self.error(dat[-1])
352 self.state = 'AUTH'
353 return typ, dat
354
355
356 def capability(self):
357 """(typ, [data]) = <instance>.capability()
358 Fetch capabilities list from server."""
359
360 name = 'CAPABILITY'
361 typ, dat = self._simple_command(name)
362 return self._untagged_response(typ, dat, name)
363
364
365 def check(self):
366 """Checkpoint mailbox on server.
367
368 (typ, [data]) = <instance>.check()
369 """
370 return self._simple_command('CHECK')
371
372
373 def close(self):
374 """Close currently selected mailbox.
375
376 Deleted messages are removed from writable mailbox.
377 This is the recommended command before 'LOGOUT'.
378
379 (typ, [data]) = <instance>.close()
380 """
381 try:
382 typ, dat = self._simple_command('CLOSE')
383 finally:
384 self.state = 'AUTH'
385 return typ, dat
386
387
388 def copy(self, message_set, new_mailbox):
389 """Copy 'message_set' messages onto end of 'new_mailbox'.
390
391 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
392 """
393 return self._simple_command('COPY', message_set, new_mailbox)
394
395
396 def create(self, mailbox):
397 """Create new mailbox.
398
399 (typ, [data]) = <instance>.create(mailbox)
400 """
401 return self._simple_command('CREATE', mailbox)
402
403
404 def delete(self, mailbox):
405 """Delete old mailbox.
406
407 (typ, [data]) = <instance>.delete(mailbox)
408 """
409 return self._simple_command('DELETE', mailbox)
410
411 def deleteacl(self, mailbox, who):
412 """Delete the ACLs (remove any rights) set for who on mailbox.
413
414 (typ, [data]) = <instance>.deleteacl(mailbox, who)
415 """
416 return self._simple_command('DELETEACL', mailbox, who)
417
418 def expunge(self):
419 """Permanently remove deleted items from selected mailbox.
420
421 Generates 'EXPUNGE' response for each deleted message.
422
423 (typ, [data]) = <instance>.expunge()
424
425 'data' is list of 'EXPUNGE'd message numbers in order received.
426 """
427 name = 'EXPUNGE'
428 typ, dat = self._simple_command(name)
429 return self._untagged_response(typ, dat, name)
430
431
432 def fetch(self, message_set, message_parts):
433 """Fetch (parts of) messages.
434
435 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
436
437 'message_parts' should be a string of selected parts
438 enclosed in parentheses, eg: "(UID BODY[TEXT])".
439
440 'data' are tuples of message part envelope and data.
441 """
442 name = 'FETCH'
443 typ, dat = self._simple_command(name, message_set, message_parts)
444 return self._untagged_response(typ, dat, name)
445
446
447 def getacl(self, mailbox):
448 """Get the ACLs for a mailbox.
449
450 (typ, [data]) = <instance>.getacl(mailbox)
451 """
452 typ, dat = self._simple_command('GETACL', mailbox)
453 return self._untagged_response(typ, dat, 'ACL')
454
455
456 def getannotation(self, mailbox, entry, attribute):
457 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
458 Retrieve ANNOTATIONs."""
459
460 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
461 return self._untagged_response(typ, dat, 'ANNOTATION')
462
463
464 def getquota(self, root):
465 """Get the quota root's resource usage and limits.
466
467 Part of the IMAP4 QUOTA extension defined in rfc2087.
468
469 (typ, [data]) = <instance>.getquota(root)
470 """
471 typ, dat = self._simple_command('GETQUOTA', root)
472 return self._untagged_response(typ, dat, 'QUOTA')
473
474
475 def getquotaroot(self, mailbox):
476 """Get the list of quota roots for the named mailbox.
477
478 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
479 """
480 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
481 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
482 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
483 return typ, [quotaroot, quota]
484
485
486 def list(self, directory='""', pattern='*'):
487 """List mailbox names in directory matching pattern.
488
489 (typ, [data]) = <instance>.list(directory='""', pattern='*')
490
491 'data' is list of LIST responses.
492 """
493 name = 'LIST'
494 typ, dat = self._simple_command(name, directory, pattern)
495 return self._untagged_response(typ, dat, name)
496
497
498 def login(self, user, password):
499 """Identify client using plaintext password.
500
501 (typ, [data]) = <instance>.login(user, password)
502
503 NB: 'password' will be quoted.
504 """
505 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
506 if typ != 'OK':
507 raise self.error(dat[-1])
508 self.state = 'AUTH'
509 return typ, dat
510
511
512 def login_cram_md5(self, user, password):
513 """ Force use of CRAM-MD5 authentication.
514
515 (typ, [data]) = <instance>.login_cram_md5(user, password)
516 """
517 self.user, self.password = user, password
518 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
519
520
521 def _CRAM_MD5_AUTH(self, challenge):
522 """ Authobject to use with CRAM-MD5 authentication. """
523 import hmac
524 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
525
526
527 def logout(self):
528 """Shutdown connection to server.
529
530 (typ, [data]) = <instance>.logout()
531
532 Returns server 'BYE' response.
533 """
534 self.state = 'LOGOUT'
535 try: typ, dat = self._simple_command('LOGOUT')
536 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
537 self.shutdown()
538 if 'BYE' in self.untagged_responses:
539 return 'BYE', self.untagged_responses['BYE']
540 return typ, dat
541
542
543 def lsub(self, directory='""', pattern='*'):
544 """List 'subscribed' mailbox names in directory matching pattern.
545
546 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
547
548 'data' are tuples of message part envelope and data.
549 """
550 name = 'LSUB'
551 typ, dat = self._simple_command(name, directory, pattern)
552 return self._untagged_response(typ, dat, name)
553
554 def myrights(self, mailbox):
555 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
556
557 (typ, [data]) = <instance>.myrights(mailbox)
558 """
559 typ,dat = self._simple_command('MYRIGHTS', mailbox)
560 return self._untagged_response(typ, dat, 'MYRIGHTS')
561
562 def namespace(self):
563 """ Returns IMAP namespaces ala rfc2342
564
565 (typ, [data, ...]) = <instance>.namespace()
566 """
567 name = 'NAMESPACE'
568 typ, dat = self._simple_command(name)
569 return self._untagged_response(typ, dat, name)
570
571
572 def noop(self):
573 """Send NOOP command.
574
575 (typ, [data]) = <instance>.noop()
576 """
577 if __debug__:
578 if self.debug >= 3:
579 self._dump_ur(self.untagged_responses)
580 return self._simple_command('NOOP')
581
582
583 def partial(self, message_num, message_part, start, length):
584 """Fetch truncated part of a message.
585
586 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
587
588 'data' is tuple of message part envelope and data.
589 """
590 name = 'PARTIAL'
591 typ, dat = self._simple_command(name, message_num, message_part, start, length)
592 return self._untagged_response(typ, dat, 'FETCH')
593
594
595 def proxyauth(self, user):
596 """Assume authentication as "user".
597
598 Allows an authorised administrator to proxy into any user's
599 mailbox.
600
601 (typ, [data]) = <instance>.proxyauth(user)
602 """
603
604 name = 'PROXYAUTH'
605 return self._simple_command('PROXYAUTH', user)
606
607
608 def rename(self, oldmailbox, newmailbox):
609 """Rename old mailbox name to new.
610
611 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
612 """
613 return self._simple_command('RENAME', oldmailbox, newmailbox)
614
615
616 def search(self, charset, *criteria):
617 """Search mailbox for matching messages.
618
619 (typ, [data]) = <instance>.search(charset, criterion, ...)
620
621 'data' is space separated list of matching message numbers.
622 """
623 name = 'SEARCH'
624 if charset:
625 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
626 else:
627 typ, dat = self._simple_command(name, *criteria)
628 return self._untagged_response(typ, dat, name)
629
630
631 def select(self, mailbox='INBOX', readonly=False):
632 """Select a mailbox.
633
634 Flush all untagged responses.
635
636 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
637
638 'data' is count of messages in mailbox ('EXISTS' response).
639
640 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
641 other responses should be obtained via <instance>.response('FLAGS') etc.
642 """
643 self.untagged_responses = {} # Flush old responses.
644 self.is_readonly = readonly
645 if readonly:
646 name = 'EXAMINE'
647 else:
648 name = 'SELECT'
649 typ, dat = self._simple_command(name, mailbox)
650 if typ != 'OK':
651 self.state = 'AUTH' # Might have been 'SELECTED'
652 return typ, dat
653 self.state = 'SELECTED'
654 if 'READ-ONLY' in self.untagged_responses \
655 and not readonly:
656 if __debug__:
657 if self.debug >= 1:
658 self._dump_ur(self.untagged_responses)
659 raise self.readonly('%s is not writable' % mailbox)
660 return typ, self.untagged_responses.get('EXISTS', [None])
661
662
663 def setacl(self, mailbox, who, what):
664 """Set a mailbox acl.
665
666 (typ, [data]) = <instance>.setacl(mailbox, who, what)
667 """
668 return self._simple_command('SETACL', mailbox, who, what)
669
670
671 def setannotation(self, *args):
672 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
673 Set ANNOTATIONs."""
674
675 typ, dat = self._simple_command('SETANNOTATION', *args)
676 return self._untagged_response(typ, dat, 'ANNOTATION')
677
678
679 def setquota(self, root, limits):
680 """Set the quota root's resource limits.
681
682 (typ, [data]) = <instance>.setquota(root, limits)
683 """
684 typ, dat = self._simple_command('SETQUOTA', root, limits)
685 return self._untagged_response(typ, dat, 'QUOTA')
686
687
688 def sort(self, sort_criteria, charset, *search_criteria):
689 """IMAP4rev1 extension SORT command.
690
691 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
692 """
693 name = 'SORT'
694 #if not name in self.capabilities: # Let the server decide!
695 # raise self.error('unimplemented extension command: %s' % name)
696 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
697 sort_criteria = '(%s)' % sort_criteria
698 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
699 return self._untagged_response(typ, dat, name)
700
701
702 def status(self, mailbox, names):
703 """Request named status conditions for mailbox.
704
705 (typ, [data]) = <instance>.status(mailbox, names)
706 """
707 name = 'STATUS'
708 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
709 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
710 typ, dat = self._simple_command(name, mailbox, names)
711 return self._untagged_response(typ, dat, name)
712
713
714 def store(self, message_set, command, flags):
715 """Alters flag dispositions for messages in mailbox.
716
717 (typ, [data]) = <instance>.store(message_set, command, flags)
718 """
719 if (flags[0],flags[-1]) != ('(',')'):
720 flags = '(%s)' % flags # Avoid quoting the flags
721 typ, dat = self._simple_command('STORE', message_set, command, flags)
722 return self._untagged_response(typ, dat, 'FETCH')
723
724
725 def subscribe(self, mailbox):
726 """Subscribe to new mailbox.
727
728 (typ, [data]) = <instance>.subscribe(mailbox)
729 """
730 return self._simple_command('SUBSCRIBE', mailbox)
731
732
733 def thread(self, threading_algorithm, charset, *search_criteria):
734 """IMAPrev1 extension THREAD command.
735
736 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
737 """
738 name = 'THREAD'
739 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
740 return self._untagged_response(typ, dat, name)
741
742
743 def uid(self, command, *args):
744 """Execute "command arg ..." with messages identified by UID,
745 rather than message number.
746
747 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
748
749 Returns response appropriate to 'command'.
750 """
751 command = command.upper()
752 if not command in Commands:
753 raise self.error("Unknown IMAP4 UID command: %s" % command)
754 if self.state not in Commands[command]:
755 raise self.error("command %s illegal in state %s, "
756 "only allowed in states %s" %
757 (command, self.state,
758 ', '.join(Commands[command])))
759 name = 'UID'
760 typ, dat = self._simple_command(name, command, *args)
761 if command in ('SEARCH', 'SORT', 'THREAD'):
762 name = command
763 else:
764 name = 'FETCH'
765 return self._untagged_response(typ, dat, name)
766
767
768 def unsubscribe(self, mailbox):
769 """Unsubscribe from old mailbox.
770
771 (typ, [data]) = <instance>.unsubscribe(mailbox)
772 """
773 return self._simple_command('UNSUBSCRIBE', mailbox)
774
775
776 def xatom(self, name, *args):
777 """Allow simple extension commands
778 notified by server in CAPABILITY response.
779
780 Assumes command is legal in current state.
781
782 (typ, [data]) = <instance>.xatom(name, arg, ...)
783
784 Returns response appropriate to extension command `name'.
785 """
786 name = name.upper()
787 #if not name in self.capabilities: # Let the server decide!
788 # raise self.error('unknown extension command: %s' % name)
789 if not name in Commands:
790 Commands[name] = (self.state,)
791 return self._simple_command(name, *args)
792
793
794
795 # Private methods
796
797
798 def _append_untagged(self, typ, dat):
799
800 if dat is None: dat = ''
801 ur = self.untagged_responses
802 if __debug__:
803 if self.debug >= 5:
804 self._mesg('untagged_responses[%s] %s += ["%s"]' %
805 (typ, len(ur.get(typ,'')), dat))
806 if typ in ur:
807 ur[typ].append(dat)
808 else:
809 ur[typ] = [dat]
810
811
812 def _check_bye(self):
813 bye = self.untagged_responses.get('BYE')
814 if bye:
815 raise self.abort(bye[-1])
816
817
818 def _command(self, name, *args):
819
820 if self.state not in Commands[name]:
821 self.literal = None
822 raise self.error("command %s illegal in state %s, "
823 "only allowed in states %s" %
824 (name, self.state,
825 ', '.join(Commands[name])))
826
827 for typ in ('OK', 'NO', 'BAD'):
828 if typ in self.untagged_responses:
829 del self.untagged_responses[typ]
830
831 if 'READ-ONLY' in self.untagged_responses \
832 and not self.is_readonly:
833 raise self.readonly('mailbox status changed to READ-ONLY')
834
835 tag = self._new_tag()
836 data = '%s %s' % (tag, name)
837 for arg in args:
838 if arg is None: continue
839 data = '%s %s' % (data, self._checkquote(arg))
840
841 literal = self.literal
842 if literal is not None:
843 self.literal = None
844 if type(literal) is type(self._command):
845 literator = literal
846 else:
847 literator = None
848 data = '%s {%s}' % (data, len(literal))
849
850 if __debug__:
851 if self.debug >= 4:
852 self._mesg('> %s' % data)
853 else:
854 self._log('> %s' % data)
855
856 try:
857 self.send('%s%s' % (data, CRLF))
858 except (socket.error, OSError), val:
859 raise self.abort('socket error: %s' % val)
860
861 if literal is None:
862 return tag
863
864 while 1:
865 # Wait for continuation response
866
867 while self._get_response():
868 if self.tagged_commands[tag]: # BAD/NO?
869 return tag
870
871 # Send literal
872
873 if literator:
874 literal = literator(self.continuation_response)
875
876 if __debug__:
877 if self.debug >= 4:
878 self._mesg('write literal size %s' % len(literal))
879
880 try:
881 self.send(literal)
882 self.send(CRLF)
883 except (socket.error, OSError), val:
884 raise self.abort('socket error: %s' % val)
885
886 if not literator:
887 break
888
889 return tag
890
891
892 def _command_complete(self, name, tag):
893 # BYE is expected after LOGOUT
894 if name != 'LOGOUT':
895 self._check_bye()
896 try:
897 typ, data = self._get_tagged_response(tag)
898 except self.abort, val:
899 raise self.abort('command: %s => %s' % (name, val))
900 except self.error, val:
901 raise self.error('command: %s => %s' % (name, val))
902 if name != 'LOGOUT':
903 self._check_bye()
904 if typ == 'BAD':
905 raise self.error('%s command error: %s %s' % (name, typ, data))
906 return typ, data
907
908
909 def _get_response(self):
910
911 # Read response and store.
912 #
913 # Returns None for continuation responses,
914 # otherwise first response line received.
915
916 resp = self._get_line()
917
918 # Command completion response?
919
920 if self._match(self.tagre, resp):
921 tag = self.mo.group('tag')
922 if not tag in self.tagged_commands:
923 raise self.abort('unexpected tagged response: %s' % resp)
924
925 typ = self.mo.group('type')
926 dat = self.mo.group('data')
927 self.tagged_commands[tag] = (typ, [dat])
928 else:
929 dat2 = None
930
931 # '*' (untagged) responses?
932
933 if not self._match(Untagged_response, resp):
934 if self._match(Untagged_status, resp):
935 dat2 = self.mo.group('data2')
936
937 if self.mo is None:
938 # Only other possibility is '+' (continuation) response...
939
940 if self._match(Continuation, resp):
941 self.continuation_response = self.mo.group('data')
942 return None # NB: indicates continuation
943
944 raise self.abort("unexpected response: '%s'" % resp)
945
946 typ = self.mo.group('type')
947 dat = self.mo.group('data')
948 if dat is None: dat = '' # Null untagged response
949 if dat2: dat = dat + ' ' + dat2
950
951 # Is there a literal to come?
952
953 while self._match(Literal, dat):
954
955 # Read literal direct from connection.
956
957 size = int(self.mo.group('size'))
958 if __debug__:
959 if self.debug >= 4:
960 self._mesg('read literal size %s' % size)
961 data = self.read(size)
962
963 # Store response with literal as tuple
964
965 self._append_untagged(typ, (dat, data))
966
967 # Read trailer - possibly containing another literal
968
969 dat = self._get_line()
970
971 self._append_untagged(typ, dat)
972
973 # Bracketed response information?
974
975 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
976 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
977
978 if __debug__:
979 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
980 self._mesg('%s response: %s' % (typ, dat))
981
982 return resp
983
984
985 def _get_tagged_response(self, tag):
986
987 while 1:
988 result = self.tagged_commands[tag]
989 if result is not None:
990 del self.tagged_commands[tag]
991 return result
992
993 # Some have reported "unexpected response" exceptions.
994 # Note that ignoring them here causes loops.
995 # Instead, send me details of the unexpected response and
996 # I'll update the code in `_get_response()'.
997
998 try:
999 self._get_response()
1000 except self.abort, val:
1001 if __debug__:
1002 if self.debug >= 1:
1003 self.print_log()
1004 raise
1005
1006
1007 def _get_line(self):
1008
1009 line = self.readline()
1010 if not line:
1011 raise self.abort('socket error: EOF')
1012
1013 # Protocol mandates all lines terminated by CRLF
1014 if not line.endswith('\r\n'):
1015 raise self.abort('socket error: unterminated line')
1016
1017 line = line[:-2]
1018 if __debug__:
1019 if self.debug >= 4:
1020 self._mesg('< %s' % line)
1021 else:
1022 self._log('< %s' % line)
1023 return line
1024
1025
1026 def _match(self, cre, s):
1027
1028 # Run compiled regular expression match method on 's'.
1029 # Save result, return success.
1030
1031 self.mo = cre.match(s)
1032 if __debug__:
1033 if self.mo is not None and self.debug >= 5:
1034 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
1035 return self.mo is not None
1036
1037
1038 def _new_tag(self):
1039
1040 tag = '%s%s' % (self.tagpre, self.tagnum)
1041 self.tagnum = self.tagnum + 1
1042 self.tagged_commands[tag] = None
1043 return tag
1044
1045
1046 def _checkquote(self, arg):
1047
1048 # Must quote command args if non-alphanumeric chars present,
1049 # and not already quoted.
1050
1051 if type(arg) is not type(''):
1052 return arg
1053 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
1054 return arg
1055 if arg and self.mustquote.search(arg) is None:
1056 return arg
1057 return self._quote(arg)
1058
1059
1060 def _quote(self, arg):
1061
1062 arg = arg.replace('\\', '\\\\')
1063 arg = arg.replace('"', '\\"')
1064
1065 return '"%s"' % arg
1066
1067
1068 def _simple_command(self, name, *args):
1069
1070 return self._command_complete(name, self._command(name, *args))
1071
1072
1073 def _untagged_response(self, typ, dat, name):
1074
1075 if typ == 'NO':
1076 return typ, dat
1077 if not name in self.untagged_responses:
1078 return typ, [None]
1079 data = self.untagged_responses.pop(name)
1080 if __debug__:
1081 if self.debug >= 5:
1082 self._mesg('untagged_responses[%s] => %s' % (name, data))
1083 return typ, data
1084
1085
1086 if __debug__:
1087
1088 def _mesg(self, 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()
1094
1095 def _dump_ur(self, dict):
1096 # Dump untagged responses (in `dict').
1097 l = dict.items()
1098 if not l: return
1099 t = '\n\t\t'
1100 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1101 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1102
1103 def _log(self, line):
1104 # Keep log of last `_cmd_log_len' interactions for debugging.
1105 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1106 self._cmd_log_idx += 1
1107 if self._cmd_log_idx >= self._cmd_log_len:
1108 self._cmd_log_idx = 0
1109
1110 def print_log(self):
1111 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1112 i, n = self._cmd_log_idx, self._cmd_log_len
1113 while n:
1114 try:
1115 self._mesg(*self._cmd_log[i])
1116 except:
1117 pass
1118 i += 1
1119 if i >= self._cmd_log_len:
1120 i = 0
1121 n -= 1
1122
1123
1124
1125 try:
1126 import ssl
1127 except ImportError:
1128 pass
1129 else:
1130 class IMAP4_SSL(IMAP4):
1131
1132 """IMAP4 client class over SSL connection
1133
1134 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
1135
1136 host - host's name (default: localhost);
1137 port - port number (default: standard IMAP4 SSL port).
1138 keyfile - PEM formatted file that contains your private key (default: None);
1139 certfile - PEM formatted certificate chain file (default: None);
1140
1141 for more documentation see the docstring of the parent class IMAP4.
1142 """
1143
1144
1145 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1146 self.keyfile = keyfile
1147 self.certfile = certfile
1148 IMAP4.__init__(self, host, port)
1149
1150
1151 def open(self, host = '', port = IMAP4_SSL_PORT):
1152 """Setup connection to remote server on "host:port".
1153 (default: localhost:standard IMAP4 SSL port).
1154 This connection will be used by the routines:
1155 read, readline, send, shutdown.
1156 """
1157 self.host = host
1158 self.port = port
1159 self.sock = socket.create_connection((host, port))
1160 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
1161 self.file = self.sslobj.makefile('rb')
1162
1163
1164 def read(self, size):
1165 """Read 'size' bytes from remote."""
1166 return self.file.read(size)
1167
1168
1169 def readline(self):
1170 """Read line from remote."""
1171 return self.file.readline()
1172
1173
1174 def send(self, data):
1175 """Send data to remote."""
1176 bytes = len(data)
1177 while bytes > 0:
1178 sent = self.sslobj.write(data)
1179 if sent == bytes:
1180 break # avoid copy
1181 data = data[sent:]
1182 bytes = bytes - sent
1183
1184
1185 def shutdown(self):
1186 """Close I/O established in "open"."""
1187 self.file.close()
1188 self.sock.close()
1189
1190
1191 def socket(self):
1192 """Return socket instance used to connect to IMAP4 server.
1193
1194 socket = <instance>.socket()
1195 """
1196 return self.sock
1197
1198
1199 def ssl(self):
1200 """Return SSLObject instance used to communicate with the IMAP4 server.
1201
1202 ssl = ssl.wrap_socket(<instance>.socket)
1203 """
1204 return self.sslobj
1205
1206 __all__.append("IMAP4_SSL")
1207
1208
1209 class IMAP4_stream(IMAP4):
1210
1211 """IMAP4 client class over a stream
1212
1213 Instantiate with: IMAP4_stream(command)
1214
1215 where "command" is a string that can be passed to subprocess.Popen()
1216
1217 for more documentation see the docstring of the parent class IMAP4.
1218 """
1219
1220
1221 def __init__(self, command):
1222 self.command = command
1223 IMAP4.__init__(self)
1224
1225
1226 def open(self, host = None, port = None):
1227 """Setup a stream connection.
1228 This connection will be used by the routines:
1229 read, readline, send, shutdown.
1230 """
1231 self.host = None # For compatibility with parent class
1232 self.port = None
1233 self.sock = None
1234 self.file = None
1235 self.process = subprocess.Popen(self.command,
1236 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1237 shell=True, close_fds=True)
1238 self.writefile = self.process.stdin
1239 self.readfile = self.process.stdout
1240
1241
1242 def read(self, size):
1243 """Read 'size' bytes from remote."""
1244 return self.readfile.read(size)
1245
1246
1247 def readline(self):
1248 """Read line from remote."""
1249 return self.readfile.readline()
1250
1251
1252 def send(self, data):
1253 """Send data to remote."""
1254 self.writefile.write(data)
1255 self.writefile.flush()
1256
1257
1258 def shutdown(self):
1259 """Close I/O established in "open"."""
1260 self.readfile.close()
1261 self.writefile.close()
1262 self.process.wait()
1263
1264
1265
1266 class _Authenticator:
1267
1268 """Private class to provide en/decoding
1269 for base64-based authentication conversation.
1270 """
1271
1272 def __init__(self, mechinst):
1273 self.mech = mechinst # Callable object to provide/process data
1274
1275 def process(self, data):
1276 ret = self.mech(self.decode(data))
1277 if ret is None:
1278 return '*' # Abort conversation
1279 return self.encode(ret)
1280
1281 def encode(self, inp):
1282 #
1283 # Invoke binascii.b2a_base64 iteratively with
1284 # short even length buffers, strip the trailing
1285 # line feed from the result and append. "Even"
1286 # means a number that factors to both 6 and 8,
1287 # so when it gets to the end of the 8-bit input
1288 # there's no partial 6-bit output.
1289 #
1290 oup = ''
1291 while inp:
1292 if len(inp) > 48:
1293 t = inp[:48]
1294 inp = inp[48:]
1295 else:
1296 t = inp
1297 inp = ''
1298 e = binascii.b2a_base64(t)
1299 if e:
1300 oup = oup + e[:-1]
1301 return oup
1302
1303 def decode(self, inp):
1304 if not inp:
1305 return ''
1306 return binascii.a2b_base64(inp)
1307
1308
1309
1310 Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
1311 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
1312
1313 def Internaldate2tuple(resp):
1314 """Parse an IMAP4 INTERNALDATE string.
1315
1316 Return corresponding local time. The return value is a
1317 time.struct_time instance or None if the string has wrong format.
1318 """
1319
1320 mo = InternalDate.match(resp)
1321 if not mo:
1322 return None
1323
1324 mon = Mon2num[mo.group('mon')]
1325 zonen = mo.group('zonen')
1326
1327 day = int(mo.group('day'))
1328 year = int(mo.group('year'))
1329 hour = int(mo.group('hour'))
1330 min = int(mo.group('min'))
1331 sec = int(mo.group('sec'))
1332 zoneh = int(mo.group('zoneh'))
1333 zonem = int(mo.group('zonem'))
1334
1335 # INTERNALDATE timezone must be subtracted to get UT
1336
1337 zone = (zoneh*60 + zonem)*60
1338 if zonen == '-':
1339 zone = -zone
1340
1341 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
1342
1343 utc = time.mktime(tt)
1344
1345 # Following is necessary because the time module has no 'mkgmtime'.
1346 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
1347
1348 lt = time.localtime(utc)
1349 if time.daylight and lt[-1]:
1350 zone = zone + time.altzone
1351 else:
1352 zone = zone + time.timezone
1353
1354 return time.localtime(utc - zone)
1355
1356
1357
1358 def Int2AP(num):
1359
1360 """Convert integer to A-P string representation."""
1361
1362 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1363 num = int(abs(num))
1364 while num:
1365 num, mod = divmod(num, 16)
1366 val = AP[mod] + val
1367 return val
1368
1369
1370
1371 def ParseFlags(resp):
1372
1373 """Convert IMAP4 flags response to python tuple."""
1374
1375 mo = Flags.match(resp)
1376 if not mo:
1377 return ()
1378
1379 return tuple(mo.group('flags').split())
1380
1381
1382 def Time2Internaldate(date_time):
1383
1384 """Convert date_time to IMAP4 INTERNALDATE representation.
1385
1386 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
1387 date_time argument can be a number (int or float) representing
1388 seconds since epoch (as returned by time.time()), a 9-tuple
1389 representing local time (as returned by time.localtime()), or a
1390 double-quoted string. In the last case, it is assumed to already
1391 be in the correct format.
1392 """
1393
1394 if isinstance(date_time, (int, float)):
1395 tt = time.localtime(date_time)
1396 elif isinstance(date_time, (tuple, time.struct_time)):
1397 tt = date_time
1398 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
1399 return date_time # Assume in correct format
1400 else:
1401 raise ValueError("date_time not of a known type")
1402
1403 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1404 if dt[0] == '0':
1405 dt = ' ' + dt[1:]
1406 if time.daylight and tt[-1]:
1407 zone = -time.altzone
1408 else:
1409 zone = -time.timezone
1410 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
1411
1412
1413
1414 if __name__ == '__main__':
1415
1416 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1417 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1418 # to test the IMAP4_stream class
1419
1420 import getopt, getpass
1421
1422 try:
1423 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
1424 except getopt.error, val:
1425 optlist, args = (), ()
1426
1427 stream_command = None
1428 for opt,val in optlist:
1429 if opt == '-d':
1430 Debug = int(val)
1431 elif opt == '-s':
1432 stream_command = val
1433 if not args: args = (stream_command,)
1434
1435 if not args: args = ('',)
1436
1437 host = args[0]
1438
1439 USER = getpass.getuser()
1440 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
1441
1442 test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)sdata...%(lf)s' % {'user':USER, 'lf':'\n'}
1443 test_seq1 = (
1444 ('login', (USER, PASSWD)),
1445 ('create', ('/tmp/xxx 1',)),
1446 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1447 ('CREATE', ('/tmp/yyz 2',)),
1448 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1449 ('list', ('/tmp', 'yy*')),
1450 ('select', ('/tmp/yyz 2',)),
1451 ('search', (None, 'SUBJECT', 'test')),
1452 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
1453 ('store', ('1', 'FLAGS', '(\Deleted)')),
1454 ('namespace', ()),
1455 ('expunge', ()),
1456 ('recent', ()),
1457 ('close', ()),
1458 )
1459
1460 test_seq2 = (
1461 ('select', ()),
1462 ('response',('UIDVALIDITY',)),
1463 ('uid', ('SEARCH', 'ALL')),
1464 ('response', ('EXISTS',)),
1465 ('append', (None, None, None, test_mesg)),
1466 ('recent', ()),
1467 ('logout', ()),
1468 )
1469
1470 def run(cmd, args):
1471 M._mesg('%s %s' % (cmd, args))
1472 typ, dat = getattr(M, cmd)(*args)
1473 M._mesg('%s => %s %s' % (cmd, typ, dat))
1474 if typ == 'NO': raise dat[0]
1475 return dat
1476
1477 try:
1478 if stream_command:
1479 M = IMAP4_stream(stream_command)
1480 else:
1481 M = IMAP4(host)
1482 if M.state == 'AUTH':
1483 test_seq1 = test_seq1[1:] # Login not needed
1484 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1485 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
1486
1487 for cmd,args in test_seq1:
1488 run(cmd, args)
1489
1490 for ml in run('list', ('/tmp/', 'yy%')):
1491 mo = re.match(r'.*"([^"]+)"$', ml)
1492 if mo: path = mo.group(1)
1493 else: path = ml.split()[-1]
1494 run('delete', (path,))
1495
1496 for cmd,args in test_seq2:
1497 dat = run(cmd, args)
1498
1499 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1500 continue
1501
1502 uid = dat[-1].split()
1503 if not uid: continue
1504 run('uid', ('FETCH', '%s' % uid[-1],
1505 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
1506
1507 print '\nAll tests OK.'
1508
1509 except:
1510 print '\nTests failed.'
1511
1512 if not Debug:
1513 print '''
1514 If you would like to see debugging output,
1515 try: %s -d5
1516 ''' % sys.argv[0]
1517
1518 raise