blob: 266acb668d20ecede4bcb0aed0e4f9d373038c3f [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.
Piers Laudera4f83132002-03-08 01:53:24 +000018# IMAP4_SSL contributed by Tino Lange <Tino.Lange@isg.de> March 2002.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000019
Piers Laudera4f83132002-03-08 01:53:24 +000020__version__ = "2.51"
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000021
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +000022import binascii, re, socket, time, random, sys
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000023
Barry Warsawf4493912001-01-24 04:16:09 +000024__all__ = ["IMAP4", "Internaldate2tuple",
25 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000026
Tim Peters07e99cb2001-01-14 23:47:14 +000027# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000028
29CRLF = '\r\n'
30Debug = 0
31IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000032IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000033AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000034
Tim Peters07e99cb2001-01-14 23:47:14 +000035# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000036
37Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000038 # name valid states
39 'APPEND': ('AUTH', 'SELECTED'),
40 'AUTHENTICATE': ('NONAUTH',),
41 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
42 'CHECK': ('SELECTED',),
43 'CLOSE': ('SELECTED',),
44 'COPY': ('SELECTED',),
45 'CREATE': ('AUTH', 'SELECTED'),
46 'DELETE': ('AUTH', 'SELECTED'),
47 'EXAMINE': ('AUTH', 'SELECTED'),
48 'EXPUNGE': ('SELECTED',),
49 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000050 'GETACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000051 'LIST': ('AUTH', 'SELECTED'),
52 'LOGIN': ('NONAUTH',),
53 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
54 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000055 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000056 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000057 'PARTIAL': ('SELECTED',), # NB: obsolete
Tim Peters07e99cb2001-01-14 23:47:14 +000058 'RENAME': ('AUTH', 'SELECTED'),
59 'SEARCH': ('SELECTED',),
60 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000061 'SETACL': ('AUTH', 'SELECTED'),
62 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000063 'STATUS': ('AUTH', 'SELECTED'),
64 'STORE': ('SELECTED',),
65 'SUBSCRIBE': ('AUTH', 'SELECTED'),
66 'UID': ('SELECTED',),
67 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
68 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000069
Tim Peters07e99cb2001-01-14 23:47:14 +000070# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000071
Guido van Rossumeda960a1998-06-18 14:24:28 +000072Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000073Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
74InternalDate = re.compile(r'.*INTERNALDATE "'
Tim Peters07e99cb2001-01-14 23:47:14 +000075 r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
76 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
77 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
78 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000079Literal = re.compile(r'.*{(?P<size>\d+)}$')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000080Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000081Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000082Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
83
84
85
86class IMAP4:
87
Tim Peters07e99cb2001-01-14 23:47:14 +000088 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000089
Tim Peters07e99cb2001-01-14 23:47:14 +000090 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000091
Tim Peters07e99cb2001-01-14 23:47:14 +000092 host - host's name (default: localhost);
93 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000094
Tim Peters07e99cb2001-01-14 23:47:14 +000095 All IMAP4rev1 commands are supported by methods of the same
96 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +000097
Tim Peters07e99cb2001-01-14 23:47:14 +000098 All arguments to commands are converted to strings, except for
99 AUTHENTICATE, and the last argument to APPEND which is passed as
100 an IMAP4 literal. If necessary (the string contains any
101 non-printing characters or white-space and isn't enclosed with
102 either parentheses or double quotes) each string is quoted.
103 However, the 'password' argument to the LOGIN command is always
104 quoted. If you want to avoid having an argument string quoted
105 (eg: the 'flags' argument to STORE) then enclose the string in
106 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000107
Tim Peters07e99cb2001-01-14 23:47:14 +0000108 Each command returns a tuple: (type, [data, ...]) where 'type'
109 is usually 'OK' or 'NO', and 'data' is either the text from the
110 tagged response, or untagged results from command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000111
Tim Peters07e99cb2001-01-14 23:47:14 +0000112 Errors raise the exception class <instance>.error("<reason>").
113 IMAP4 server errors raise <instance>.abort("<reason>"),
114 which is a sub-class of 'error'. Mailbox status changes
115 from READ-WRITE to READ-ONLY raise the exception class
116 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000117
Tim Peters07e99cb2001-01-14 23:47:14 +0000118 "error" exceptions imply a program error.
119 "abort" exceptions imply the connection should be reset, and
120 the command re-tried.
121 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000122
Tim Peters07e99cb2001-01-14 23:47:14 +0000123 Note: to use this module, you must read the RFCs pertaining
124 to the IMAP4 protocol, as the semantics of the arguments to
125 each IMAP4 command are left to the invoker, not to mention
126 the results.
127 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 class error(Exception): pass # Logical errors - debug required
130 class abort(error): pass # Service errors - close and retry
131 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000132
Tim Peters07e99cb2001-01-14 23:47:14 +0000133 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000134
Tim Peters07e99cb2001-01-14 23:47:14 +0000135 def __init__(self, host = '', port = IMAP4_PORT):
136 self.host = host
137 self.port = port
138 self.debug = Debug
139 self.state = 'LOGOUT'
140 self.literal = None # A literal argument to a command
141 self.tagged_commands = {} # Tagged commands awaiting response
142 self.untagged_responses = {} # {typ: [data, ...], ...}
143 self.continuation_response = '' # Last continuation response
144 self.is_readonly = None # READ-ONLY desired state
145 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000146
Tim Peters07e99cb2001-01-14 23:47:14 +0000147 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000148
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000150
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 # Create unique tag for this session,
152 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 self.tagpre = Int2AP(random.randint(0, 31999))
155 self.tagre = re.compile(r'(?P<tag>'
156 + self.tagpre
157 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000158
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 # Get server welcome message,
160 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000163 self._cmd_log_len = 10
164 self._cmd_log_idx = 0
165 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000166 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000167 self._mesg('imaplib version %s' % __version__)
168 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000169
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 self.welcome = self._get_response()
171 if self.untagged_responses.has_key('PREAUTH'):
172 self.state = 'AUTH'
173 elif self.untagged_responses.has_key('OK'):
174 self.state = 'NONAUTH'
175 else:
176 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000177
Tim Peters07e99cb2001-01-14 23:47:14 +0000178 cap = 'CAPABILITY'
179 self._simple_command(cap)
180 if not self.untagged_responses.has_key(cap):
181 raise self.error('no CAPABILITY response from server')
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000182 self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
Tim Peters07e99cb2001-01-14 23:47:14 +0000184 if __debug__:
185 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000186 self._mesg('CAPABILITIES: %s' % `self.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000187
Tim Peters07e99cb2001-01-14 23:47:14 +0000188 for version in AllowedVersions:
189 if not version in self.capabilities:
190 continue
191 self.PROTOCOL_VERSION = version
192 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000193
Tim Peters07e99cb2001-01-14 23:47:14 +0000194 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000195
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000196
Tim Peters07e99cb2001-01-14 23:47:14 +0000197 def __getattr__(self, attr):
198 # Allow UPPERCASE variants of IMAP4 command methods.
199 if Commands.has_key(attr):
Piers Lauder15e5d532001-07-20 10:52:06 +0000200 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000201 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000202
203
204
Piers Lauder15e5d532001-07-20 10:52:06 +0000205 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000206
207
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 def open(self, host, port):
Piers Lauder15e5d532001-07-20 10:52:06 +0000209 """Setup connection to remote server on "host:port".
210 This connection will be used by the routines:
211 read, readline, send, shutdown.
212 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000213 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
214 self.sock.connect((self.host, self.port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000215 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000216
217
Piers Lauder15e5d532001-07-20 10:52:06 +0000218 def read(self, size):
219 """Read 'size' bytes from remote."""
220 return self.file.read(size)
221
222
223 def readline(self):
224 """Read line from remote."""
225 return self.file.readline()
226
227
228 def send(self, data):
229 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000230 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000231
Piers Lauderf2d7d152002-02-22 01:15:17 +0000232
Piers Lauder15e5d532001-07-20 10:52:06 +0000233 def shutdown(self):
234 """Close I/O established in "open"."""
235 self.file.close()
236 self.sock.close()
237
238
239 def socket(self):
240 """Return socket instance used to connect to IMAP4 server.
241
242 socket = <instance>.socket()
243 """
244 return self.sock
245
246
247
248 # Utility methods
249
250
Tim Peters07e99cb2001-01-14 23:47:14 +0000251 def recent(self):
252 """Return most recent 'RECENT' responses if any exist,
253 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000254
Tim Peters07e99cb2001-01-14 23:47:14 +0000255 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000256
Tim Peters07e99cb2001-01-14 23:47:14 +0000257 'data' is None if no new messages,
258 else list of RECENT responses, most recent last.
259 """
260 name = 'RECENT'
261 typ, dat = self._untagged_response('OK', [None], name)
262 if dat[-1]:
263 return typ, dat
264 typ, dat = self.noop() # Prod server for response
265 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000266
267
Tim Peters07e99cb2001-01-14 23:47:14 +0000268 def response(self, code):
269 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000270
Tim Peters07e99cb2001-01-14 23:47:14 +0000271 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000272
Tim Peters07e99cb2001-01-14 23:47:14 +0000273 (code, [data]) = <instance>.response(code)
274 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000275 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000276
277
Guido van Rossum26367a01998-09-28 15:34:46 +0000278
Tim Peters07e99cb2001-01-14 23:47:14 +0000279 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000280
281
Tim Peters07e99cb2001-01-14 23:47:14 +0000282 def append(self, mailbox, flags, date_time, message):
283 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000284
Tim Peters07e99cb2001-01-14 23:47:14 +0000285 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000286
Tim Peters07e99cb2001-01-14 23:47:14 +0000287 All args except `message' can be None.
288 """
289 name = 'APPEND'
290 if not mailbox:
291 mailbox = 'INBOX'
292 if flags:
293 if (flags[0],flags[-1]) != ('(',')'):
294 flags = '(%s)' % flags
295 else:
296 flags = None
297 if date_time:
298 date_time = Time2Internaldate(date_time)
299 else:
300 date_time = None
301 self.literal = message
302 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000303
304
Tim Peters07e99cb2001-01-14 23:47:14 +0000305 def authenticate(self, mechanism, authobject):
306 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000307
Tim Peters07e99cb2001-01-14 23:47:14 +0000308 'mechanism' specifies which authentication mechanism is to
309 be used - it must appear in <instance>.capabilities in the
310 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000311
Tim Peters07e99cb2001-01-14 23:47:14 +0000312 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000313
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000315
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 It will be called to process server continuation responses.
317 It should return data that will be encoded and sent to server.
318 It should return None if the client abort response '*' should
319 be sent instead.
320 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000321 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000322 cap = 'AUTH=%s' % mech
323 if not cap in self.capabilities:
324 raise self.error("Server doesn't allow %s authentication." % mech)
325 self.literal = _Authenticator(authobject).process
326 typ, dat = self._simple_command('AUTHENTICATE', mech)
327 if typ != 'OK':
328 raise self.error(dat[-1])
329 self.state = 'AUTH'
330 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000331
332
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 def check(self):
334 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000335
Tim Peters07e99cb2001-01-14 23:47:14 +0000336 (typ, [data]) = <instance>.check()
337 """
338 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000339
340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 def close(self):
342 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000343
Tim Peters07e99cb2001-01-14 23:47:14 +0000344 Deleted messages are removed from writable mailbox.
345 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000346
Tim Peters07e99cb2001-01-14 23:47:14 +0000347 (typ, [data]) = <instance>.close()
348 """
349 try:
350 typ, dat = self._simple_command('CLOSE')
351 finally:
352 self.state = 'AUTH'
353 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000354
355
Tim Peters07e99cb2001-01-14 23:47:14 +0000356 def copy(self, message_set, new_mailbox):
357 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000358
Tim Peters07e99cb2001-01-14 23:47:14 +0000359 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
360 """
361 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000362
363
Tim Peters07e99cb2001-01-14 23:47:14 +0000364 def create(self, mailbox):
365 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 (typ, [data]) = <instance>.create(mailbox)
368 """
369 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000370
371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 def delete(self, mailbox):
373 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000374
Tim Peters07e99cb2001-01-14 23:47:14 +0000375 (typ, [data]) = <instance>.delete(mailbox)
376 """
377 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 def expunge(self):
381 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000386
Tim Peters07e99cb2001-01-14 23:47:14 +0000387 'data' is list of 'EXPUNGE'd message numbers in order received.
388 """
389 name = 'EXPUNGE'
390 typ, dat = self._simple_command(name)
391 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000392
393
Tim Peters07e99cb2001-01-14 23:47:14 +0000394 def fetch(self, message_set, message_parts):
395 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 'message_parts' should be a string of selected parts
400 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000401
Tim Peters07e99cb2001-01-14 23:47:14 +0000402 'data' are tuples of message part envelope and data.
403 """
404 name = 'FETCH'
405 typ, dat = self._simple_command(name, message_set, message_parts)
406 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
408
Piers Lauder15e5d532001-07-20 10:52:06 +0000409 def getacl(self, mailbox):
410 """Get the ACLs for a mailbox.
411
412 (typ, [data]) = <instance>.getacl(mailbox)
413 """
414 typ, dat = self._simple_command('GETACL', mailbox)
415 return self._untagged_response(typ, dat, 'ACL')
416
417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 def list(self, directory='""', pattern='*'):
419 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000420
Tim Peters07e99cb2001-01-14 23:47:14 +0000421 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 'data' is list of LIST responses.
424 """
425 name = 'LIST'
426 typ, dat = self._simple_command(name, directory, pattern)
427 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000428
429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 def login(self, user, password):
431 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000432
Tim Peters07e99cb2001-01-14 23:47:14 +0000433 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000434
Tim Peters07e99cb2001-01-14 23:47:14 +0000435 NB: 'password' will be quoted.
436 """
437 #if not 'AUTH=LOGIN' in self.capabilities:
438 # raise self.error("Server doesn't allow LOGIN authentication." % mech)
439 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
440 if typ != 'OK':
441 raise self.error(dat[-1])
442 self.state = 'AUTH'
443 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000444
445
Tim Peters07e99cb2001-01-14 23:47:14 +0000446 def logout(self):
447 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000448
Tim Peters07e99cb2001-01-14 23:47:14 +0000449 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000450
Tim Peters07e99cb2001-01-14 23:47:14 +0000451 Returns server 'BYE' response.
452 """
453 self.state = 'LOGOUT'
454 try: typ, dat = self._simple_command('LOGOUT')
455 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000456 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 if self.untagged_responses.has_key('BYE'):
458 return 'BYE', self.untagged_responses['BYE']
459 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000460
461
Tim Peters07e99cb2001-01-14 23:47:14 +0000462 def lsub(self, directory='""', pattern='*'):
463 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000464
Tim Peters07e99cb2001-01-14 23:47:14 +0000465 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000466
Tim Peters07e99cb2001-01-14 23:47:14 +0000467 'data' are tuples of message part envelope and data.
468 """
469 name = 'LSUB'
470 typ, dat = self._simple_command(name, directory, pattern)
471 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000472
473
Piers Lauder15e5d532001-07-20 10:52:06 +0000474 def namespace(self):
475 """ Returns IMAP namespaces ala rfc2342
476
477 (typ, [data, ...]) = <instance>.namespace()
478 """
479 name = 'NAMESPACE'
480 typ, dat = self._simple_command(name)
481 return self._untagged_response(typ, dat, name)
482
483
Tim Peters07e99cb2001-01-14 23:47:14 +0000484 def noop(self):
485 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000486
Tim Peters07e99cb2001-01-14 23:47:14 +0000487 (typ, data) = <instance>.noop()
488 """
489 if __debug__:
490 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000491 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000492 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000493
494
Tim Peters07e99cb2001-01-14 23:47:14 +0000495 def partial(self, message_num, message_part, start, length):
496 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000499
Tim Peters07e99cb2001-01-14 23:47:14 +0000500 'data' is tuple of message part envelope and data.
501 """
502 name = 'PARTIAL'
503 typ, dat = self._simple_command(name, message_num, message_part, start, length)
504 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
506
Tim Peters07e99cb2001-01-14 23:47:14 +0000507 def rename(self, oldmailbox, newmailbox):
508 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 (typ, data) = <instance>.rename(oldmailbox, newmailbox)
511 """
512 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 def search(self, charset, *criteria):
516 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000517
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 (typ, [data]) = <instance>.search(charset, criterium, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000519
Tim Peters07e99cb2001-01-14 23:47:14 +0000520 'data' is space separated list of matching message numbers.
521 """
522 name = 'SEARCH'
523 if charset:
Piers Lauder15e5d532001-07-20 10:52:06 +0000524 typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria)
525 else:
526 typ, dat = apply(self._simple_command, (name,) + criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000527 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000528
529
Tim Peters07e99cb2001-01-14 23:47:14 +0000530 def select(self, mailbox='INBOX', readonly=None):
531 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000532
Tim Peters07e99cb2001-01-14 23:47:14 +0000533 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000534
Tim Peters07e99cb2001-01-14 23:47:14 +0000535 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 'data' is count of messages in mailbox ('EXISTS' response).
538 """
539 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
540 self.untagged_responses = {} # Flush old responses.
541 self.is_readonly = readonly
542 if readonly:
543 name = 'EXAMINE'
544 else:
545 name = 'SELECT'
546 typ, dat = self._simple_command(name, mailbox)
547 if typ != 'OK':
548 self.state = 'AUTH' # Might have been 'SELECTED'
549 return typ, dat
550 self.state = 'SELECTED'
551 if self.untagged_responses.has_key('READ-ONLY') \
552 and not readonly:
553 if __debug__:
554 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000555 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000556 raise self.readonly('%s is not writable' % mailbox)
557 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000558
559
Piers Lauder15e5d532001-07-20 10:52:06 +0000560 def setacl(self, mailbox, who, what):
561 """Set a mailbox acl.
562
563 (typ, [data]) = <instance>.create(mailbox, who, what)
564 """
565 return self._simple_command('SETACL', mailbox, who, what)
566
567
568 def sort(self, sort_criteria, charset, *search_criteria):
569 """IMAP4rev1 extension SORT command.
570
571 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
572 """
573 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000574 #if not name in self.capabilities: # Let the server decide!
575 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000576 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000577 sort_criteria = '(%s)' % sort_criteria
Piers Lauder15e5d532001-07-20 10:52:06 +0000578 typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria)
579 return self._untagged_response(typ, dat, name)
580
581
Tim Peters07e99cb2001-01-14 23:47:14 +0000582 def status(self, mailbox, names):
583 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000584
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 (typ, [data]) = <instance>.status(mailbox, names)
586 """
587 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000588 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000589 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000590 typ, dat = self._simple_command(name, mailbox, names)
591 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000592
593
Tim Peters07e99cb2001-01-14 23:47:14 +0000594 def store(self, message_set, command, flags):
595 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000596
Tim Peters07e99cb2001-01-14 23:47:14 +0000597 (typ, [data]) = <instance>.store(message_set, command, flags)
598 """
599 if (flags[0],flags[-1]) != ('(',')'):
600 flags = '(%s)' % flags # Avoid quoting the flags
601 typ, dat = self._simple_command('STORE', message_set, command, flags)
602 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000603
604
Tim Peters07e99cb2001-01-14 23:47:14 +0000605 def subscribe(self, mailbox):
606 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000607
Tim Peters07e99cb2001-01-14 23:47:14 +0000608 (typ, [data]) = <instance>.subscribe(mailbox)
609 """
610 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000611
612
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 def uid(self, command, *args):
614 """Execute "command arg ..." with messages identified by UID,
615 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000616
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000618
Tim Peters07e99cb2001-01-14 23:47:14 +0000619 Returns response appropriate to 'command'.
620 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000621 command = command.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000622 if not Commands.has_key(command):
623 raise self.error("Unknown IMAP4 UID command: %s" % command)
624 if self.state not in Commands[command]:
625 raise self.error('command %s illegal in state %s'
626 % (command, self.state))
627 name = 'UID'
628 typ, dat = apply(self._simple_command, (name, command) + args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000629 if command in ('SEARCH', 'SORT'):
630 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000631 else:
632 name = 'FETCH'
633 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000634
635
Tim Peters07e99cb2001-01-14 23:47:14 +0000636 def unsubscribe(self, mailbox):
637 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 (typ, [data]) = <instance>.unsubscribe(mailbox)
640 """
641 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
643
Tim Peters07e99cb2001-01-14 23:47:14 +0000644 def xatom(self, name, *args):
645 """Allow simple extension commands
646 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000647
Piers Lauder15e5d532001-07-20 10:52:06 +0000648 Assumes command is legal in current state.
649
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000651
652 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000654 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000655 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000656 # raise self.error('unknown extension command: %s' % name)
657 if not Commands.has_key(name):
658 Commands[name] = (self.state,)
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 return apply(self._simple_command, (name,) + args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000660
661
662
Tim Peters07e99cb2001-01-14 23:47:14 +0000663 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000664
665
Tim Peters07e99cb2001-01-14 23:47:14 +0000666 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000667
Tim Peters07e99cb2001-01-14 23:47:14 +0000668 if dat is None: dat = ''
669 ur = self.untagged_responses
670 if __debug__:
671 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000672 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000673 (typ, len(ur.get(typ,'')), dat))
674 if ur.has_key(typ):
675 ur[typ].append(dat)
676 else:
677 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000678
679
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 def _check_bye(self):
681 bye = self.untagged_responses.get('BYE')
682 if bye:
683 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000684
685
Tim Peters07e99cb2001-01-14 23:47:14 +0000686 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000687
Tim Peters07e99cb2001-01-14 23:47:14 +0000688 if self.state not in Commands[name]:
689 self.literal = None
690 raise self.error(
691 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000692
Tim Peters07e99cb2001-01-14 23:47:14 +0000693 for typ in ('OK', 'NO', 'BAD'):
694 if self.untagged_responses.has_key(typ):
695 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000696
Tim Peters07e99cb2001-01-14 23:47:14 +0000697 if self.untagged_responses.has_key('READ-ONLY') \
698 and not self.is_readonly:
699 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000700
Tim Peters07e99cb2001-01-14 23:47:14 +0000701 tag = self._new_tag()
702 data = '%s %s' % (tag, name)
703 for arg in args:
704 if arg is None: continue
705 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000706
Tim Peters07e99cb2001-01-14 23:47:14 +0000707 literal = self.literal
708 if literal is not None:
709 self.literal = None
710 if type(literal) is type(self._command):
711 literator = literal
712 else:
713 literator = None
714 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000715
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 if __debug__:
717 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000718 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000720 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000721
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000723 self.send('%s%s' % (data, CRLF))
724 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000725 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000726
Tim Peters07e99cb2001-01-14 23:47:14 +0000727 if literal is None:
728 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 while 1:
731 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000732
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 while self._get_response():
734 if self.tagged_commands[tag]: # BAD/NO?
735 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000738
Tim Peters07e99cb2001-01-14 23:47:14 +0000739 if literator:
740 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000741
Tim Peters07e99cb2001-01-14 23:47:14 +0000742 if __debug__:
743 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000744 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000745
Tim Peters07e99cb2001-01-14 23:47:14 +0000746 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000747 self.send(literal)
748 self.send(CRLF)
749 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000751
Tim Peters07e99cb2001-01-14 23:47:14 +0000752 if not literator:
753 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000754
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000756
757
Tim Peters07e99cb2001-01-14 23:47:14 +0000758 def _command_complete(self, name, tag):
759 self._check_bye()
760 try:
761 typ, data = self._get_tagged_response(tag)
762 except self.abort, val:
763 raise self.abort('command: %s => %s' % (name, val))
764 except self.error, val:
765 raise self.error('command: %s => %s' % (name, val))
766 self._check_bye()
767 if typ == 'BAD':
768 raise self.error('%s command error: %s %s' % (name, typ, data))
769 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000770
771
Tim Peters07e99cb2001-01-14 23:47:14 +0000772 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000773
Tim Peters07e99cb2001-01-14 23:47:14 +0000774 # Read response and store.
775 #
776 # Returns None for continuation responses,
777 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000778
Tim Peters07e99cb2001-01-14 23:47:14 +0000779 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000780
Tim Peters07e99cb2001-01-14 23:47:14 +0000781 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000782
Tim Peters07e99cb2001-01-14 23:47:14 +0000783 if self._match(self.tagre, resp):
784 tag = self.mo.group('tag')
785 if not self.tagged_commands.has_key(tag):
786 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000787
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 typ = self.mo.group('type')
789 dat = self.mo.group('data')
790 self.tagged_commands[tag] = (typ, [dat])
791 else:
792 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000795
Tim Peters07e99cb2001-01-14 23:47:14 +0000796 if not self._match(Untagged_response, resp):
797 if self._match(Untagged_status, resp):
798 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000799
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 if self.mo is None:
801 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000802
Tim Peters07e99cb2001-01-14 23:47:14 +0000803 if self._match(Continuation, resp):
804 self.continuation_response = self.mo.group('data')
805 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000806
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000808
Tim Peters07e99cb2001-01-14 23:47:14 +0000809 typ = self.mo.group('type')
810 dat = self.mo.group('data')
811 if dat is None: dat = '' # Null untagged response
812 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000813
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000817
Tim Peters07e99cb2001-01-14 23:47:14 +0000818 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000820 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 if __debug__:
822 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000823 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000824 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000825
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000829
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000837
Tim Peters07e99cb2001-01-14 23:47:14 +0000838 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
839 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000840
Tim Peters07e99cb2001-01-14 23:47:14 +0000841 if __debug__:
842 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000843 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000844
Tim Peters07e99cb2001-01-14 23:47:14 +0000845 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000846
847
Tim Peters07e99cb2001-01-14 23:47:14 +0000848 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 while 1:
851 result = self.tagged_commands[tag]
852 if result is not None:
853 del self.tagged_commands[tag]
854 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000855
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 # Some have reported "unexpected response" exceptions.
857 # Note that ignoring them here causes loops.
858 # Instead, send me details of the unexpected response and
859 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 try:
862 self._get_response()
863 except self.abort, val:
864 if __debug__:
865 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000866 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000868
869
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Piers Lauder15e5d532001-07-20 10:52:06 +0000872 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 if not line:
874 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 line = line[:-2]
879 if __debug__:
880 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000881 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000883 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 # Run compiled regular expression match method on 's'.
890 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000891
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 self.mo = cre.match(s)
893 if __debug__:
894 if self.mo is not None and self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000895 self._mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000897
898
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 tag = '%s%s' % (self.tagpre, self.tagnum)
902 self.tagnum = self.tagnum + 1
903 self.tagged_commands[tag] = None
904 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000905
906
Tim Peters07e99cb2001-01-14 23:47:14 +0000907 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000908
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 # Must quote command args if non-alphanumeric chars present,
910 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000911
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 if type(arg) is not type(''):
913 return arg
914 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
915 return arg
916 if self.mustquote.search(arg) is None:
917 return arg
918 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000919
920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000922
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000923 arg = arg.replace('\\', '\\\\')
924 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000925
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000927
928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 return self._command_complete(name, apply(self._command, (name,) + args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000935
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 if typ == 'NO':
937 return typ, dat
938 if not self.untagged_responses.has_key(name):
939 return typ, [None]
940 data = self.untagged_responses[name]
941 if __debug__:
942 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000943 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 del self.untagged_responses[name]
945 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000946
947
Piers Lauderf2d7d152002-02-22 01:15:17 +0000948 if __debug__:
949
950 def _mesg(self, s, secs=None):
951 if secs is None:
952 secs = time.time()
953 tm = time.strftime('%M:%S', time.localtime(secs))
954 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
955 sys.stderr.flush()
956
957 def _dump_ur(self, dict):
958 # Dump untagged responses (in `dict').
959 l = dict.items()
960 if not l: return
961 t = '\n\t\t'
962 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
963 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
964
965 def _log(self, line):
966 # Keep log of last `_cmd_log_len' interactions for debugging.
967 self._cmd_log[self._cmd_log_idx] = (line, time.time())
968 self._cmd_log_idx += 1
969 if self._cmd_log_idx >= self._cmd_log_len:
970 self._cmd_log_idx = 0
971
972 def print_log(self):
973 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
974 i, n = self._cmd_log_idx, self._cmd_log_len
975 while n:
976 try:
977 apply(self._mesg, self._cmd_log[i])
978 except:
979 pass
980 i += 1
981 if i >= self._cmd_log_len:
982 i = 0
983 n -= 1
984
985
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Piers Laudera4f83132002-03-08 01:53:24 +0000987class IMAP4_SSL(IMAP4):
988
989 """IMAP4 client class over SSL connection
990
Piers Lauder95f84952002-03-08 09:05:12 +0000991 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +0000992
993 host - host's name (default: localhost);
994 port - port number (default: standard IMAP4 SSL port).
995 keyfile - PEM formatted file that contains your private key (default: None);
996 certfile - PEM formatted certificate chain file (default: None);
997
998 for more documentation see the docstring of the parent class IMAP4.
999 """
1000
1001
1002 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1003 self.keyfile = keyfile
1004 self.certfile = certfile
1005 IMAP4.__init__(self, host, port)
1006
1007
1008 def open(self, host, port):
1009 """Setup connection to remote server on "host:port".
1010 This connection will be used by the routines:
1011 read, readline, send, shutdown.
1012 """
1013 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1014 self.sock.connect((self.host, self.port))
1015 self.sslobj = socket.ssl(self.sock,self.keyfile, self.certfile)
1016
1017
1018 def read(self, size):
1019 """Read 'size' bytes from remote."""
1020 return self.sslobj.read(size)
1021
1022
1023 def readline(self):
1024 """Read line from remote."""
1025 line = ""
1026 while 1:
1027 char = self.sslobj.read(1)
1028 line += char
1029 if char == "\n": return line
1030
1031
1032 def send(self, data):
1033 """Send data to remote."""
1034 self.sslobj.write(data)
1035
1036
1037 def shutdown(self):
1038 """Close I/O established in "open"."""
1039 self.sock.close()
1040
1041
1042 def socket(self):
1043 """Return socket instance used to connect to IMAP4 server.
1044
1045 socket = <instance>.socket()
1046 """
1047 return self.sock
1048
1049
1050 def ssl(self):
1051 """Return SSLObject instance used to communicate with the IMAP4 server.
1052
1053 ssl = <instance>.socket.ssl()
1054 """
1055 return self.sslobj
1056
1057
1058
Guido van Rossumeda960a1998-06-18 14:24:28 +00001059class _Authenticator:
1060
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 """Private class to provide en/decoding
1062 for base64-based authentication conversation.
1063 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001064
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 def __init__(self, mechinst):
1066 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 def process(self, data):
1069 ret = self.mech(self.decode(data))
1070 if ret is None:
1071 return '*' # Abort conversation
1072 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001073
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 def encode(self, inp):
1075 #
1076 # Invoke binascii.b2a_base64 iteratively with
1077 # short even length buffers, strip the trailing
1078 # line feed from the result and append. "Even"
1079 # means a number that factors to both 6 and 8,
1080 # so when it gets to the end of the 8-bit input
1081 # there's no partial 6-bit output.
1082 #
1083 oup = ''
1084 while inp:
1085 if len(inp) > 48:
1086 t = inp[:48]
1087 inp = inp[48:]
1088 else:
1089 t = inp
1090 inp = ''
1091 e = binascii.b2a_base64(t)
1092 if e:
1093 oup = oup + e[:-1]
1094 return oup
1095
1096 def decode(self, inp):
1097 if not inp:
1098 return ''
1099 return binascii.a2b_base64(inp)
1100
Guido van Rossumeda960a1998-06-18 14:24:28 +00001101
1102
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001103Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001105
1106def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001107 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001108
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 Returns Python time module tuple.
1110 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001111
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 mo = InternalDate.match(resp)
1113 if not mo:
1114 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 mon = Mon2num[mo.group('mon')]
1117 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001118
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001119 day = int(mo.group('day'))
1120 year = int(mo.group('year'))
1121 hour = int(mo.group('hour'))
1122 min = int(mo.group('min'))
1123 sec = int(mo.group('sec'))
1124 zoneh = int(mo.group('zoneh'))
1125 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001126
Tim Peters07e99cb2001-01-14 23:47:14 +00001127 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001128
Tim Peters07e99cb2001-01-14 23:47:14 +00001129 zone = (zoneh*60 + zonem)*60
1130 if zonen == '-':
1131 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001132
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001134
Tim Peters07e99cb2001-01-14 23:47:14 +00001135 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001136
Tim Peters07e99cb2001-01-14 23:47:14 +00001137 # Following is necessary because the time module has no 'mkgmtime'.
1138 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001139
Tim Peters07e99cb2001-01-14 23:47:14 +00001140 lt = time.localtime(utc)
1141 if time.daylight and lt[-1]:
1142 zone = zone + time.altzone
1143 else:
1144 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001145
Tim Peters07e99cb2001-01-14 23:47:14 +00001146 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001147
1148
1149
1150def Int2AP(num):
1151
Tim Peters07e99cb2001-01-14 23:47:14 +00001152 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001153
Tim Peters07e99cb2001-01-14 23:47:14 +00001154 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1155 num = int(abs(num))
1156 while num:
1157 num, mod = divmod(num, 16)
1158 val = AP[mod] + val
1159 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001160
1161
1162
1163def ParseFlags(resp):
1164
Tim Peters07e99cb2001-01-14 23:47:14 +00001165 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001166
Tim Peters07e99cb2001-01-14 23:47:14 +00001167 mo = Flags.match(resp)
1168 if not mo:
1169 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001170
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001171 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001172
1173
1174def Time2Internaldate(date_time):
1175
Tim Peters07e99cb2001-01-14 23:47:14 +00001176 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001177
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1179 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001180
Fred Drakedb519202002-01-05 17:17:09 +00001181 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001182 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001183 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001184 tt = date_time
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001185 elif isinstance(date_time, str):
Tim Peters07e99cb2001-01-14 23:47:14 +00001186 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001187 else:
1188 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001189
Tim Peters07e99cb2001-01-14 23:47:14 +00001190 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1191 if dt[0] == '0':
1192 dt = ' ' + dt[1:]
1193 if time.daylight and tt[-1]:
1194 zone = -time.altzone
1195 else:
1196 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001197 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001198
1199
1200
Guido van Rossum8c062211999-12-13 23:27:45 +00001201if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001202
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001203 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001204
Tim Peters07e99cb2001-01-14 23:47:14 +00001205 try:
1206 optlist, args = getopt.getopt(sys.argv[1:], 'd:')
1207 except getopt.error, val:
1208 pass
Guido van Rossum66d45132000-03-28 20:20:53 +00001209
Tim Peters07e99cb2001-01-14 23:47:14 +00001210 for opt,val in optlist:
1211 if opt == '-d':
1212 Debug = int(val)
Guido van Rossum66d45132000-03-28 20:20:53 +00001213
Tim Peters07e99cb2001-01-14 23:47:14 +00001214 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001215
Tim Peters07e99cb2001-01-14 23:47:14 +00001216 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001217
Tim Peters07e99cb2001-01-14 23:47:14 +00001218 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001219 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001220
Piers Laudere02f9042001-08-05 10:43:03 +00001221 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 +00001222 test_seq1 = (
1223 ('login', (USER, PASSWD)),
1224 ('create', ('/tmp/xxx 1',)),
1225 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1226 ('CREATE', ('/tmp/yyz 2',)),
1227 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1228 ('list', ('/tmp', 'yy*')),
1229 ('select', ('/tmp/yyz 2',)),
1230 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001231 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001232 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001233 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001234 ('expunge', ()),
1235 ('recent', ()),
1236 ('close', ()),
1237 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001238
Tim Peters07e99cb2001-01-14 23:47:14 +00001239 test_seq2 = (
1240 ('select', ()),
1241 ('response',('UIDVALIDITY',)),
1242 ('uid', ('SEARCH', 'ALL')),
1243 ('response', ('EXISTS',)),
1244 ('append', (None, None, None, test_mesg)),
1245 ('recent', ()),
1246 ('logout', ()),
1247 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001248
Tim Peters07e99cb2001-01-14 23:47:14 +00001249 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001250 M._mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001251 typ, dat = apply(getattr(M, cmd), args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001252 M._mesg('%s => %s %s' % (cmd, typ, dat))
Tim Peters07e99cb2001-01-14 23:47:14 +00001253 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001254
Tim Peters07e99cb2001-01-14 23:47:14 +00001255 try:
1256 M = IMAP4(host)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001257 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1258 M._mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001259
Tim Peters07e99cb2001-01-14 23:47:14 +00001260 for cmd,args in test_seq1:
1261 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001262
Tim Peters07e99cb2001-01-14 23:47:14 +00001263 for ml in run('list', ('/tmp/', 'yy%')):
1264 mo = re.match(r'.*"([^"]+)"$', ml)
1265 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001266 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001267 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001268
Tim Peters07e99cb2001-01-14 23:47:14 +00001269 for cmd,args in test_seq2:
1270 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001271
Tim Peters07e99cb2001-01-14 23:47:14 +00001272 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1273 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001274
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001275 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 if not uid: continue
1277 run('uid', ('FETCH', '%s' % uid[-1],
1278 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001279
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001281
Tim Peters07e99cb2001-01-14 23:47:14 +00001282 except:
1283 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001284
Tim Peters07e99cb2001-01-14 23:47:14 +00001285 if not Debug:
1286 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001287If you would like to see debugging output,
1288try: %s -d5
1289''' % sys.argv[0]
1290
Tim Peters07e99cb2001-01-14 23:47:14 +00001291 raise