blob: 03513a36b204aab36c6e5bdf47d13c580dfb1817 [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."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000225 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000226
227 def shutdown(self):
228 """Close I/O established in "open"."""
229 self.file.close()
230 self.sock.close()
231
232
233 def socket(self):
234 """Return socket instance used to connect to IMAP4 server.
235
236 socket = <instance>.socket()
237 """
238 return self.sock
239
240
241
242 # Utility methods
243
244
Tim Peters07e99cb2001-01-14 23:47:14 +0000245 def recent(self):
246 """Return most recent 'RECENT' responses if any exist,
247 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000248
Tim Peters07e99cb2001-01-14 23:47:14 +0000249 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000250
Tim Peters07e99cb2001-01-14 23:47:14 +0000251 'data' is None if no new messages,
252 else list of RECENT responses, most recent last.
253 """
254 name = 'RECENT'
255 typ, dat = self._untagged_response('OK', [None], name)
256 if dat[-1]:
257 return typ, dat
258 typ, dat = self.noop() # Prod server for response
259 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000260
261
Tim Peters07e99cb2001-01-14 23:47:14 +0000262 def response(self, code):
263 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000264
Tim Peters07e99cb2001-01-14 23:47:14 +0000265 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000266
Tim Peters07e99cb2001-01-14 23:47:14 +0000267 (code, [data]) = <instance>.response(code)
268 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000269 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000270
271
Guido van Rossum26367a01998-09-28 15:34:46 +0000272
Tim Peters07e99cb2001-01-14 23:47:14 +0000273 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000274
275
Tim Peters07e99cb2001-01-14 23:47:14 +0000276 def append(self, mailbox, flags, date_time, message):
277 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000278
Tim Peters07e99cb2001-01-14 23:47:14 +0000279 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000280
Tim Peters07e99cb2001-01-14 23:47:14 +0000281 All args except `message' can be None.
282 """
283 name = 'APPEND'
284 if not mailbox:
285 mailbox = 'INBOX'
286 if flags:
287 if (flags[0],flags[-1]) != ('(',')'):
288 flags = '(%s)' % flags
289 else:
290 flags = None
291 if date_time:
292 date_time = Time2Internaldate(date_time)
293 else:
294 date_time = None
295 self.literal = message
296 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000297
298
Tim Peters07e99cb2001-01-14 23:47:14 +0000299 def authenticate(self, mechanism, authobject):
300 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000301
Tim Peters07e99cb2001-01-14 23:47:14 +0000302 'mechanism' specifies which authentication mechanism is to
303 be used - it must appear in <instance>.capabilities in the
304 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000305
Tim Peters07e99cb2001-01-14 23:47:14 +0000306 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000307
Tim Peters07e99cb2001-01-14 23:47:14 +0000308 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000309
Tim Peters07e99cb2001-01-14 23:47:14 +0000310 It will be called to process server continuation responses.
311 It should return data that will be encoded and sent to server.
312 It should return None if the client abort response '*' should
313 be sent instead.
314 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000315 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 cap = 'AUTH=%s' % mech
317 if not cap in self.capabilities:
318 raise self.error("Server doesn't allow %s authentication." % mech)
319 self.literal = _Authenticator(authobject).process
320 typ, dat = self._simple_command('AUTHENTICATE', mech)
321 if typ != 'OK':
322 raise self.error(dat[-1])
323 self.state = 'AUTH'
324 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000325
326
Tim Peters07e99cb2001-01-14 23:47:14 +0000327 def check(self):
328 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000329
Tim Peters07e99cb2001-01-14 23:47:14 +0000330 (typ, [data]) = <instance>.check()
331 """
332 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000333
334
Tim Peters07e99cb2001-01-14 23:47:14 +0000335 def close(self):
336 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000337
Tim Peters07e99cb2001-01-14 23:47:14 +0000338 Deleted messages are removed from writable mailbox.
339 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 (typ, [data]) = <instance>.close()
342 """
343 try:
344 typ, dat = self._simple_command('CLOSE')
345 finally:
346 self.state = 'AUTH'
347 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000348
349
Tim Peters07e99cb2001-01-14 23:47:14 +0000350 def copy(self, message_set, new_mailbox):
351 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000352
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
354 """
355 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000356
357
Tim Peters07e99cb2001-01-14 23:47:14 +0000358 def create(self, mailbox):
359 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000360
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 (typ, [data]) = <instance>.create(mailbox)
362 """
363 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000364
365
Tim Peters07e99cb2001-01-14 23:47:14 +0000366 def delete(self, mailbox):
367 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 (typ, [data]) = <instance>.delete(mailbox)
370 """
371 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000372
373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 def expunge(self):
375 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000376
Tim Peters07e99cb2001-01-14 23:47:14 +0000377 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
Tim Peters07e99cb2001-01-14 23:47:14 +0000379 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 'data' is list of 'EXPUNGE'd message numbers in order received.
382 """
383 name = 'EXPUNGE'
384 typ, dat = self._simple_command(name)
385 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000386
387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 def fetch(self, message_set, message_parts):
389 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000390
Tim Peters07e99cb2001-01-14 23:47:14 +0000391 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 'message_parts' should be a string of selected parts
394 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 'data' are tuples of message part envelope and data.
397 """
398 name = 'FETCH'
399 typ, dat = self._simple_command(name, message_set, message_parts)
400 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000401
402
Piers Lauder15e5d532001-07-20 10:52:06 +0000403 def getacl(self, mailbox):
404 """Get the ACLs for a mailbox.
405
406 (typ, [data]) = <instance>.getacl(mailbox)
407 """
408 typ, dat = self._simple_command('GETACL', mailbox)
409 return self._untagged_response(typ, dat, 'ACL')
410
411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 def list(self, directory='""', pattern='*'):
413 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000414
Tim Peters07e99cb2001-01-14 23:47:14 +0000415 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000416
Tim Peters07e99cb2001-01-14 23:47:14 +0000417 'data' is list of LIST responses.
418 """
419 name = 'LIST'
420 typ, dat = self._simple_command(name, directory, pattern)
421 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
423
Tim Peters07e99cb2001-01-14 23:47:14 +0000424 def login(self, user, password):
425 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000426
Tim Peters07e99cb2001-01-14 23:47:14 +0000427 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000428
Tim Peters07e99cb2001-01-14 23:47:14 +0000429 NB: 'password' will be quoted.
430 """
431 #if not 'AUTH=LOGIN' in self.capabilities:
432 # raise self.error("Server doesn't allow LOGIN authentication." % mech)
433 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
434 if typ != 'OK':
435 raise self.error(dat[-1])
436 self.state = 'AUTH'
437 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000438
439
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 def logout(self):
441 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
Tim Peters07e99cb2001-01-14 23:47:14 +0000443 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000444
Tim Peters07e99cb2001-01-14 23:47:14 +0000445 Returns server 'BYE' response.
446 """
447 self.state = 'LOGOUT'
448 try: typ, dat = self._simple_command('LOGOUT')
449 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000450 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000451 if self.untagged_responses.has_key('BYE'):
452 return 'BYE', self.untagged_responses['BYE']
453 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000454
455
Tim Peters07e99cb2001-01-14 23:47:14 +0000456 def lsub(self, directory='""', pattern='*'):
457 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000458
Tim Peters07e99cb2001-01-14 23:47:14 +0000459 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000460
Tim Peters07e99cb2001-01-14 23:47:14 +0000461 'data' are tuples of message part envelope and data.
462 """
463 name = 'LSUB'
464 typ, dat = self._simple_command(name, directory, pattern)
465 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000466
467
Piers Lauder15e5d532001-07-20 10:52:06 +0000468 def namespace(self):
469 """ Returns IMAP namespaces ala rfc2342
470
471 (typ, [data, ...]) = <instance>.namespace()
472 """
473 name = 'NAMESPACE'
474 typ, dat = self._simple_command(name)
475 return self._untagged_response(typ, dat, name)
476
477
Tim Peters07e99cb2001-01-14 23:47:14 +0000478 def noop(self):
479 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000480
Tim Peters07e99cb2001-01-14 23:47:14 +0000481 (typ, data) = <instance>.noop()
482 """
483 if __debug__:
484 if self.debug >= 3:
485 _dump_ur(self.untagged_responses)
486 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000487
488
Tim Peters07e99cb2001-01-14 23:47:14 +0000489 def partial(self, message_num, message_part, start, length):
490 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000491
Tim Peters07e99cb2001-01-14 23:47:14 +0000492 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000493
Tim Peters07e99cb2001-01-14 23:47:14 +0000494 'data' is tuple of message part envelope and data.
495 """
496 name = 'PARTIAL'
497 typ, dat = self._simple_command(name, message_num, message_part, start, length)
498 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000499
500
Tim Peters07e99cb2001-01-14 23:47:14 +0000501 def rename(self, oldmailbox, newmailbox):
502 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000503
Tim Peters07e99cb2001-01-14 23:47:14 +0000504 (typ, data) = <instance>.rename(oldmailbox, newmailbox)
505 """
506 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000507
508
Tim Peters07e99cb2001-01-14 23:47:14 +0000509 def search(self, charset, *criteria):
510 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 (typ, [data]) = <instance>.search(charset, criterium, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 'data' is space separated list of matching message numbers.
515 """
516 name = 'SEARCH'
517 if charset:
Piers Lauder15e5d532001-07-20 10:52:06 +0000518 typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria)
519 else:
520 typ, dat = apply(self._simple_command, (name,) + criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000522
523
Tim Peters07e99cb2001-01-14 23:47:14 +0000524 def select(self, mailbox='INBOX', readonly=None):
525 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000526
Tim Peters07e99cb2001-01-14 23:47:14 +0000527 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000528
Tim Peters07e99cb2001-01-14 23:47:14 +0000529 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 'data' is count of messages in mailbox ('EXISTS' response).
532 """
533 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
534 self.untagged_responses = {} # Flush old responses.
535 self.is_readonly = readonly
536 if readonly:
537 name = 'EXAMINE'
538 else:
539 name = 'SELECT'
540 typ, dat = self._simple_command(name, mailbox)
541 if typ != 'OK':
542 self.state = 'AUTH' # Might have been 'SELECTED'
543 return typ, dat
544 self.state = 'SELECTED'
545 if self.untagged_responses.has_key('READ-ONLY') \
546 and not readonly:
547 if __debug__:
548 if self.debug >= 1:
549 _dump_ur(self.untagged_responses)
550 raise self.readonly('%s is not writable' % mailbox)
551 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000552
553
Piers Lauder15e5d532001-07-20 10:52:06 +0000554 def setacl(self, mailbox, who, what):
555 """Set a mailbox acl.
556
557 (typ, [data]) = <instance>.create(mailbox, who, what)
558 """
559 return self._simple_command('SETACL', mailbox, who, what)
560
561
562 def sort(self, sort_criteria, charset, *search_criteria):
563 """IMAP4rev1 extension SORT command.
564
565 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
566 """
567 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000568 #if not name in self.capabilities: # Let the server decide!
569 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000570 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000571 sort_criteria = '(%s)' % sort_criteria
Piers Lauder15e5d532001-07-20 10:52:06 +0000572 typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria)
573 return self._untagged_response(typ, dat, name)
574
575
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 def status(self, mailbox, names):
577 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000578
Tim Peters07e99cb2001-01-14 23:47:14 +0000579 (typ, [data]) = <instance>.status(mailbox, names)
580 """
581 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000582 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000583 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000584 typ, dat = self._simple_command(name, mailbox, names)
585 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000586
587
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 def store(self, message_set, command, flags):
589 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000590
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 (typ, [data]) = <instance>.store(message_set, command, flags)
592 """
593 if (flags[0],flags[-1]) != ('(',')'):
594 flags = '(%s)' % flags # Avoid quoting the flags
595 typ, dat = self._simple_command('STORE', message_set, command, flags)
596 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000597
598
Tim Peters07e99cb2001-01-14 23:47:14 +0000599 def subscribe(self, mailbox):
600 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000601
Tim Peters07e99cb2001-01-14 23:47:14 +0000602 (typ, [data]) = <instance>.subscribe(mailbox)
603 """
604 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000605
606
Tim Peters07e99cb2001-01-14 23:47:14 +0000607 def uid(self, command, *args):
608 """Execute "command arg ..." with messages identified by UID,
609 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000610
Tim Peters07e99cb2001-01-14 23:47:14 +0000611 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000612
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 Returns response appropriate to 'command'.
614 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000615 command = command.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000616 if not Commands.has_key(command):
617 raise self.error("Unknown IMAP4 UID command: %s" % command)
618 if self.state not in Commands[command]:
619 raise self.error('command %s illegal in state %s'
620 % (command, self.state))
621 name = 'UID'
622 typ, dat = apply(self._simple_command, (name, command) + args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000623 if command in ('SEARCH', 'SORT'):
624 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 else:
626 name = 'FETCH'
627 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000628
629
Tim Peters07e99cb2001-01-14 23:47:14 +0000630 def unsubscribe(self, mailbox):
631 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000632
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 (typ, [data]) = <instance>.unsubscribe(mailbox)
634 """
635 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
637
Tim Peters07e99cb2001-01-14 23:47:14 +0000638 def xatom(self, name, *args):
639 """Allow simple extension commands
640 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000641
Piers Lauder15e5d532001-07-20 10:52:06 +0000642 Assumes command is legal in current state.
643
Tim Peters07e99cb2001-01-14 23:47:14 +0000644 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000645
646 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000647 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000648 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000649 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000650 # raise self.error('unknown extension command: %s' % name)
651 if not Commands.has_key(name):
652 Commands[name] = (self.state,)
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 return apply(self._simple_command, (name,) + args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
655
656
Tim Peters07e99cb2001-01-14 23:47:14 +0000657 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000658
659
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000661
Tim Peters07e99cb2001-01-14 23:47:14 +0000662 if dat is None: dat = ''
663 ur = self.untagged_responses
664 if __debug__:
665 if self.debug >= 5:
666 _mesg('untagged_responses[%s] %s += ["%s"]' %
667 (typ, len(ur.get(typ,'')), dat))
668 if ur.has_key(typ):
669 ur[typ].append(dat)
670 else:
671 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000672
673
Tim Peters07e99cb2001-01-14 23:47:14 +0000674 def _check_bye(self):
675 bye = self.untagged_responses.get('BYE')
676 if bye:
677 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000678
679
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000681
Tim Peters07e99cb2001-01-14 23:47:14 +0000682 if self.state not in Commands[name]:
683 self.literal = None
684 raise self.error(
685 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000686
Tim Peters07e99cb2001-01-14 23:47:14 +0000687 for typ in ('OK', 'NO', 'BAD'):
688 if self.untagged_responses.has_key(typ):
689 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000690
Tim Peters07e99cb2001-01-14 23:47:14 +0000691 if self.untagged_responses.has_key('READ-ONLY') \
692 and not self.is_readonly:
693 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000694
Tim Peters07e99cb2001-01-14 23:47:14 +0000695 tag = self._new_tag()
696 data = '%s %s' % (tag, name)
697 for arg in args:
698 if arg is None: continue
699 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000700
Tim Peters07e99cb2001-01-14 23:47:14 +0000701 literal = self.literal
702 if literal is not None:
703 self.literal = None
704 if type(literal) is type(self._command):
705 literator = literal
706 else:
707 literator = None
708 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000709
Tim Peters07e99cb2001-01-14 23:47:14 +0000710 if __debug__:
711 if self.debug >= 4:
712 _mesg('> %s' % data)
713 else:
714 _log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000715
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000717 self.send('%s%s' % (data, CRLF))
718 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000720
Tim Peters07e99cb2001-01-14 23:47:14 +0000721 if literal is None:
722 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000723
Tim Peters07e99cb2001-01-14 23:47:14 +0000724 while 1:
725 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000726
Tim Peters07e99cb2001-01-14 23:47:14 +0000727 while self._get_response():
728 if self.tagged_commands[tag]: # BAD/NO?
729 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000730
Tim Peters07e99cb2001-01-14 23:47:14 +0000731 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000732
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 if literator:
734 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000735
Tim Peters07e99cb2001-01-14 23:47:14 +0000736 if __debug__:
737 if self.debug >= 4:
738 _mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000739
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000741 self.send(literal)
742 self.send(CRLF)
743 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000744 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000745
Tim Peters07e99cb2001-01-14 23:47:14 +0000746 if not literator:
747 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000748
Tim Peters07e99cb2001-01-14 23:47:14 +0000749 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000750
751
Tim Peters07e99cb2001-01-14 23:47:14 +0000752 def _command_complete(self, name, tag):
753 self._check_bye()
754 try:
755 typ, data = self._get_tagged_response(tag)
756 except self.abort, val:
757 raise self.abort('command: %s => %s' % (name, val))
758 except self.error, val:
759 raise self.error('command: %s => %s' % (name, val))
760 self._check_bye()
761 if typ == 'BAD':
762 raise self.error('%s command error: %s %s' % (name, typ, data))
763 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000764
765
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000767
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 # Read response and store.
769 #
770 # Returns None for continuation responses,
771 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000772
Tim Peters07e99cb2001-01-14 23:47:14 +0000773 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000774
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000776
Tim Peters07e99cb2001-01-14 23:47:14 +0000777 if self._match(self.tagre, resp):
778 tag = self.mo.group('tag')
779 if not self.tagged_commands.has_key(tag):
780 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000781
Tim Peters07e99cb2001-01-14 23:47:14 +0000782 typ = self.mo.group('type')
783 dat = self.mo.group('data')
784 self.tagged_commands[tag] = (typ, [dat])
785 else:
786 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000787
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000789
Tim Peters07e99cb2001-01-14 23:47:14 +0000790 if not self._match(Untagged_response, resp):
791 if self._match(Untagged_status, resp):
792 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 if self.mo is None:
795 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000796
Tim Peters07e99cb2001-01-14 23:47:14 +0000797 if self._match(Continuation, resp):
798 self.continuation_response = self.mo.group('data')
799 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000800
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000802
Tim Peters07e99cb2001-01-14 23:47:14 +0000803 typ = self.mo.group('type')
804 dat = self.mo.group('data')
805 if dat is None: dat = '' # Null untagged response
806 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000807
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000809
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000811
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000813
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000814 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000815 if __debug__:
816 if self.debug >= 4:
817 _mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000818 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000821
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000825
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000829
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
833 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 if __debug__:
836 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
837 _mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000840
841
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000843
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 while 1:
845 result = self.tagged_commands[tag]
846 if result is not None:
847 del self.tagged_commands[tag]
848 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 # Some have reported "unexpected response" exceptions.
851 # Note that ignoring them here causes loops.
852 # Instead, send me details of the unexpected response and
853 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000854
Tim Peters07e99cb2001-01-14 23:47:14 +0000855 try:
856 self._get_response()
857 except self.abort, val:
858 if __debug__:
859 if self.debug >= 1:
860 print_log()
861 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000862
863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000865
Piers Lauder15e5d532001-07-20 10:52:06 +0000866 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 if not line:
868 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 line = line[:-2]
873 if __debug__:
874 if self.debug >= 4:
875 _mesg('< %s' % line)
876 else:
877 _log('< %s' % line)
878 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000879
880
Tim Peters07e99cb2001-01-14 23:47:14 +0000881 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 # Run compiled regular expression match method on 's'.
884 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 self.mo = cre.match(s)
887 if __debug__:
888 if self.mo is not None and self.debug >= 5:
889 _mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
890 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000891
892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000894
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 tag = '%s%s' % (self.tagpre, self.tagnum)
896 self.tagnum = self.tagnum + 1
897 self.tagged_commands[tag] = None
898 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000902
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 # Must quote command args if non-alphanumeric chars present,
904 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000905
Tim Peters07e99cb2001-01-14 23:47:14 +0000906 if type(arg) is not type(''):
907 return arg
908 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
909 return arg
910 if self.mustquote.search(arg) is None:
911 return arg
912 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000913
914
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000916
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000917 arg = arg.replace('\\', '\\\\')
918 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000919
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000921
922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 return self._command_complete(name, apply(self._command, (name,) + args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 if typ == 'NO':
931 return typ, dat
932 if not self.untagged_responses.has_key(name):
933 return typ, [None]
934 data = self.untagged_responses[name]
935 if __debug__:
936 if self.debug >= 5:
937 _mesg('untagged_responses[%s] => %s' % (name, data))
938 del self.untagged_responses[name]
939 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
941
942
Guido van Rossumeda960a1998-06-18 14:24:28 +0000943class _Authenticator:
944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 """Private class to provide en/decoding
946 for base64-based authentication conversation.
947 """
Guido van Rossumeda960a1998-06-18 14:24:28 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 def __init__(self, mechinst):
950 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +0000951
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 def process(self, data):
953 ret = self.mech(self.decode(data))
954 if ret is None:
955 return '*' # Abort conversation
956 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000957
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 def encode(self, inp):
959 #
960 # Invoke binascii.b2a_base64 iteratively with
961 # short even length buffers, strip the trailing
962 # line feed from the result and append. "Even"
963 # means a number that factors to both 6 and 8,
964 # so when it gets to the end of the 8-bit input
965 # there's no partial 6-bit output.
966 #
967 oup = ''
968 while inp:
969 if len(inp) > 48:
970 t = inp[:48]
971 inp = inp[48:]
972 else:
973 t = inp
974 inp = ''
975 e = binascii.b2a_base64(t)
976 if e:
977 oup = oup + e[:-1]
978 return oup
979
980 def decode(self, inp):
981 if not inp:
982 return ''
983 return binascii.a2b_base64(inp)
984
Guido van Rossumeda960a1998-06-18 14:24:28 +0000985
986
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000987Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000989
990def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 Returns Python time module tuple.
994 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000995
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 mo = InternalDate.match(resp)
997 if not mo:
998 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 mon = Mon2num[mo.group('mon')]
1001 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001003 day = int(mo.group('day'))
1004 year = int(mo.group('year'))
1005 hour = int(mo.group('hour'))
1006 min = int(mo.group('min'))
1007 sec = int(mo.group('sec'))
1008 zoneh = int(mo.group('zoneh'))
1009 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001010
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001012
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 zone = (zoneh*60 + zonem)*60
1014 if zonen == '-':
1015 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001016
Tim Peters07e99cb2001-01-14 23:47:14 +00001017 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001018
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001020
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 # Following is necessary because the time module has no 'mkgmtime'.
1022 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 lt = time.localtime(utc)
1025 if time.daylight and lt[-1]:
1026 zone = zone + time.altzone
1027 else:
1028 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001031
1032
1033
1034def Int2AP(num):
1035
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1039 num = int(abs(num))
1040 while num:
1041 num, mod = divmod(num, 16)
1042 val = AP[mod] + val
1043 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
1045
1046
1047def ParseFlags(resp):
1048
Tim Peters07e99cb2001-01-14 23:47:14 +00001049 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001050
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 mo = Flags.match(resp)
1052 if not mo:
1053 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001054
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001055 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001056
1057
1058def Time2Internaldate(date_time):
1059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001061
Tim Peters07e99cb2001-01-14 23:47:14 +00001062 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1063 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001064
Fred Drakedb519202002-01-05 17:17:09 +00001065 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001067 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 tt = date_time
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001069 elif isinstance(date_time, str):
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001071 else:
1072 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1075 if dt[0] == '0':
1076 dt = ' ' + dt[1:]
1077 if time.daylight and tt[-1]:
1078 zone = -time.altzone
1079 else:
1080 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001081 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001082
1083
1084
Guido van Rossumeda960a1998-06-18 14:24:28 +00001085if __debug__:
1086
Tim Peters07e99cb2001-01-14 23:47:14 +00001087 def _mesg(s, secs=None):
1088 if secs is None:
1089 secs = time.time()
1090 tm = time.strftime('%M:%S', time.localtime(secs))
1091 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1092 sys.stderr.flush()
Guido van Rossum26367a01998-09-28 15:34:46 +00001093
Tim Peters07e99cb2001-01-14 23:47:14 +00001094 def _dump_ur(dict):
1095 # Dump untagged responses (in `dict').
1096 l = dict.items()
1097 if not l: return
1098 t = '\n\t\t'
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001099 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
Piers Lauder34d97052001-07-20 10:28:51 +00001100 _mesg('untagged responses dump:%s%s' % (t, t.join(l)))
Guido van Rossumeda960a1998-06-18 14:24:28 +00001101
Tim Peters07e99cb2001-01-14 23:47:14 +00001102 _cmd_log = [] # Last `_cmd_log_len' interactions
1103 _cmd_log_len = 10
Guido van Rossum8c062211999-12-13 23:27:45 +00001104
Tim Peters07e99cb2001-01-14 23:47:14 +00001105 def _log(line):
1106 # Keep log of last `_cmd_log_len' interactions for debugging.
1107 if len(_cmd_log) == _cmd_log_len:
1108 del _cmd_log[0]
1109 _cmd_log.append((time.time(), line))
Guido van Rossum8c062211999-12-13 23:27:45 +00001110
Tim Peters07e99cb2001-01-14 23:47:14 +00001111 def print_log():
1112 _mesg('last %d IMAP4 interactions:' % len(_cmd_log))
1113 for secs,line in _cmd_log:
1114 _mesg(line, secs)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001115
1116
Guido van Rossum8c062211999-12-13 23:27:45 +00001117
1118if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001119
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001120 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001121
Tim Peters07e99cb2001-01-14 23:47:14 +00001122 try:
1123 optlist, args = getopt.getopt(sys.argv[1:], 'd:')
1124 except getopt.error, val:
1125 pass
Guido van Rossum66d45132000-03-28 20:20:53 +00001126
Tim Peters07e99cb2001-01-14 23:47:14 +00001127 for opt,val in optlist:
1128 if opt == '-d':
1129 Debug = int(val)
Guido van Rossum66d45132000-03-28 20:20:53 +00001130
Tim Peters07e99cb2001-01-14 23:47:14 +00001131 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001132
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001134
Tim Peters07e99cb2001-01-14 23:47:14 +00001135 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001136 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001137
Piers Laudere02f9042001-08-05 10:43:03 +00001138 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 +00001139 test_seq1 = (
1140 ('login', (USER, PASSWD)),
1141 ('create', ('/tmp/xxx 1',)),
1142 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1143 ('CREATE', ('/tmp/yyz 2',)),
1144 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1145 ('list', ('/tmp', 'yy*')),
1146 ('select', ('/tmp/yyz 2',)),
1147 ('search', (None, 'SUBJECT', 'test')),
1148 ('partial', ('1', 'RFC822', 1, 1024)),
1149 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001150 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001151 ('expunge', ()),
1152 ('recent', ()),
1153 ('close', ()),
1154 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001155
Tim Peters07e99cb2001-01-14 23:47:14 +00001156 test_seq2 = (
1157 ('select', ()),
1158 ('response',('UIDVALIDITY',)),
1159 ('uid', ('SEARCH', 'ALL')),
1160 ('response', ('EXISTS',)),
1161 ('append', (None, None, None, test_mesg)),
1162 ('recent', ()),
1163 ('logout', ()),
1164 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001165
Tim Peters07e99cb2001-01-14 23:47:14 +00001166 def run(cmd, args):
1167 _mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001168 typ, dat = apply(getattr(M, cmd), args)
Tim Peters07e99cb2001-01-14 23:47:14 +00001169 _mesg('%s => %s %s' % (cmd, typ, dat))
1170 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001171
Tim Peters07e99cb2001-01-14 23:47:14 +00001172 try:
1173 M = IMAP4(host)
1174 _mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Piers Lauder15e5d532001-07-20 10:52:06 +00001175 _mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001176
Tim Peters07e99cb2001-01-14 23:47:14 +00001177 for cmd,args in test_seq1:
1178 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001179
Tim Peters07e99cb2001-01-14 23:47:14 +00001180 for ml in run('list', ('/tmp/', 'yy%')):
1181 mo = re.match(r'.*"([^"]+)"$', ml)
1182 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001183 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001184 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001185
Tim Peters07e99cb2001-01-14 23:47:14 +00001186 for cmd,args in test_seq2:
1187 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001188
Tim Peters07e99cb2001-01-14 23:47:14 +00001189 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1190 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001191
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001192 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001193 if not uid: continue
1194 run('uid', ('FETCH', '%s' % uid[-1],
1195 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001196
Tim Peters07e99cb2001-01-14 23:47:14 +00001197 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001198
Tim Peters07e99cb2001-01-14 23:47:14 +00001199 except:
1200 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001201
Tim Peters07e99cb2001-01-14 23:47:14 +00001202 if not Debug:
1203 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001204If you would like to see debugging output,
1205try: %s -d5
1206''' % sys.argv[0]
1207
Tim Peters07e99cb2001-01-14 23:47:14 +00001208 raise