blob: 5141d080ed33429032d2eee2b96a86fc67ccabfc [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 Lauderf2d7d152002-02-22 01:15:17 +000019__version__ = "2.50"
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'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000055 'PARTIAL': ('SELECTED',), # NB: obsolete
Tim Peters07e99cb2001-01-14 23:47:14 +000056 '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__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000161 self._cmd_log_len = 10
162 self._cmd_log_idx = 0
163 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000164 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000165 self._mesg('imaplib version %s' % __version__)
166 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000167
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 self.welcome = self._get_response()
169 if self.untagged_responses.has_key('PREAUTH'):
170 self.state = 'AUTH'
171 elif self.untagged_responses.has_key('OK'):
172 self.state = 'NONAUTH'
173 else:
174 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000175
Tim Peters07e99cb2001-01-14 23:47:14 +0000176 cap = 'CAPABILITY'
177 self._simple_command(cap)
178 if not self.untagged_responses.has_key(cap):
179 raise self.error('no CAPABILITY response from server')
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000180 self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000181
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 if __debug__:
183 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000184 self._mesg('CAPABILITIES: %s' % `self.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000185
Tim Peters07e99cb2001-01-14 23:47:14 +0000186 for version in AllowedVersions:
187 if not version in self.capabilities:
188 continue
189 self.PROTOCOL_VERSION = version
190 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000191
Tim Peters07e99cb2001-01-14 23:47:14 +0000192 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000193
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000194
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 def __getattr__(self, attr):
196 # Allow UPPERCASE variants of IMAP4 command methods.
197 if Commands.has_key(attr):
Piers Lauder15e5d532001-07-20 10:52:06 +0000198 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000199 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000200
201
202
Piers Lauder15e5d532001-07-20 10:52:06 +0000203 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000204
205
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 def open(self, host, port):
Piers Lauder15e5d532001-07-20 10:52:06 +0000207 """Setup connection to remote server on "host:port".
208 This connection will be used by the routines:
209 read, readline, send, shutdown.
210 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000211 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
212 self.sock.connect((self.host, self.port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000213 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000214
215
Piers Lauder15e5d532001-07-20 10:52:06 +0000216 def read(self, size):
217 """Read 'size' bytes from remote."""
218 return self.file.read(size)
219
220
221 def readline(self):
222 """Read line from remote."""
223 return self.file.readline()
224
225
226 def send(self, data):
227 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000228 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000229
Piers Lauderf2d7d152002-02-22 01:15:17 +0000230
Piers Lauder15e5d532001-07-20 10:52:06 +0000231 def shutdown(self):
232 """Close I/O established in "open"."""
233 self.file.close()
234 self.sock.close()
235
236
237 def socket(self):
238 """Return socket instance used to connect to IMAP4 server.
239
240 socket = <instance>.socket()
241 """
242 return self.sock
243
244
245
246 # Utility methods
247
248
Tim Peters07e99cb2001-01-14 23:47:14 +0000249 def recent(self):
250 """Return most recent 'RECENT' responses if any exist,
251 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000252
Tim Peters07e99cb2001-01-14 23:47:14 +0000253 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000254
Tim Peters07e99cb2001-01-14 23:47:14 +0000255 'data' is None if no new messages,
256 else list of RECENT responses, most recent last.
257 """
258 name = 'RECENT'
259 typ, dat = self._untagged_response('OK', [None], name)
260 if dat[-1]:
261 return typ, dat
262 typ, dat = self.noop() # Prod server for response
263 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000264
265
Tim Peters07e99cb2001-01-14 23:47:14 +0000266 def response(self, code):
267 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000268
Tim Peters07e99cb2001-01-14 23:47:14 +0000269 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000270
Tim Peters07e99cb2001-01-14 23:47:14 +0000271 (code, [data]) = <instance>.response(code)
272 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000273 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000274
275
Guido van Rossum26367a01998-09-28 15:34:46 +0000276
Tim Peters07e99cb2001-01-14 23:47:14 +0000277 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000278
279
Tim Peters07e99cb2001-01-14 23:47:14 +0000280 def append(self, mailbox, flags, date_time, message):
281 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000282
Tim Peters07e99cb2001-01-14 23:47:14 +0000283 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000284
Tim Peters07e99cb2001-01-14 23:47:14 +0000285 All args except `message' can be None.
286 """
287 name = 'APPEND'
288 if not mailbox:
289 mailbox = 'INBOX'
290 if flags:
291 if (flags[0],flags[-1]) != ('(',')'):
292 flags = '(%s)' % flags
293 else:
294 flags = None
295 if date_time:
296 date_time = Time2Internaldate(date_time)
297 else:
298 date_time = None
299 self.literal = message
300 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000301
302
Tim Peters07e99cb2001-01-14 23:47:14 +0000303 def authenticate(self, mechanism, authobject):
304 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000305
Tim Peters07e99cb2001-01-14 23:47:14 +0000306 'mechanism' specifies which authentication mechanism is to
307 be used - it must appear in <instance>.capabilities in the
308 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000309
Tim Peters07e99cb2001-01-14 23:47:14 +0000310 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000311
Tim Peters07e99cb2001-01-14 23:47:14 +0000312 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000313
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 It will be called to process server continuation responses.
315 It should return data that will be encoded and sent to server.
316 It should return None if the client abort response '*' should
317 be sent instead.
318 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000319 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000320 cap = 'AUTH=%s' % mech
321 if not cap in self.capabilities:
322 raise self.error("Server doesn't allow %s authentication." % mech)
323 self.literal = _Authenticator(authobject).process
324 typ, dat = self._simple_command('AUTHENTICATE', mech)
325 if typ != 'OK':
326 raise self.error(dat[-1])
327 self.state = 'AUTH'
328 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000329
330
Tim Peters07e99cb2001-01-14 23:47:14 +0000331 def check(self):
332 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000333
Tim Peters07e99cb2001-01-14 23:47:14 +0000334 (typ, [data]) = <instance>.check()
335 """
336 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000337
338
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 def close(self):
340 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000341
Tim Peters07e99cb2001-01-14 23:47:14 +0000342 Deleted messages are removed from writable mailbox.
343 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 (typ, [data]) = <instance>.close()
346 """
347 try:
348 typ, dat = self._simple_command('CLOSE')
349 finally:
350 self.state = 'AUTH'
351 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000352
353
Tim Peters07e99cb2001-01-14 23:47:14 +0000354 def copy(self, message_set, new_mailbox):
355 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000356
Tim Peters07e99cb2001-01-14 23:47:14 +0000357 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
358 """
359 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000360
361
Tim Peters07e99cb2001-01-14 23:47:14 +0000362 def create(self, mailbox):
363 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000364
Tim Peters07e99cb2001-01-14 23:47:14 +0000365 (typ, [data]) = <instance>.create(mailbox)
366 """
367 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000368
369
Tim Peters07e99cb2001-01-14 23:47:14 +0000370 def delete(self, mailbox):
371 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000372
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 (typ, [data]) = <instance>.delete(mailbox)
374 """
375 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000376
377
Tim Peters07e99cb2001-01-14 23:47:14 +0000378 def expunge(self):
379 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 'data' is list of 'EXPUNGE'd message numbers in order received.
386 """
387 name = 'EXPUNGE'
388 typ, dat = self._simple_command(name)
389 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000390
391
Tim Peters07e99cb2001-01-14 23:47:14 +0000392 def fetch(self, message_set, message_parts):
393 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 'message_parts' should be a string of selected parts
398 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000399
Tim Peters07e99cb2001-01-14 23:47:14 +0000400 'data' are tuples of message part envelope and data.
401 """
402 name = 'FETCH'
403 typ, dat = self._simple_command(name, message_set, message_parts)
404 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000405
406
Piers Lauder15e5d532001-07-20 10:52:06 +0000407 def getacl(self, mailbox):
408 """Get the ACLs for a mailbox.
409
410 (typ, [data]) = <instance>.getacl(mailbox)
411 """
412 typ, dat = self._simple_command('GETACL', mailbox)
413 return self._untagged_response(typ, dat, 'ACL')
414
415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 def list(self, directory='""', pattern='*'):
417 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000420
Tim Peters07e99cb2001-01-14 23:47:14 +0000421 'data' is list of LIST responses.
422 """
423 name = 'LIST'
424 typ, dat = self._simple_command(name, directory, pattern)
425 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000426
427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 def login(self, user, password):
429 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000432
Tim Peters07e99cb2001-01-14 23:47:14 +0000433 NB: 'password' will be quoted.
434 """
435 #if not 'AUTH=LOGIN' in self.capabilities:
436 # raise self.error("Server doesn't allow LOGIN authentication." % mech)
437 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
438 if typ != 'OK':
439 raise self.error(dat[-1])
440 self.state = 'AUTH'
441 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 def logout(self):
445 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000448
Tim Peters07e99cb2001-01-14 23:47:14 +0000449 Returns server 'BYE' response.
450 """
451 self.state = 'LOGOUT'
452 try: typ, dat = self._simple_command('LOGOUT')
453 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000454 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000455 if self.untagged_responses.has_key('BYE'):
456 return 'BYE', self.untagged_responses['BYE']
457 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000458
459
Tim Peters07e99cb2001-01-14 23:47:14 +0000460 def lsub(self, directory='""', pattern='*'):
461 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000464
Tim Peters07e99cb2001-01-14 23:47:14 +0000465 'data' are tuples of message part envelope and data.
466 """
467 name = 'LSUB'
468 typ, dat = self._simple_command(name, directory, pattern)
469 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000470
471
Piers Lauder15e5d532001-07-20 10:52:06 +0000472 def namespace(self):
473 """ Returns IMAP namespaces ala rfc2342
474
475 (typ, [data, ...]) = <instance>.namespace()
476 """
477 name = 'NAMESPACE'
478 typ, dat = self._simple_command(name)
479 return self._untagged_response(typ, dat, name)
480
481
Tim Peters07e99cb2001-01-14 23:47:14 +0000482 def noop(self):
483 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000484
Tim Peters07e99cb2001-01-14 23:47:14 +0000485 (typ, data) = <instance>.noop()
486 """
487 if __debug__:
488 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000489 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000490 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000491
492
Tim Peters07e99cb2001-01-14 23:47:14 +0000493 def partial(self, message_num, message_part, start, length):
494 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 'data' is tuple of message part envelope and data.
499 """
500 name = 'PARTIAL'
501 typ, dat = self._simple_command(name, message_num, message_part, start, length)
502 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000503
504
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 def rename(self, oldmailbox, newmailbox):
506 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 (typ, data) = <instance>.rename(oldmailbox, newmailbox)
509 """
510 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000511
512
Tim Peters07e99cb2001-01-14 23:47:14 +0000513 def search(self, charset, *criteria):
514 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000515
Tim Peters07e99cb2001-01-14 23:47:14 +0000516 (typ, [data]) = <instance>.search(charset, criterium, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000517
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 'data' is space separated list of matching message numbers.
519 """
520 name = 'SEARCH'
521 if charset:
Piers Lauder15e5d532001-07-20 10:52:06 +0000522 typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria)
523 else:
524 typ, dat = apply(self._simple_command, (name,) + criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000525 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000526
527
Tim Peters07e99cb2001-01-14 23:47:14 +0000528 def select(self, mailbox='INBOX', readonly=None):
529 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000532
Tim Peters07e99cb2001-01-14 23:47:14 +0000533 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000534
Tim Peters07e99cb2001-01-14 23:47:14 +0000535 'data' is count of messages in mailbox ('EXISTS' response).
536 """
537 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
538 self.untagged_responses = {} # Flush old responses.
539 self.is_readonly = readonly
540 if readonly:
541 name = 'EXAMINE'
542 else:
543 name = 'SELECT'
544 typ, dat = self._simple_command(name, mailbox)
545 if typ != 'OK':
546 self.state = 'AUTH' # Might have been 'SELECTED'
547 return typ, dat
548 self.state = 'SELECTED'
549 if self.untagged_responses.has_key('READ-ONLY') \
550 and not readonly:
551 if __debug__:
552 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000553 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000554 raise self.readonly('%s is not writable' % mailbox)
555 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000556
557
Piers Lauder15e5d532001-07-20 10:52:06 +0000558 def setacl(self, mailbox, who, what):
559 """Set a mailbox acl.
560
561 (typ, [data]) = <instance>.create(mailbox, who, what)
562 """
563 return self._simple_command('SETACL', mailbox, who, what)
564
565
566 def sort(self, sort_criteria, charset, *search_criteria):
567 """IMAP4rev1 extension SORT command.
568
569 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
570 """
571 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000572 #if not name in self.capabilities: # Let the server decide!
573 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000574 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000575 sort_criteria = '(%s)' % sort_criteria
Piers Lauder15e5d532001-07-20 10:52:06 +0000576 typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria)
577 return self._untagged_response(typ, dat, name)
578
579
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 def status(self, mailbox, names):
581 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000582
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 (typ, [data]) = <instance>.status(mailbox, names)
584 """
585 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000586 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000587 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 typ, dat = self._simple_command(name, mailbox, names)
589 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000590
591
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 def store(self, message_set, command, flags):
593 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 (typ, [data]) = <instance>.store(message_set, command, flags)
596 """
597 if (flags[0],flags[-1]) != ('(',')'):
598 flags = '(%s)' % flags # Avoid quoting the flags
599 typ, dat = self._simple_command('STORE', message_set, command, flags)
600 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000601
602
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 def subscribe(self, mailbox):
604 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000605
Tim Peters07e99cb2001-01-14 23:47:14 +0000606 (typ, [data]) = <instance>.subscribe(mailbox)
607 """
608 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000609
610
Tim Peters07e99cb2001-01-14 23:47:14 +0000611 def uid(self, command, *args):
612 """Execute "command arg ..." with messages identified by UID,
613 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000614
Tim Peters07e99cb2001-01-14 23:47:14 +0000615 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000616
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 Returns response appropriate to 'command'.
618 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000619 command = command.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000620 if not Commands.has_key(command):
621 raise self.error("Unknown IMAP4 UID command: %s" % command)
622 if self.state not in Commands[command]:
623 raise self.error('command %s illegal in state %s'
624 % (command, self.state))
625 name = 'UID'
626 typ, dat = apply(self._simple_command, (name, command) + args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000627 if command in ('SEARCH', 'SORT'):
628 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000629 else:
630 name = 'FETCH'
631 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000632
633
Tim Peters07e99cb2001-01-14 23:47:14 +0000634 def unsubscribe(self, mailbox):
635 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 (typ, [data]) = <instance>.unsubscribe(mailbox)
638 """
639 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000640
641
Tim Peters07e99cb2001-01-14 23:47:14 +0000642 def xatom(self, name, *args):
643 """Allow simple extension commands
644 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000645
Piers Lauder15e5d532001-07-20 10:52:06 +0000646 Assumes command is legal in current state.
647
Tim Peters07e99cb2001-01-14 23:47:14 +0000648 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000649
650 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000652 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000653 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000654 # raise self.error('unknown extension command: %s' % name)
655 if not Commands.has_key(name):
656 Commands[name] = (self.state,)
Tim Peters07e99cb2001-01-14 23:47:14 +0000657 return apply(self._simple_command, (name,) + args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000658
659
660
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000662
663
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000665
Tim Peters07e99cb2001-01-14 23:47:14 +0000666 if dat is None: dat = ''
667 ur = self.untagged_responses
668 if __debug__:
669 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000670 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 (typ, len(ur.get(typ,'')), dat))
672 if ur.has_key(typ):
673 ur[typ].append(dat)
674 else:
675 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000676
677
Tim Peters07e99cb2001-01-14 23:47:14 +0000678 def _check_bye(self):
679 bye = self.untagged_responses.get('BYE')
680 if bye:
681 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000682
683
Tim Peters07e99cb2001-01-14 23:47:14 +0000684 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000685
Tim Peters07e99cb2001-01-14 23:47:14 +0000686 if self.state not in Commands[name]:
687 self.literal = None
688 raise self.error(
689 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000690
Tim Peters07e99cb2001-01-14 23:47:14 +0000691 for typ in ('OK', 'NO', 'BAD'):
692 if self.untagged_responses.has_key(typ):
693 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000694
Tim Peters07e99cb2001-01-14 23:47:14 +0000695 if self.untagged_responses.has_key('READ-ONLY') \
696 and not self.is_readonly:
697 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000698
Tim Peters07e99cb2001-01-14 23:47:14 +0000699 tag = self._new_tag()
700 data = '%s %s' % (tag, name)
701 for arg in args:
702 if arg is None: continue
703 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000704
Tim Peters07e99cb2001-01-14 23:47:14 +0000705 literal = self.literal
706 if literal is not None:
707 self.literal = None
708 if type(literal) is type(self._command):
709 literator = literal
710 else:
711 literator = None
712 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000713
Tim Peters07e99cb2001-01-14 23:47:14 +0000714 if __debug__:
715 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000716 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000718 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000719
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000721 self.send('%s%s' % (data, CRLF))
722 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000724
Tim Peters07e99cb2001-01-14 23:47:14 +0000725 if literal is None:
726 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
Tim Peters07e99cb2001-01-14 23:47:14 +0000728 while 1:
729 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000730
Tim Peters07e99cb2001-01-14 23:47:14 +0000731 while self._get_response():
732 if self.tagged_commands[tag]: # BAD/NO?
733 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000734
Tim Peters07e99cb2001-01-14 23:47:14 +0000735 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 if literator:
738 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000739
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 if __debug__:
741 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000742 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000743
Tim Peters07e99cb2001-01-14 23:47:14 +0000744 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000745 self.send(literal)
746 self.send(CRLF)
747 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000748 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000749
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 if not literator:
751 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000752
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000754
755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 def _command_complete(self, name, tag):
757 self._check_bye()
758 try:
759 typ, data = self._get_tagged_response(tag)
760 except self.abort, val:
761 raise self.abort('command: %s => %s' % (name, val))
762 except self.error, val:
763 raise self.error('command: %s => %s' % (name, val))
764 self._check_bye()
765 if typ == 'BAD':
766 raise self.error('%s command error: %s %s' % (name, typ, data))
767 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000768
769
Tim Peters07e99cb2001-01-14 23:47:14 +0000770 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000771
Tim Peters07e99cb2001-01-14 23:47:14 +0000772 # Read response and store.
773 #
774 # Returns None for continuation responses,
775 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000776
Tim Peters07e99cb2001-01-14 23:47:14 +0000777 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000778
Tim Peters07e99cb2001-01-14 23:47:14 +0000779 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000780
Tim Peters07e99cb2001-01-14 23:47:14 +0000781 if self._match(self.tagre, resp):
782 tag = self.mo.group('tag')
783 if not self.tagged_commands.has_key(tag):
784 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000785
Tim Peters07e99cb2001-01-14 23:47:14 +0000786 typ = self.mo.group('type')
787 dat = self.mo.group('data')
788 self.tagged_commands[tag] = (typ, [dat])
789 else:
790 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000791
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 if not self._match(Untagged_response, resp):
795 if self._match(Untagged_status, resp):
796 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000797
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 if self.mo is None:
799 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000800
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 if self._match(Continuation, resp):
802 self.continuation_response = self.mo.group('data')
803 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000804
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000806
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 typ = self.mo.group('type')
808 dat = self.mo.group('data')
809 if dat is None: dat = '' # Null untagged response
810 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000811
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000813
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000817
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000818 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 if __debug__:
820 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000821 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000822 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000825
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000829
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
837 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 if __debug__:
840 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000841 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000844
845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000847
Tim Peters07e99cb2001-01-14 23:47:14 +0000848 while 1:
849 result = self.tagged_commands[tag]
850 if result is not None:
851 del self.tagged_commands[tag]
852 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000853
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 # Some have reported "unexpected response" exceptions.
855 # Note that ignoring them here causes loops.
856 # Instead, send me details of the unexpected response and
857 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000858
Tim Peters07e99cb2001-01-14 23:47:14 +0000859 try:
860 self._get_response()
861 except self.abort, val:
862 if __debug__:
863 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000864 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000865 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000866
867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
Piers Lauder15e5d532001-07-20 10:52:06 +0000870 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 if not line:
872 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000873
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 line = line[:-2]
877 if __debug__:
878 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000879 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000881 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 # Run compiled regular expression match method on 's'.
888 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000889
Tim Peters07e99cb2001-01-14 23:47:14 +0000890 self.mo = cre.match(s)
891 if __debug__:
892 if self.mo is not None and self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000893 self._mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000895
896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000898
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 tag = '%s%s' % (self.tagpre, self.tagnum)
900 self.tagnum = self.tagnum + 1
901 self.tagged_commands[tag] = None
902 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000906
Tim Peters07e99cb2001-01-14 23:47:14 +0000907 # Must quote command args if non-alphanumeric chars present,
908 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 if type(arg) is not type(''):
911 return arg
912 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
913 return arg
914 if self.mustquote.search(arg) is None:
915 return arg
916 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000917
918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000920
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000921 arg = arg.replace('\\', '\\\\')
922 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000925
926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 return self._command_complete(name, apply(self._command, (name,) + args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 if typ == 'NO':
935 return typ, dat
936 if not self.untagged_responses.has_key(name):
937 return typ, [None]
938 data = self.untagged_responses[name]
939 if __debug__:
940 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000941 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +0000942 del self.untagged_responses[name]
943 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000944
945
Piers Lauderf2d7d152002-02-22 01:15:17 +0000946 if __debug__:
947
948 def _mesg(self, s, secs=None):
949 if secs is None:
950 secs = time.time()
951 tm = time.strftime('%M:%S', time.localtime(secs))
952 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
953 sys.stderr.flush()
954
955 def _dump_ur(self, dict):
956 # Dump untagged responses (in `dict').
957 l = dict.items()
958 if not l: return
959 t = '\n\t\t'
960 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
961 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
962
963 def _log(self, line):
964 # Keep log of last `_cmd_log_len' interactions for debugging.
965 self._cmd_log[self._cmd_log_idx] = (line, time.time())
966 self._cmd_log_idx += 1
967 if self._cmd_log_idx >= self._cmd_log_len:
968 self._cmd_log_idx = 0
969
970 def print_log(self):
971 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
972 i, n = self._cmd_log_idx, self._cmd_log_len
973 while n:
974 try:
975 apply(self._mesg, self._cmd_log[i])
976 except:
977 pass
978 i += 1
979 if i >= self._cmd_log_len:
980 i = 0
981 n -= 1
982
983
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Guido van Rossumeda960a1998-06-18 14:24:28 +0000985class _Authenticator:
986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 """Private class to provide en/decoding
988 for base64-based authentication conversation.
989 """
Guido van Rossumeda960a1998-06-18 14:24:28 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 def __init__(self, mechinst):
992 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 def process(self, data):
995 ret = self.mech(self.decode(data))
996 if ret is None:
997 return '*' # Abort conversation
998 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 def encode(self, inp):
1001 #
1002 # Invoke binascii.b2a_base64 iteratively with
1003 # short even length buffers, strip the trailing
1004 # line feed from the result and append. "Even"
1005 # means a number that factors to both 6 and 8,
1006 # so when it gets to the end of the 8-bit input
1007 # there's no partial 6-bit output.
1008 #
1009 oup = ''
1010 while inp:
1011 if len(inp) > 48:
1012 t = inp[:48]
1013 inp = inp[48:]
1014 else:
1015 t = inp
1016 inp = ''
1017 e = binascii.b2a_base64(t)
1018 if e:
1019 oup = oup + e[:-1]
1020 return oup
1021
1022 def decode(self, inp):
1023 if not inp:
1024 return ''
1025 return binascii.a2b_base64(inp)
1026
Guido van Rossumeda960a1998-06-18 14:24:28 +00001027
1028
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001031
1032def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001034
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 Returns Python time module tuple.
1036 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 mo = InternalDate.match(resp)
1039 if not mo:
1040 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001041
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 mon = Mon2num[mo.group('mon')]
1043 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001045 day = int(mo.group('day'))
1046 year = int(mo.group('year'))
1047 hour = int(mo.group('hour'))
1048 min = int(mo.group('min'))
1049 sec = int(mo.group('sec'))
1050 zoneh = int(mo.group('zoneh'))
1051 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001052
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001054
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 zone = (zoneh*60 + zonem)*60
1056 if zonen == '-':
1057 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001058
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001060
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001062
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 # Following is necessary because the time module has no 'mkgmtime'.
1064 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 lt = time.localtime(utc)
1067 if time.daylight and lt[-1]:
1068 zone = zone + time.altzone
1069 else:
1070 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001071
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
1074
1075
1076def Int2AP(num):
1077
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1081 num = int(abs(num))
1082 while num:
1083 num, mod = divmod(num, 16)
1084 val = AP[mod] + val
1085 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001086
1087
1088
1089def ParseFlags(resp):
1090
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001092
Tim Peters07e99cb2001-01-14 23:47:14 +00001093 mo = Flags.match(resp)
1094 if not mo:
1095 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001096
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001097 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001098
1099
1100def Time2Internaldate(date_time):
1101
Tim Peters07e99cb2001-01-14 23:47:14 +00001102 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001103
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1105 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001106
Fred Drakedb519202002-01-05 17:17:09 +00001107 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001108 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001109 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001110 tt = date_time
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001111 elif isinstance(date_time, str):
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001113 else:
1114 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1117 if dt[0] == '0':
1118 dt = ' ' + dt[1:]
1119 if time.daylight and tt[-1]:
1120 zone = -time.altzone
1121 else:
1122 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001123 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001124
1125
1126
Guido van Rossum8c062211999-12-13 23:27:45 +00001127if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001128
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001129 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001130
Tim Peters07e99cb2001-01-14 23:47:14 +00001131 try:
1132 optlist, args = getopt.getopt(sys.argv[1:], 'd:')
1133 except getopt.error, val:
1134 pass
Guido van Rossum66d45132000-03-28 20:20:53 +00001135
Tim Peters07e99cb2001-01-14 23:47:14 +00001136 for opt,val in optlist:
1137 if opt == '-d':
1138 Debug = int(val)
Guido van Rossum66d45132000-03-28 20:20:53 +00001139
Tim Peters07e99cb2001-01-14 23:47:14 +00001140 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001141
Tim Peters07e99cb2001-01-14 23:47:14 +00001142 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001143
Tim Peters07e99cb2001-01-14 23:47:14 +00001144 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001145 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001146
Piers Laudere02f9042001-08-05 10:43:03 +00001147 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 +00001148 test_seq1 = (
1149 ('login', (USER, PASSWD)),
1150 ('create', ('/tmp/xxx 1',)),
1151 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1152 ('CREATE', ('/tmp/yyz 2',)),
1153 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1154 ('list', ('/tmp', 'yy*')),
1155 ('select', ('/tmp/yyz 2',)),
1156 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001157 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001158 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001159 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001160 ('expunge', ()),
1161 ('recent', ()),
1162 ('close', ()),
1163 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001164
Tim Peters07e99cb2001-01-14 23:47:14 +00001165 test_seq2 = (
1166 ('select', ()),
1167 ('response',('UIDVALIDITY',)),
1168 ('uid', ('SEARCH', 'ALL')),
1169 ('response', ('EXISTS',)),
1170 ('append', (None, None, None, test_mesg)),
1171 ('recent', ()),
1172 ('logout', ()),
1173 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001174
Tim Peters07e99cb2001-01-14 23:47:14 +00001175 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001176 M._mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001177 typ, dat = apply(getattr(M, cmd), args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001178 M._mesg('%s => %s %s' % (cmd, typ, dat))
Tim Peters07e99cb2001-01-14 23:47:14 +00001179 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001180
Tim Peters07e99cb2001-01-14 23:47:14 +00001181 try:
1182 M = IMAP4(host)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001183 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1184 M._mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001185
Tim Peters07e99cb2001-01-14 23:47:14 +00001186 for cmd,args in test_seq1:
1187 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001188
Tim Peters07e99cb2001-01-14 23:47:14 +00001189 for ml in run('list', ('/tmp/', 'yy%')):
1190 mo = re.match(r'.*"([^"]+)"$', ml)
1191 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001192 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001193 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001194
Tim Peters07e99cb2001-01-14 23:47:14 +00001195 for cmd,args in test_seq2:
1196 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001197
Tim Peters07e99cb2001-01-14 23:47:14 +00001198 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1199 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001200
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001201 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001202 if not uid: continue
1203 run('uid', ('FETCH', '%s' % uid[-1],
1204 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001205
Tim Peters07e99cb2001-01-14 23:47:14 +00001206 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001207
Tim Peters07e99cb2001-01-14 23:47:14 +00001208 except:
1209 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001210
Tim Peters07e99cb2001-01-14 23:47:14 +00001211 if not Debug:
1212 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001213If you would like to see debugging output,
1214try: %s -d5
1215''' % sys.argv[0]
1216
Tim Peters07e99cb2001-01-14 23:47:14 +00001217 raise