blob: 455ba9c0bf99aa944668b9e3af0490869a5e1df3 [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):
Tim Peters07e99cb2001-01-14 23:47:14 +0000136 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()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000169 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000171 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 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)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000178 if not cap in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 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.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000197 if attr in Commands:
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
Piers Lauderf97b2d72002-06-05 22:31:57 +0000206 def open(self, host = '', port = IMAP4_PORT):
207 """Setup connection to remote server on "host:port"
208 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000209 This connection will be used by the routines:
210 read, readline, send, shutdown.
211 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000212 self.host = host
213 self.port = port
Tim Peters07e99cb2001-01-14 23:47:14 +0000214 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +0000215 self.sock.connect((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000216 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000217
218
Piers Lauder15e5d532001-07-20 10:52:06 +0000219 def read(self, size):
220 """Read 'size' bytes from remote."""
221 return self.file.read(size)
222
223
224 def readline(self):
225 """Read line from remote."""
226 return self.file.readline()
227
228
229 def send(self, data):
230 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000231 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000232
Piers Lauderf2d7d152002-02-22 01:15:17 +0000233
Piers Lauder15e5d532001-07-20 10:52:06 +0000234 def shutdown(self):
235 """Close I/O established in "open"."""
236 self.file.close()
237 self.sock.close()
238
239
240 def socket(self):
241 """Return socket instance used to connect to IMAP4 server.
242
243 socket = <instance>.socket()
244 """
245 return self.sock
246
247
248
249 # Utility methods
250
251
Tim Peters07e99cb2001-01-14 23:47:14 +0000252 def recent(self):
253 """Return most recent 'RECENT' responses if any exist,
254 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000255
Tim Peters07e99cb2001-01-14 23:47:14 +0000256 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000257
Tim Peters07e99cb2001-01-14 23:47:14 +0000258 'data' is None if no new messages,
259 else list of RECENT responses, most recent last.
260 """
261 name = 'RECENT'
262 typ, dat = self._untagged_response('OK', [None], name)
263 if dat[-1]:
264 return typ, dat
265 typ, dat = self.noop() # Prod server for response
266 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000267
268
Tim Peters07e99cb2001-01-14 23:47:14 +0000269 def response(self, code):
270 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000271
Tim Peters07e99cb2001-01-14 23:47:14 +0000272 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000273
Tim Peters07e99cb2001-01-14 23:47:14 +0000274 (code, [data]) = <instance>.response(code)
275 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000276 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000277
278
Guido van Rossum26367a01998-09-28 15:34:46 +0000279
Tim Peters07e99cb2001-01-14 23:47:14 +0000280 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000281
282
Tim Peters07e99cb2001-01-14 23:47:14 +0000283 def append(self, mailbox, flags, date_time, message):
284 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000285
Tim Peters07e99cb2001-01-14 23:47:14 +0000286 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000287
Tim Peters07e99cb2001-01-14 23:47:14 +0000288 All args except `message' can be None.
289 """
290 name = 'APPEND'
291 if not mailbox:
292 mailbox = 'INBOX'
293 if flags:
294 if (flags[0],flags[-1]) != ('(',')'):
295 flags = '(%s)' % flags
296 else:
297 flags = None
298 if date_time:
299 date_time = Time2Internaldate(date_time)
300 else:
301 date_time = None
302 self.literal = message
303 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000304
305
Tim Peters07e99cb2001-01-14 23:47:14 +0000306 def authenticate(self, mechanism, authobject):
307 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 'mechanism' specifies which authentication mechanism is to
310 be used - it must appear in <instance>.capabilities in the
311 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000312
Tim Peters07e99cb2001-01-14 23:47:14 +0000313 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000314
Tim Peters07e99cb2001-01-14 23:47:14 +0000315 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000316
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 It will be called to process server continuation responses.
318 It should return data that will be encoded and sent to server.
319 It should return None if the client abort response '*' should
320 be sent instead.
321 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000322 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000323 cap = 'AUTH=%s' % mech
324 if not cap in self.capabilities:
325 raise self.error("Server doesn't allow %s authentication." % mech)
326 self.literal = _Authenticator(authobject).process
327 typ, dat = self._simple_command('AUTHENTICATE', mech)
328 if typ != 'OK':
329 raise self.error(dat[-1])
330 self.state = 'AUTH'
331 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000332
333
Tim Peters07e99cb2001-01-14 23:47:14 +0000334 def check(self):
335 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000336
Tim Peters07e99cb2001-01-14 23:47:14 +0000337 (typ, [data]) = <instance>.check()
338 """
339 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000340
341
Tim Peters07e99cb2001-01-14 23:47:14 +0000342 def close(self):
343 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 Deleted messages are removed from writable mailbox.
346 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000347
Tim Peters07e99cb2001-01-14 23:47:14 +0000348 (typ, [data]) = <instance>.close()
349 """
350 try:
351 typ, dat = self._simple_command('CLOSE')
352 finally:
353 self.state = 'AUTH'
354 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000355
356
Tim Peters07e99cb2001-01-14 23:47:14 +0000357 def copy(self, message_set, new_mailbox):
358 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000359
Tim Peters07e99cb2001-01-14 23:47:14 +0000360 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
361 """
362 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000363
364
Tim Peters07e99cb2001-01-14 23:47:14 +0000365 def create(self, mailbox):
366 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000367
Tim Peters07e99cb2001-01-14 23:47:14 +0000368 (typ, [data]) = <instance>.create(mailbox)
369 """
370 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000371
372
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 def delete(self, mailbox):
374 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000375
Tim Peters07e99cb2001-01-14 23:47:14 +0000376 (typ, [data]) = <instance>.delete(mailbox)
377 """
378 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000379
380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 def expunge(self):
382 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
Tim Peters07e99cb2001-01-14 23:47:14 +0000386 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 'data' is list of 'EXPUNGE'd message numbers in order received.
389 """
390 name = 'EXPUNGE'
391 typ, dat = self._simple_command(name)
392 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 def fetch(self, message_set, message_parts):
396 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
Tim Peters07e99cb2001-01-14 23:47:14 +0000400 'message_parts' should be a string of selected parts
401 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000402
Tim Peters07e99cb2001-01-14 23:47:14 +0000403 'data' are tuples of message part envelope and data.
404 """
405 name = 'FETCH'
406 typ, dat = self._simple_command(name, message_set, message_parts)
407 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000408
409
Piers Lauder15e5d532001-07-20 10:52:06 +0000410 def getacl(self, mailbox):
411 """Get the ACLs for a mailbox.
412
413 (typ, [data]) = <instance>.getacl(mailbox)
414 """
415 typ, dat = self._simple_command('GETACL', mailbox)
416 return self._untagged_response(typ, dat, 'ACL')
417
418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 def list(self, directory='""', pattern='*'):
420 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000421
Tim Peters07e99cb2001-01-14 23:47:14 +0000422 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000423
Tim Peters07e99cb2001-01-14 23:47:14 +0000424 'data' is list of LIST responses.
425 """
426 name = 'LIST'
427 typ, dat = self._simple_command(name, directory, pattern)
428 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 def login(self, user, password):
432 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000433
Tim Peters07e99cb2001-01-14 23:47:14 +0000434 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000435
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 NB: 'password' will be quoted.
437 """
438 #if not 'AUTH=LOGIN' in self.capabilities:
439 # raise self.error("Server doesn't allow LOGIN authentication." % mech)
440 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
441 if typ != 'OK':
442 raise self.error(dat[-1])
443 self.state = 'AUTH'
444 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000445
446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 def logout(self):
448 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 Returns server 'BYE' response.
453 """
454 self.state = 'LOGOUT'
455 try: typ, dat = self._simple_command('LOGOUT')
456 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000457 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000458 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000459 return 'BYE', self.untagged_responses['BYE']
460 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 def lsub(self, directory='""', pattern='*'):
464 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
Tim Peters07e99cb2001-01-14 23:47:14 +0000466 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000467
Tim Peters07e99cb2001-01-14 23:47:14 +0000468 'data' are tuples of message part envelope and data.
469 """
470 name = 'LSUB'
471 typ, dat = self._simple_command(name, directory, pattern)
472 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000473
474
Piers Lauder15e5d532001-07-20 10:52:06 +0000475 def namespace(self):
476 """ Returns IMAP namespaces ala rfc2342
477
478 (typ, [data, ...]) = <instance>.namespace()
479 """
480 name = 'NAMESPACE'
481 typ, dat = self._simple_command(name)
482 return self._untagged_response(typ, dat, name)
483
484
Tim Peters07e99cb2001-01-14 23:47:14 +0000485 def noop(self):
486 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000487
Tim Peters07e99cb2001-01-14 23:47:14 +0000488 (typ, data) = <instance>.noop()
489 """
490 if __debug__:
491 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000492 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000493 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000494
495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 def partial(self, message_num, message_part, start, length):
497 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000498
Tim Peters07e99cb2001-01-14 23:47:14 +0000499 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000500
Tim Peters07e99cb2001-01-14 23:47:14 +0000501 'data' is tuple of message part envelope and data.
502 """
503 name = 'PARTIAL'
504 typ, dat = self._simple_command(name, message_num, message_part, start, length)
505 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000506
507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 def rename(self, oldmailbox, newmailbox):
509 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000510
Tim Peters07e99cb2001-01-14 23:47:14 +0000511 (typ, data) = <instance>.rename(oldmailbox, newmailbox)
512 """
513 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000514
515
Tim Peters07e99cb2001-01-14 23:47:14 +0000516 def search(self, charset, *criteria):
517 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000518
Tim Peters07e99cb2001-01-14 23:47:14 +0000519 (typ, [data]) = <instance>.search(charset, criterium, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000520
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 'data' is space separated list of matching message numbers.
522 """
523 name = 'SEARCH'
524 if charset:
Piers Lauder15e5d532001-07-20 10:52:06 +0000525 typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria)
526 else:
527 typ, dat = apply(self._simple_command, (name,) + criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000528 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000529
530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 def select(self, mailbox='INBOX', readonly=None):
532 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000533
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000535
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000537
Tim Peters07e99cb2001-01-14 23:47:14 +0000538 'data' is count of messages in mailbox ('EXISTS' response).
539 """
540 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
541 self.untagged_responses = {} # Flush old responses.
542 self.is_readonly = readonly
Raymond Hettinger936654b2002-06-01 03:06:31 +0000543 if readonly is not None:
Tim Peters07e99cb2001-01-14 23:47:14 +0000544 name = 'EXAMINE'
545 else:
546 name = 'SELECT'
547 typ, dat = self._simple_command(name, mailbox)
548 if typ != 'OK':
549 self.state = 'AUTH' # Might have been 'SELECTED'
550 return typ, dat
551 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000552 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000553 and not readonly:
554 if __debug__:
555 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000556 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000557 raise self.readonly('%s is not writable' % mailbox)
558 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000559
560
Piers Lauder15e5d532001-07-20 10:52:06 +0000561 def setacl(self, mailbox, who, what):
562 """Set a mailbox acl.
563
564 (typ, [data]) = <instance>.create(mailbox, who, what)
565 """
566 return self._simple_command('SETACL', mailbox, who, what)
567
568
569 def sort(self, sort_criteria, charset, *search_criteria):
570 """IMAP4rev1 extension SORT command.
571
572 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
573 """
574 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000575 #if not name in self.capabilities: # Let the server decide!
576 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000577 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000578 sort_criteria = '(%s)' % sort_criteria
Piers Lauder15e5d532001-07-20 10:52:06 +0000579 typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria)
580 return self._untagged_response(typ, dat, name)
581
582
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 def status(self, mailbox, names):
584 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000585
Tim Peters07e99cb2001-01-14 23:47:14 +0000586 (typ, [data]) = <instance>.status(mailbox, names)
587 """
588 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000589 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000590 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 typ, dat = self._simple_command(name, mailbox, names)
592 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000593
594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 def store(self, message_set, command, flags):
596 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000597
Tim Peters07e99cb2001-01-14 23:47:14 +0000598 (typ, [data]) = <instance>.store(message_set, command, flags)
599 """
600 if (flags[0],flags[-1]) != ('(',')'):
601 flags = '(%s)' % flags # Avoid quoting the flags
602 typ, dat = self._simple_command('STORE', message_set, command, flags)
603 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000604
605
Tim Peters07e99cb2001-01-14 23:47:14 +0000606 def subscribe(self, mailbox):
607 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000608
Tim Peters07e99cb2001-01-14 23:47:14 +0000609 (typ, [data]) = <instance>.subscribe(mailbox)
610 """
611 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000612
613
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 def uid(self, command, *args):
615 """Execute "command arg ..." with messages identified by UID,
616 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000617
Tim Peters07e99cb2001-01-14 23:47:14 +0000618 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000619
Tim Peters07e99cb2001-01-14 23:47:14 +0000620 Returns response appropriate to 'command'.
621 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000622 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000623 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000624 raise self.error("Unknown IMAP4 UID command: %s" % command)
625 if self.state not in Commands[command]:
626 raise self.error('command %s illegal in state %s'
627 % (command, self.state))
628 name = 'UID'
629 typ, dat = apply(self._simple_command, (name, command) + args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000630 if command in ('SEARCH', 'SORT'):
631 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000632 else:
633 name = 'FETCH'
634 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
636
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 def unsubscribe(self, mailbox):
638 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000639
Tim Peters07e99cb2001-01-14 23:47:14 +0000640 (typ, [data]) = <instance>.unsubscribe(mailbox)
641 """
642 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000643
644
Tim Peters07e99cb2001-01-14 23:47:14 +0000645 def xatom(self, name, *args):
646 """Allow simple extension commands
647 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000648
Piers Lauder15e5d532001-07-20 10:52:06 +0000649 Assumes command is legal in current state.
650
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000652
653 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000655 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000656 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000657 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000658 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000659 Commands[name] = (self.state,)
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 return apply(self._simple_command, (name,) + args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000661
662
663
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000665
666
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000668
Tim Peters07e99cb2001-01-14 23:47:14 +0000669 if dat is None: dat = ''
670 ur = self.untagged_responses
671 if __debug__:
672 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000673 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000674 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000675 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 ur[typ].append(dat)
677 else:
678 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000679
680
Tim Peters07e99cb2001-01-14 23:47:14 +0000681 def _check_bye(self):
682 bye = self.untagged_responses.get('BYE')
683 if bye:
684 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000685
686
Tim Peters07e99cb2001-01-14 23:47:14 +0000687 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000688
Tim Peters07e99cb2001-01-14 23:47:14 +0000689 if self.state not in Commands[name]:
690 self.literal = None
691 raise self.error(
692 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000693
Tim Peters07e99cb2001-01-14 23:47:14 +0000694 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000695 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000696 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000697
Raymond Hettinger54f02222002-06-01 14:18:47 +0000698 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000699 and not self.is_readonly:
700 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000701
Tim Peters07e99cb2001-01-14 23:47:14 +0000702 tag = self._new_tag()
703 data = '%s %s' % (tag, name)
704 for arg in args:
705 if arg is None: continue
706 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000707
Tim Peters07e99cb2001-01-14 23:47:14 +0000708 literal = self.literal
709 if literal is not None:
710 self.literal = None
711 if type(literal) is type(self._command):
712 literator = literal
713 else:
714 literator = None
715 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000716
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 if __debug__:
718 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000719 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000721 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000722
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000724 self.send('%s%s' % (data, CRLF))
725 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
Tim Peters07e99cb2001-01-14 23:47:14 +0000728 if literal is None:
729 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000730
Tim Peters07e99cb2001-01-14 23:47:14 +0000731 while 1:
732 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000733
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 while self._get_response():
735 if self.tagged_commands[tag]: # BAD/NO?
736 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000737
Tim Peters07e99cb2001-01-14 23:47:14 +0000738 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000739
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 if literator:
741 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000742
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 if __debug__:
744 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000745 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000746
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000748 self.send(literal)
749 self.send(CRLF)
750 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000751 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000752
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 if not literator:
754 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000757
758
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 def _command_complete(self, name, tag):
760 self._check_bye()
761 try:
762 typ, data = self._get_tagged_response(tag)
763 except self.abort, val:
764 raise self.abort('command: %s => %s' % (name, val))
765 except self.error, val:
766 raise self.error('command: %s => %s' % (name, val))
767 self._check_bye()
768 if typ == 'BAD':
769 raise self.error('%s command error: %s %s' % (name, typ, data))
770 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000771
772
Tim Peters07e99cb2001-01-14 23:47:14 +0000773 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000774
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 # Read response and store.
776 #
777 # Returns None for continuation responses,
778 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000779
Tim Peters07e99cb2001-01-14 23:47:14 +0000780 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000781
Tim Peters07e99cb2001-01-14 23:47:14 +0000782 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000783
Tim Peters07e99cb2001-01-14 23:47:14 +0000784 if self._match(self.tagre, resp):
785 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000786 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000787 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000788
Tim Peters07e99cb2001-01-14 23:47:14 +0000789 typ = self.mo.group('type')
790 dat = self.mo.group('data')
791 self.tagged_commands[tag] = (typ, [dat])
792 else:
793 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000794
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000796
Tim Peters07e99cb2001-01-14 23:47:14 +0000797 if not self._match(Untagged_response, resp):
798 if self._match(Untagged_status, resp):
799 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000800
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 if self.mo is None:
802 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000803
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 if self._match(Continuation, resp):
805 self.continuation_response = self.mo.group('data')
806 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000807
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000809
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 typ = self.mo.group('type')
811 dat = self.mo.group('data')
812 if dat is None: dat = '' # Null untagged response
813 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000814
Tim Peters07e99cb2001-01-14 23:47:14 +0000815 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000820
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000821 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 if __debug__:
823 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000824 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000825 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000828
Tim Peters07e99cb2001-01-14 23:47:14 +0000829 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000830
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000836
Tim Peters07e99cb2001-01-14 23:47:14 +0000837 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
840 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 if __debug__:
843 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000844 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000847
848
Tim Peters07e99cb2001-01-14 23:47:14 +0000849 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000850
Tim Peters07e99cb2001-01-14 23:47:14 +0000851 while 1:
852 result = self.tagged_commands[tag]
853 if result is not None:
854 del self.tagged_commands[tag]
855 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000856
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 # Some have reported "unexpected response" exceptions.
858 # Note that ignoring them here causes loops.
859 # Instead, send me details of the unexpected response and
860 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000861
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 try:
863 self._get_response()
864 except self.abort, val:
865 if __debug__:
866 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000867 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000872
Piers Lauder15e5d532001-07-20 10:52:06 +0000873 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 if not line:
875 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 line = line[:-2]
880 if __debug__:
881 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000882 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000884 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
887
Tim Peters07e99cb2001-01-14 23:47:14 +0000888 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000889
Tim Peters07e99cb2001-01-14 23:47:14 +0000890 # Run compiled regular expression match method on 's'.
891 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 self.mo = cre.match(s)
894 if __debug__:
895 if self.mo is not None and self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000896 self._mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000898
899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
Tim Peters07e99cb2001-01-14 23:47:14 +0000902 tag = '%s%s' % (self.tagpre, self.tagnum)
903 self.tagnum = self.tagnum + 1
904 self.tagged_commands[tag] = None
905 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000906
907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 # Must quote command args if non-alphanumeric chars present,
911 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000912
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 if type(arg) is not type(''):
914 return arg
915 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
916 return arg
917 if self.mustquote.search(arg) is None:
918 return arg
919 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000920
921
Tim Peters07e99cb2001-01-14 23:47:14 +0000922 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000923
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000924 arg = arg.replace('\\', '\\\\')
925 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000928
929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 return self._command_complete(name, apply(self._command, (name,) + args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 if typ == 'NO':
938 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +0000939 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 return typ, [None]
941 data = self.untagged_responses[name]
942 if __debug__:
943 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000944 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 del self.untagged_responses[name]
946 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000947
948
Piers Lauderf2d7d152002-02-22 01:15:17 +0000949 if __debug__:
950
951 def _mesg(self, s, secs=None):
952 if secs is None:
953 secs = time.time()
954 tm = time.strftime('%M:%S', time.localtime(secs))
955 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
956 sys.stderr.flush()
957
958 def _dump_ur(self, dict):
959 # Dump untagged responses (in `dict').
960 l = dict.items()
961 if not l: return
962 t = '\n\t\t'
963 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
964 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
965
966 def _log(self, line):
967 # Keep log of last `_cmd_log_len' interactions for debugging.
968 self._cmd_log[self._cmd_log_idx] = (line, time.time())
969 self._cmd_log_idx += 1
970 if self._cmd_log_idx >= self._cmd_log_len:
971 self._cmd_log_idx = 0
972
973 def print_log(self):
974 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
975 i, n = self._cmd_log_idx, self._cmd_log_len
976 while n:
977 try:
978 apply(self._mesg, self._cmd_log[i])
979 except:
980 pass
981 i += 1
982 if i >= self._cmd_log_len:
983 i = 0
984 n -= 1
985
986
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000987
Piers Laudera4f83132002-03-08 01:53:24 +0000988class IMAP4_SSL(IMAP4):
989
990 """IMAP4 client class over SSL connection
991
Piers Lauder95f84952002-03-08 09:05:12 +0000992 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +0000993
994 host - host's name (default: localhost);
995 port - port number (default: standard IMAP4 SSL port).
996 keyfile - PEM formatted file that contains your private key (default: None);
997 certfile - PEM formatted certificate chain file (default: None);
998
999 for more documentation see the docstring of the parent class IMAP4.
1000 """
1001
1002
1003 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1004 self.keyfile = keyfile
1005 self.certfile = certfile
1006 IMAP4.__init__(self, host, port)
1007
1008
Piers Lauderf97b2d72002-06-05 22:31:57 +00001009 def open(self, host = '', port = IMAP4_SSL_PORT):
Piers Laudera4f83132002-03-08 01:53:24 +00001010 """Setup connection to remote server on "host:port".
Piers Lauderf97b2d72002-06-05 22:31:57 +00001011 (default: localhost:standard IMAP4 SSL port).
Piers Laudera4f83132002-03-08 01:53:24 +00001012 This connection will be used by the routines:
1013 read, readline, send, shutdown.
1014 """
Piers Lauderf97b2d72002-06-05 22:31:57 +00001015 self.host = host
1016 self.port = port
Piers Laudera4f83132002-03-08 01:53:24 +00001017 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +00001018 self.sock.connect((host, port))
1019 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001020
1021
1022 def read(self, size):
1023 """Read 'size' bytes from remote."""
1024 return self.sslobj.read(size)
1025
1026
1027 def readline(self):
1028 """Read line from remote."""
1029 line = ""
1030 while 1:
1031 char = self.sslobj.read(1)
1032 line += char
1033 if char == "\n": return line
1034
1035
1036 def send(self, data):
1037 """Send data to remote."""
1038 self.sslobj.write(data)
1039
1040
1041 def shutdown(self):
1042 """Close I/O established in "open"."""
1043 self.sock.close()
1044
1045
1046 def socket(self):
1047 """Return socket instance used to connect to IMAP4 server.
1048
1049 socket = <instance>.socket()
1050 """
1051 return self.sock
1052
1053
1054 def ssl(self):
1055 """Return SSLObject instance used to communicate with the IMAP4 server.
1056
1057 ssl = <instance>.socket.ssl()
1058 """
1059 return self.sslobj
1060
1061
1062
Guido van Rossumeda960a1998-06-18 14:24:28 +00001063class _Authenticator:
1064
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 """Private class to provide en/decoding
1066 for base64-based authentication conversation.
1067 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001068
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 def __init__(self, mechinst):
1070 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001071
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 def process(self, data):
1073 ret = self.mech(self.decode(data))
1074 if ret is None:
1075 return '*' # Abort conversation
1076 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001077
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 def encode(self, inp):
1079 #
1080 # Invoke binascii.b2a_base64 iteratively with
1081 # short even length buffers, strip the trailing
1082 # line feed from the result and append. "Even"
1083 # means a number that factors to both 6 and 8,
1084 # so when it gets to the end of the 8-bit input
1085 # there's no partial 6-bit output.
1086 #
1087 oup = ''
1088 while inp:
1089 if len(inp) > 48:
1090 t = inp[:48]
1091 inp = inp[48:]
1092 else:
1093 t = inp
1094 inp = ''
1095 e = binascii.b2a_base64(t)
1096 if e:
1097 oup = oup + e[:-1]
1098 return oup
1099
1100 def decode(self, inp):
1101 if not inp:
1102 return ''
1103 return binascii.a2b_base64(inp)
1104
Guido van Rossumeda960a1998-06-18 14:24:28 +00001105
1106
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001107Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001108 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001109
1110def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001111 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001112
Tim Peters07e99cb2001-01-14 23:47:14 +00001113 Returns Python time module tuple.
1114 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 mo = InternalDate.match(resp)
1117 if not mo:
1118 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001119
Tim Peters07e99cb2001-01-14 23:47:14 +00001120 mon = Mon2num[mo.group('mon')]
1121 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001122
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001123 day = int(mo.group('day'))
1124 year = int(mo.group('year'))
1125 hour = int(mo.group('hour'))
1126 min = int(mo.group('min'))
1127 sec = int(mo.group('sec'))
1128 zoneh = int(mo.group('zoneh'))
1129 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001130
Tim Peters07e99cb2001-01-14 23:47:14 +00001131 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001132
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 zone = (zoneh*60 + zonem)*60
1134 if zonen == '-':
1135 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001136
Tim Peters07e99cb2001-01-14 23:47:14 +00001137 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001138
Tim Peters07e99cb2001-01-14 23:47:14 +00001139 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001140
Tim Peters07e99cb2001-01-14 23:47:14 +00001141 # Following is necessary because the time module has no 'mkgmtime'.
1142 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001143
Tim Peters07e99cb2001-01-14 23:47:14 +00001144 lt = time.localtime(utc)
1145 if time.daylight and lt[-1]:
1146 zone = zone + time.altzone
1147 else:
1148 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001149
Tim Peters07e99cb2001-01-14 23:47:14 +00001150 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001151
1152
1153
1154def Int2AP(num):
1155
Tim Peters07e99cb2001-01-14 23:47:14 +00001156 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001157
Tim Peters07e99cb2001-01-14 23:47:14 +00001158 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1159 num = int(abs(num))
1160 while num:
1161 num, mod = divmod(num, 16)
1162 val = AP[mod] + val
1163 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001164
1165
1166
1167def ParseFlags(resp):
1168
Tim Peters07e99cb2001-01-14 23:47:14 +00001169 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001170
Tim Peters07e99cb2001-01-14 23:47:14 +00001171 mo = Flags.match(resp)
1172 if not mo:
1173 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001174
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001175 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001176
1177
1178def Time2Internaldate(date_time):
1179
Tim Peters07e99cb2001-01-14 23:47:14 +00001180 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001181
Tim Peters07e99cb2001-01-14 23:47:14 +00001182 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1183 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001184
Fred Drakedb519202002-01-05 17:17:09 +00001185 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001186 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001187 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001188 tt = date_time
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001189 elif isinstance(date_time, str):
Tim Peters07e99cb2001-01-14 23:47:14 +00001190 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001191 else:
1192 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001193
Tim Peters07e99cb2001-01-14 23:47:14 +00001194 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1195 if dt[0] == '0':
1196 dt = ' ' + dt[1:]
1197 if time.daylight and tt[-1]:
1198 zone = -time.altzone
1199 else:
1200 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001201 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001202
1203
1204
Guido van Rossum8c062211999-12-13 23:27:45 +00001205if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001206
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001207 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001208
Tim Peters07e99cb2001-01-14 23:47:14 +00001209 try:
1210 optlist, args = getopt.getopt(sys.argv[1:], 'd:')
1211 except getopt.error, val:
1212 pass
Guido van Rossum66d45132000-03-28 20:20:53 +00001213
Tim Peters07e99cb2001-01-14 23:47:14 +00001214 for opt,val in optlist:
1215 if opt == '-d':
1216 Debug = int(val)
Guido van Rossum66d45132000-03-28 20:20:53 +00001217
Tim Peters07e99cb2001-01-14 23:47:14 +00001218 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001219
Tim Peters07e99cb2001-01-14 23:47:14 +00001220 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001221
Tim Peters07e99cb2001-01-14 23:47:14 +00001222 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001223 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001224
Piers Laudere02f9042001-08-05 10:43:03 +00001225 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 +00001226 test_seq1 = (
1227 ('login', (USER, PASSWD)),
1228 ('create', ('/tmp/xxx 1',)),
1229 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1230 ('CREATE', ('/tmp/yyz 2',)),
1231 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1232 ('list', ('/tmp', 'yy*')),
1233 ('select', ('/tmp/yyz 2',)),
1234 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001235 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001236 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001237 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001238 ('expunge', ()),
1239 ('recent', ()),
1240 ('close', ()),
1241 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001242
Tim Peters07e99cb2001-01-14 23:47:14 +00001243 test_seq2 = (
1244 ('select', ()),
1245 ('response',('UIDVALIDITY',)),
1246 ('uid', ('SEARCH', 'ALL')),
1247 ('response', ('EXISTS',)),
1248 ('append', (None, None, None, test_mesg)),
1249 ('recent', ()),
1250 ('logout', ()),
1251 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001252
Tim Peters07e99cb2001-01-14 23:47:14 +00001253 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001254 M._mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001255 typ, dat = apply(getattr(M, cmd), args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001256 M._mesg('%s => %s %s' % (cmd, typ, dat))
Tim Peters07e99cb2001-01-14 23:47:14 +00001257 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001258
Tim Peters07e99cb2001-01-14 23:47:14 +00001259 try:
1260 M = IMAP4(host)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001261 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1262 M._mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001263
Tim Peters07e99cb2001-01-14 23:47:14 +00001264 for cmd,args in test_seq1:
1265 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001266
Tim Peters07e99cb2001-01-14 23:47:14 +00001267 for ml in run('list', ('/tmp/', 'yy%')):
1268 mo = re.match(r'.*"([^"]+)"$', ml)
1269 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001270 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001271 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001272
Tim Peters07e99cb2001-01-14 23:47:14 +00001273 for cmd,args in test_seq2:
1274 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001275
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1277 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001278
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001279 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 if not uid: continue
1281 run('uid', ('FETCH', '%s' % uid[-1],
1282 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001283
Tim Peters07e99cb2001-01-14 23:47:14 +00001284 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001285
Tim Peters07e99cb2001-01-14 23:47:14 +00001286 except:
1287 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001288
Tim Peters07e99cb2001-01-14 23:47:14 +00001289 if not Debug:
1290 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001291If you would like to see debugging output,
1292try: %s -d5
1293''' % sys.argv[0]
1294
Tim Peters07e99cb2001-01-14 23:47:14 +00001295 raise