blob: 2a7630b24d0b0b2e735ada6fac9cfb4d56052757 [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.
Piers Lauder3fca2912002-06-17 07:07:20 +000019# GET/SETQUOTA contributed by Andreas Zeidler <az@kreativkombinat.de> June 2002.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000020
Piers Lauder3fca2912002-06-17 07:07:20 +000021__version__ = "2.52"
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000022
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +000023import binascii, re, socket, time, random, sys
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000024
Barry Warsawf4493912001-01-24 04:16:09 +000025__all__ = ["IMAP4", "Internaldate2tuple",
26 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000027
Tim Peters07e99cb2001-01-14 23:47:14 +000028# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000029
30CRLF = '\r\n'
31Debug = 0
32IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000033IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000034AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000035
Tim Peters07e99cb2001-01-14 23:47:14 +000036# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000037
38Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000039 # name valid states
40 'APPEND': ('AUTH', 'SELECTED'),
41 'AUTHENTICATE': ('NONAUTH',),
42 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
43 'CHECK': ('SELECTED',),
44 'CLOSE': ('SELECTED',),
45 'COPY': ('SELECTED',),
46 'CREATE': ('AUTH', 'SELECTED'),
47 'DELETE': ('AUTH', 'SELECTED'),
48 'EXAMINE': ('AUTH', 'SELECTED'),
49 'EXPUNGE': ('SELECTED',),
50 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000051 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000052 'GETQUOTA': ('AUTH', 'SELECTED'),
53 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000054 'LIST': ('AUTH', 'SELECTED'),
55 'LOGIN': ('NONAUTH',),
56 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
57 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000058 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000059 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000060 'PARTIAL': ('SELECTED',), # NB: obsolete
Tim Peters07e99cb2001-01-14 23:47:14 +000061 'RENAME': ('AUTH', 'SELECTED'),
62 'SEARCH': ('SELECTED',),
63 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000064 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000065 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000066 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000067 'STATUS': ('AUTH', 'SELECTED'),
68 'STORE': ('SELECTED',),
69 'SUBSCRIBE': ('AUTH', 'SELECTED'),
70 'UID': ('SELECTED',),
71 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
72 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000073
Tim Peters07e99cb2001-01-14 23:47:14 +000074# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000075
Guido van Rossumeda960a1998-06-18 14:24:28 +000076Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000077Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
78InternalDate = re.compile(r'.*INTERNALDATE "'
Tim Peters07e99cb2001-01-14 23:47:14 +000079 r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
80 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
81 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
82 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000083Literal = re.compile(r'.*{(?P<size>\d+)}$')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000084Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000085Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000086Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
87
88
89
90class IMAP4:
91
Tim Peters07e99cb2001-01-14 23:47:14 +000092 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000093
Tim Peters07e99cb2001-01-14 23:47:14 +000094 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000095
Tim Peters07e99cb2001-01-14 23:47:14 +000096 host - host's name (default: localhost);
97 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000098
Tim Peters07e99cb2001-01-14 23:47:14 +000099 All IMAP4rev1 commands are supported by methods of the same
100 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000101
Tim Peters07e99cb2001-01-14 23:47:14 +0000102 All arguments to commands are converted to strings, except for
103 AUTHENTICATE, and the last argument to APPEND which is passed as
104 an IMAP4 literal. If necessary (the string contains any
105 non-printing characters or white-space and isn't enclosed with
106 either parentheses or double quotes) each string is quoted.
107 However, the 'password' argument to the LOGIN command is always
108 quoted. If you want to avoid having an argument string quoted
109 (eg: the 'flags' argument to STORE) then enclose the string in
110 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000111
Tim Peters07e99cb2001-01-14 23:47:14 +0000112 Each command returns a tuple: (type, [data, ...]) where 'type'
113 is usually 'OK' or 'NO', and 'data' is either the text from the
114 tagged response, or untagged results from command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000115
Tim Peters07e99cb2001-01-14 23:47:14 +0000116 Errors raise the exception class <instance>.error("<reason>").
117 IMAP4 server errors raise <instance>.abort("<reason>"),
118 which is a sub-class of 'error'. Mailbox status changes
119 from READ-WRITE to READ-ONLY raise the exception class
120 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000121
Tim Peters07e99cb2001-01-14 23:47:14 +0000122 "error" exceptions imply a program error.
123 "abort" exceptions imply the connection should be reset, and
124 the command re-tried.
125 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000126
Tim Peters07e99cb2001-01-14 23:47:14 +0000127 Note: to use this module, you must read the RFCs pertaining
128 to the IMAP4 protocol, as the semantics of the arguments to
129 each IMAP4 command are left to the invoker, not to mention
130 the results.
131 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000132
Tim Peters07e99cb2001-01-14 23:47:14 +0000133 class error(Exception): pass # Logical errors - debug required
134 class abort(error): pass # Service errors - close and retry
135 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000136
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000138
Tim Peters07e99cb2001-01-14 23:47:14 +0000139 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000140 self.debug = Debug
141 self.state = 'LOGOUT'
142 self.literal = None # A literal argument to a command
143 self.tagged_commands = {} # Tagged commands awaiting response
144 self.untagged_responses = {} # {typ: [data, ...], ...}
145 self.continuation_response = '' # Last continuation response
146 self.is_readonly = None # READ-ONLY desired state
147 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000148
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000150
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000152
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 # Create unique tag for this session,
154 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000155
Tim Peters07e99cb2001-01-14 23:47:14 +0000156 self.tagpre = Int2AP(random.randint(0, 31999))
157 self.tagre = re.compile(r'(?P<tag>'
158 + self.tagpre
159 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000160
Tim Peters07e99cb2001-01-14 23:47:14 +0000161 # Get server welcome message,
162 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000163
Tim Peters07e99cb2001-01-14 23:47:14 +0000164 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000165 self._cmd_log_len = 10
166 self._cmd_log_idx = 0
167 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000169 self._mesg('imaplib version %s' % __version__)
170 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000173 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000174 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000175 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000176 self.state = 'NONAUTH'
177 else:
178 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000179
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 cap = 'CAPABILITY'
181 self._simple_command(cap)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000182 if not cap in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 raise self.error('no CAPABILITY response from server')
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000184 self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000185
Tim Peters07e99cb2001-01-14 23:47:14 +0000186 if __debug__:
187 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000188 self._mesg('CAPABILITIES: %s' % `self.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000189
Tim Peters07e99cb2001-01-14 23:47:14 +0000190 for version in AllowedVersions:
191 if not version in self.capabilities:
192 continue
193 self.PROTOCOL_VERSION = version
194 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000195
Tim Peters07e99cb2001-01-14 23:47:14 +0000196 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000197
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000198
Tim Peters07e99cb2001-01-14 23:47:14 +0000199 def __getattr__(self, attr):
200 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000201 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000202 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000203 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000204
205
206
Piers Lauder15e5d532001-07-20 10:52:06 +0000207 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000208
209
Piers Lauderf97b2d72002-06-05 22:31:57 +0000210 def open(self, host = '', port = IMAP4_PORT):
211 """Setup connection to remote server on "host:port"
212 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000213 This connection will be used by the routines:
214 read, readline, send, shutdown.
215 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000216 self.host = host
217 self.port = port
Tim Peters07e99cb2001-01-14 23:47:14 +0000218 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +0000219 self.sock.connect((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000220 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000221
222
Piers Lauder15e5d532001-07-20 10:52:06 +0000223 def read(self, size):
224 """Read 'size' bytes from remote."""
225 return self.file.read(size)
226
227
228 def readline(self):
229 """Read line from remote."""
230 return self.file.readline()
231
232
233 def send(self, data):
234 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000235 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000236
Piers Lauderf2d7d152002-02-22 01:15:17 +0000237
Piers Lauder15e5d532001-07-20 10:52:06 +0000238 def shutdown(self):
239 """Close I/O established in "open"."""
240 self.file.close()
241 self.sock.close()
242
243
244 def socket(self):
245 """Return socket instance used to connect to IMAP4 server.
246
247 socket = <instance>.socket()
248 """
249 return self.sock
250
251
252
253 # Utility methods
254
255
Tim Peters07e99cb2001-01-14 23:47:14 +0000256 def recent(self):
257 """Return most recent 'RECENT' responses if any exist,
258 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000259
Tim Peters07e99cb2001-01-14 23:47:14 +0000260 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000261
Tim Peters07e99cb2001-01-14 23:47:14 +0000262 'data' is None if no new messages,
263 else list of RECENT responses, most recent last.
264 """
265 name = 'RECENT'
266 typ, dat = self._untagged_response('OK', [None], name)
267 if dat[-1]:
268 return typ, dat
269 typ, dat = self.noop() # Prod server for response
270 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000271
272
Tim Peters07e99cb2001-01-14 23:47:14 +0000273 def response(self, code):
274 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000275
Tim Peters07e99cb2001-01-14 23:47:14 +0000276 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000277
Tim Peters07e99cb2001-01-14 23:47:14 +0000278 (code, [data]) = <instance>.response(code)
279 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000280 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000281
282
Guido van Rossum26367a01998-09-28 15:34:46 +0000283
Tim Peters07e99cb2001-01-14 23:47:14 +0000284 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000285
286
Tim Peters07e99cb2001-01-14 23:47:14 +0000287 def append(self, mailbox, flags, date_time, message):
288 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000289
Tim Peters07e99cb2001-01-14 23:47:14 +0000290 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000291
Tim Peters07e99cb2001-01-14 23:47:14 +0000292 All args except `message' can be None.
293 """
294 name = 'APPEND'
295 if not mailbox:
296 mailbox = 'INBOX'
297 if flags:
298 if (flags[0],flags[-1]) != ('(',')'):
299 flags = '(%s)' % flags
300 else:
301 flags = None
302 if date_time:
303 date_time = Time2Internaldate(date_time)
304 else:
305 date_time = None
306 self.literal = message
307 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000308
309
Tim Peters07e99cb2001-01-14 23:47:14 +0000310 def authenticate(self, mechanism, authobject):
311 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000312
Tim Peters07e99cb2001-01-14 23:47:14 +0000313 'mechanism' specifies which authentication mechanism is to
314 be used - it must appear in <instance>.capabilities in the
315 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000316
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000318
Tim Peters07e99cb2001-01-14 23:47:14 +0000319 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000320
Tim Peters07e99cb2001-01-14 23:47:14 +0000321 It will be called to process server continuation responses.
322 It should return data that will be encoded and sent to server.
323 It should return None if the client abort response '*' should
324 be sent instead.
325 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000326 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000327 cap = 'AUTH=%s' % mech
328 if not cap in self.capabilities:
329 raise self.error("Server doesn't allow %s authentication." % mech)
330 self.literal = _Authenticator(authobject).process
331 typ, dat = self._simple_command('AUTHENTICATE', mech)
332 if typ != 'OK':
333 raise self.error(dat[-1])
334 self.state = 'AUTH'
335 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000336
337
Tim Peters07e99cb2001-01-14 23:47:14 +0000338 def check(self):
339 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 (typ, [data]) = <instance>.check()
342 """
343 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000344
345
Tim Peters07e99cb2001-01-14 23:47:14 +0000346 def close(self):
347 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000348
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 Deleted messages are removed from writable mailbox.
350 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000351
Tim Peters07e99cb2001-01-14 23:47:14 +0000352 (typ, [data]) = <instance>.close()
353 """
354 try:
355 typ, dat = self._simple_command('CLOSE')
356 finally:
357 self.state = 'AUTH'
358 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000359
360
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 def copy(self, message_set, new_mailbox):
362 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000363
Tim Peters07e99cb2001-01-14 23:47:14 +0000364 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
365 """
366 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000367
368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 def create(self, mailbox):
370 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 (typ, [data]) = <instance>.create(mailbox)
373 """
374 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000375
376
Tim Peters07e99cb2001-01-14 23:47:14 +0000377 def delete(self, mailbox):
378 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 (typ, [data]) = <instance>.delete(mailbox)
381 """
382 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000383
384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 def expunge(self):
386 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000391
Tim Peters07e99cb2001-01-14 23:47:14 +0000392 'data' is list of 'EXPUNGE'd message numbers in order received.
393 """
394 name = 'EXPUNGE'
395 typ, dat = self._simple_command(name)
396 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 def fetch(self, message_set, message_parts):
400 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000401
Tim Peters07e99cb2001-01-14 23:47:14 +0000402 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 'message_parts' should be a string of selected parts
405 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000406
Tim Peters07e99cb2001-01-14 23:47:14 +0000407 'data' are tuples of message part envelope and data.
408 """
409 name = 'FETCH'
410 typ, dat = self._simple_command(name, message_set, message_parts)
411 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000412
413
Piers Lauder15e5d532001-07-20 10:52:06 +0000414 def getacl(self, mailbox):
415 """Get the ACLs for a mailbox.
416
417 (typ, [data]) = <instance>.getacl(mailbox)
418 """
419 typ, dat = self._simple_command('GETACL', mailbox)
420 return self._untagged_response(typ, dat, 'ACL')
421
422
Piers Lauder3fca2912002-06-17 07:07:20 +0000423 def getquota(self, root):
424 """Get the quota root's resource usage and limits.
425
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000426 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000427
428 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000429 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000430 typ, dat = self._simple_command('GETQUOTA', root)
431 return self._untagged_response(typ, dat, 'QUOTA')
432
433
434 def getquotaroot(self, mailbox):
435 """Get the list of quota roots for the named mailbox.
436
437 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000438 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000439 typ, dat = self._simple_command('GETQUOTA', root)
440 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
441 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000442 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000443
444
Tim Peters07e99cb2001-01-14 23:47:14 +0000445 def list(self, directory='""', pattern='*'):
446 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000447
Tim Peters07e99cb2001-01-14 23:47:14 +0000448 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 'data' is list of LIST responses.
451 """
452 name = 'LIST'
453 typ, dat = self._simple_command(name, directory, pattern)
454 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000455
456
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 def login(self, user, password):
458 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000459
Tim Peters07e99cb2001-01-14 23:47:14 +0000460 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000461
Tim Peters07e99cb2001-01-14 23:47:14 +0000462 NB: 'password' will be quoted.
463 """
464 #if not 'AUTH=LOGIN' in self.capabilities:
465 # raise self.error("Server doesn't allow LOGIN authentication." % mech)
466 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
467 if typ != 'OK':
468 raise self.error(dat[-1])
469 self.state = 'AUTH'
470 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000471
472
Tim Peters07e99cb2001-01-14 23:47:14 +0000473 def logout(self):
474 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000475
Tim Peters07e99cb2001-01-14 23:47:14 +0000476 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000477
Tim Peters07e99cb2001-01-14 23:47:14 +0000478 Returns server 'BYE' response.
479 """
480 self.state = 'LOGOUT'
481 try: typ, dat = self._simple_command('LOGOUT')
482 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000483 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000484 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000485 return 'BYE', self.untagged_responses['BYE']
486 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000487
488
Tim Peters07e99cb2001-01-14 23:47:14 +0000489 def lsub(self, directory='""', pattern='*'):
490 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000491
Tim Peters07e99cb2001-01-14 23:47:14 +0000492 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000493
Tim Peters07e99cb2001-01-14 23:47:14 +0000494 'data' are tuples of message part envelope and data.
495 """
496 name = 'LSUB'
497 typ, dat = self._simple_command(name, directory, pattern)
498 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000499
500
Piers Lauder15e5d532001-07-20 10:52:06 +0000501 def namespace(self):
502 """ Returns IMAP namespaces ala rfc2342
503
504 (typ, [data, ...]) = <instance>.namespace()
505 """
506 name = 'NAMESPACE'
507 typ, dat = self._simple_command(name)
508 return self._untagged_response(typ, dat, name)
509
510
Tim Peters07e99cb2001-01-14 23:47:14 +0000511 def noop(self):
512 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 (typ, data) = <instance>.noop()
515 """
516 if __debug__:
517 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000518 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000519 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000520
521
Tim Peters07e99cb2001-01-14 23:47:14 +0000522 def partial(self, message_num, message_part, start, length):
523 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000524
Tim Peters07e99cb2001-01-14 23:47:14 +0000525 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000526
Tim Peters07e99cb2001-01-14 23:47:14 +0000527 'data' is tuple of message part envelope and data.
528 """
529 name = 'PARTIAL'
530 typ, dat = self._simple_command(name, message_num, message_part, start, length)
531 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000532
533
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 def rename(self, oldmailbox, newmailbox):
535 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 (typ, data) = <instance>.rename(oldmailbox, newmailbox)
538 """
539 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000540
541
Tim Peters07e99cb2001-01-14 23:47:14 +0000542 def search(self, charset, *criteria):
543 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000544
Tim Peters07e99cb2001-01-14 23:47:14 +0000545 (typ, [data]) = <instance>.search(charset, criterium, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000546
Tim Peters07e99cb2001-01-14 23:47:14 +0000547 'data' is space separated list of matching message numbers.
548 """
549 name = 'SEARCH'
550 if charset:
Piers Lauder15e5d532001-07-20 10:52:06 +0000551 typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria)
552 else:
553 typ, dat = apply(self._simple_command, (name,) + criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000554 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000555
556
Tim Peters07e99cb2001-01-14 23:47:14 +0000557 def select(self, mailbox='INBOX', readonly=None):
558 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000559
Tim Peters07e99cb2001-01-14 23:47:14 +0000560 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000561
Tim Peters07e99cb2001-01-14 23:47:14 +0000562 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000563
Tim Peters07e99cb2001-01-14 23:47:14 +0000564 'data' is count of messages in mailbox ('EXISTS' response).
565 """
566 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
567 self.untagged_responses = {} # Flush old responses.
568 self.is_readonly = readonly
Raymond Hettinger936654b2002-06-01 03:06:31 +0000569 if readonly is not None:
Tim Peters07e99cb2001-01-14 23:47:14 +0000570 name = 'EXAMINE'
571 else:
572 name = 'SELECT'
573 typ, dat = self._simple_command(name, mailbox)
574 if typ != 'OK':
575 self.state = 'AUTH' # Might have been 'SELECTED'
576 return typ, dat
577 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000578 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000579 and not readonly:
580 if __debug__:
581 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000582 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 raise self.readonly('%s is not writable' % mailbox)
584 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000585
586
Piers Lauder15e5d532001-07-20 10:52:06 +0000587 def setacl(self, mailbox, who, what):
588 """Set a mailbox acl.
589
590 (typ, [data]) = <instance>.create(mailbox, who, what)
591 """
592 return self._simple_command('SETACL', mailbox, who, what)
593
594
Piers Lauder3fca2912002-06-17 07:07:20 +0000595 def setquota(self, root, limits):
596 """Set the quota root's resource limits.
597
598 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000599 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000600 typ, dat = self._simple_command('SETQUOTA', root, limits)
601 return self._untagged_response(typ, dat, 'QUOTA')
602
603
Piers Lauder15e5d532001-07-20 10:52:06 +0000604 def sort(self, sort_criteria, charset, *search_criteria):
605 """IMAP4rev1 extension SORT command.
606
607 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
608 """
609 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000610 #if not name in self.capabilities: # Let the server decide!
611 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000612 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000613 sort_criteria = '(%s)' % sort_criteria
Piers Lauder15e5d532001-07-20 10:52:06 +0000614 typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria)
615 return self._untagged_response(typ, dat, name)
616
617
Tim Peters07e99cb2001-01-14 23:47:14 +0000618 def status(self, mailbox, names):
619 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000620
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 (typ, [data]) = <instance>.status(mailbox, names)
622 """
623 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000624 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000625 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000626 typ, dat = self._simple_command(name, mailbox, names)
627 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000628
629
Tim Peters07e99cb2001-01-14 23:47:14 +0000630 def store(self, message_set, command, flags):
631 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000632
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 (typ, [data]) = <instance>.store(message_set, command, flags)
634 """
635 if (flags[0],flags[-1]) != ('(',')'):
636 flags = '(%s)' % flags # Avoid quoting the flags
637 typ, dat = self._simple_command('STORE', message_set, command, flags)
638 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000639
640
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 def subscribe(self, mailbox):
642 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000643
Tim Peters07e99cb2001-01-14 23:47:14 +0000644 (typ, [data]) = <instance>.subscribe(mailbox)
645 """
646 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000647
648
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 def uid(self, command, *args):
650 """Execute "command arg ..." with messages identified by UID,
651 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000652
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 Returns response appropriate to 'command'.
656 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000657 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000658 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 raise self.error("Unknown IMAP4 UID command: %s" % command)
660 if self.state not in Commands[command]:
661 raise self.error('command %s illegal in state %s'
662 % (command, self.state))
663 name = 'UID'
664 typ, dat = apply(self._simple_command, (name, command) + args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000665 if command in ('SEARCH', 'SORT'):
666 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 else:
668 name = 'FETCH'
669 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000670
671
Tim Peters07e99cb2001-01-14 23:47:14 +0000672 def unsubscribe(self, mailbox):
673 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000674
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 (typ, [data]) = <instance>.unsubscribe(mailbox)
676 """
677 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000678
679
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 def xatom(self, name, *args):
681 """Allow simple extension commands
682 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000683
Piers Lauder15e5d532001-07-20 10:52:06 +0000684 Assumes command is legal in current state.
685
Tim Peters07e99cb2001-01-14 23:47:14 +0000686 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000687
688 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000689 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000690 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000691 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000692 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000693 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000694 Commands[name] = (self.state,)
Tim Peters07e99cb2001-01-14 23:47:14 +0000695 return apply(self._simple_command, (name,) + args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000696
697
698
Tim Peters07e99cb2001-01-14 23:47:14 +0000699 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000700
701
Tim Peters07e99cb2001-01-14 23:47:14 +0000702 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000703
Tim Peters07e99cb2001-01-14 23:47:14 +0000704 if dat is None: dat = ''
705 ur = self.untagged_responses
706 if __debug__:
707 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000708 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000709 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000710 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 ur[typ].append(dat)
712 else:
713 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000714
715
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 def _check_bye(self):
717 bye = self.untagged_responses.get('BYE')
718 if bye:
719 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000720
721
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000723
Tim Peters07e99cb2001-01-14 23:47:14 +0000724 if self.state not in Commands[name]:
725 self.literal = None
726 raise self.error(
727 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000728
Tim Peters07e99cb2001-01-14 23:47:14 +0000729 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000730 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000731 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000732
Raymond Hettinger54f02222002-06-01 14:18:47 +0000733 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 and not self.is_readonly:
735 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000736
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 tag = self._new_tag()
738 data = '%s %s' % (tag, name)
739 for arg in args:
740 if arg is None: continue
741 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000742
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 literal = self.literal
744 if literal is not None:
745 self.literal = None
746 if type(literal) is type(self._command):
747 literator = literal
748 else:
749 literator = None
750 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000751
Tim Peters07e99cb2001-01-14 23:47:14 +0000752 if __debug__:
753 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000754 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000756 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000757
Tim Peters07e99cb2001-01-14 23:47:14 +0000758 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000759 self.send('%s%s' % (data, CRLF))
760 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000761 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000762
Tim Peters07e99cb2001-01-14 23:47:14 +0000763 if literal is None:
764 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000765
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 while 1:
767 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000768
Tim Peters07e99cb2001-01-14 23:47:14 +0000769 while self._get_response():
770 if self.tagged_commands[tag]: # BAD/NO?
771 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000772
Tim Peters07e99cb2001-01-14 23:47:14 +0000773 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000774
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 if literator:
776 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000777
Tim Peters07e99cb2001-01-14 23:47:14 +0000778 if __debug__:
779 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000780 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000781
Tim Peters07e99cb2001-01-14 23:47:14 +0000782 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000783 self.send(literal)
784 self.send(CRLF)
785 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000786 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000787
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 if not literator:
789 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000790
Tim Peters07e99cb2001-01-14 23:47:14 +0000791 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000792
793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 def _command_complete(self, name, tag):
795 self._check_bye()
796 try:
797 typ, data = self._get_tagged_response(tag)
798 except self.abort, val:
799 raise self.abort('command: %s => %s' % (name, val))
800 except self.error, val:
801 raise self.error('command: %s => %s' % (name, val))
802 self._check_bye()
803 if typ == 'BAD':
804 raise self.error('%s command error: %s %s' % (name, typ, data))
805 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000806
807
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000809
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 # Read response and store.
811 #
812 # Returns None for continuation responses,
813 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000814
Tim Peters07e99cb2001-01-14 23:47:14 +0000815 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 if self._match(self.tagre, resp):
820 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000821 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 typ = self.mo.group('type')
825 dat = self.mo.group('data')
826 self.tagged_commands[tag] = (typ, [dat])
827 else:
828 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000829
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 if not self._match(Untagged_response, resp):
833 if self._match(Untagged_status, resp):
834 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 if self.mo is None:
837 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 if self._match(Continuation, resp):
840 self.continuation_response = self.mo.group('data')
841 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000844
Tim Peters07e99cb2001-01-14 23:47:14 +0000845 typ = self.mo.group('type')
846 dat = self.mo.group('data')
847 if dat is None: dat = '' # Null untagged response
848 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000851
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000853
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000855
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000856 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 if __debug__:
858 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000859 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000860 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000861
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000865
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000873
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
875 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 if __debug__:
878 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000879 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000880
Tim Peters07e99cb2001-01-14 23:47:14 +0000881 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 while 1:
887 result = self.tagged_commands[tag]
888 if result is not None:
889 del self.tagged_commands[tag]
890 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000891
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 # Some have reported "unexpected response" exceptions.
893 # Note that ignoring them here causes loops.
894 # Instead, send me details of the unexpected response and
895 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 try:
898 self._get_response()
899 except self.abort, val:
900 if __debug__:
901 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000902 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000904
905
Tim Peters07e99cb2001-01-14 23:47:14 +0000906 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
Piers Lauder15e5d532001-07-20 10:52:06 +0000908 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 if not line:
910 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000911
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000913
Tim Peters07e99cb2001-01-14 23:47:14 +0000914 line = line[:-2]
915 if __debug__:
916 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000917 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000919 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000921
922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 # Run compiled regular expression match method on 's'.
926 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 self.mo = cre.match(s)
929 if __debug__:
930 if self.mo is not None and self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000931 self._mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 tag = '%s%s' % (self.tagpre, self.tagnum)
938 self.tagnum = self.tagnum + 1
939 self.tagged_commands[tag] = None
940 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000941
942
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 # Must quote command args if non-alphanumeric chars present,
946 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000947
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 if type(arg) is not type(''):
949 return arg
950 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
951 return arg
952 if self.mustquote.search(arg) is None:
953 return arg
954 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000955
956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000958
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000959 arg = arg.replace('\\', '\\\\')
960 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000961
Tim Peters07e99cb2001-01-14 23:47:14 +0000962 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000963
964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 return self._command_complete(name, apply(self._command, (name,) + args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
969
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000971
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 if typ == 'NO':
973 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +0000974 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 return typ, [None]
976 data = self.untagged_responses[name]
977 if __debug__:
978 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000979 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 del self.untagged_responses[name]
981 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
983
Piers Lauderf2d7d152002-02-22 01:15:17 +0000984 if __debug__:
985
986 def _mesg(self, s, secs=None):
987 if secs is None:
988 secs = time.time()
989 tm = time.strftime('%M:%S', time.localtime(secs))
990 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
991 sys.stderr.flush()
992
993 def _dump_ur(self, dict):
994 # Dump untagged responses (in `dict').
995 l = dict.items()
996 if not l: return
997 t = '\n\t\t'
998 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
999 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1000
1001 def _log(self, line):
1002 # Keep log of last `_cmd_log_len' interactions for debugging.
1003 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1004 self._cmd_log_idx += 1
1005 if self._cmd_log_idx >= self._cmd_log_len:
1006 self._cmd_log_idx = 0
1007
1008 def print_log(self):
1009 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1010 i, n = self._cmd_log_idx, self._cmd_log_len
1011 while n:
1012 try:
1013 apply(self._mesg, self._cmd_log[i])
1014 except:
1015 pass
1016 i += 1
1017 if i >= self._cmd_log_len:
1018 i = 0
1019 n -= 1
1020
1021
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001022
Piers Laudera4f83132002-03-08 01:53:24 +00001023class IMAP4_SSL(IMAP4):
1024
1025 """IMAP4 client class over SSL connection
1026
Piers Lauder95f84952002-03-08 09:05:12 +00001027 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001028
1029 host - host's name (default: localhost);
1030 port - port number (default: standard IMAP4 SSL port).
1031 keyfile - PEM formatted file that contains your private key (default: None);
1032 certfile - PEM formatted certificate chain file (default: None);
1033
1034 for more documentation see the docstring of the parent class IMAP4.
1035 """
1036
1037
1038 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1039 self.keyfile = keyfile
1040 self.certfile = certfile
1041 IMAP4.__init__(self, host, port)
1042
1043
Piers Lauderf97b2d72002-06-05 22:31:57 +00001044 def open(self, host = '', port = IMAP4_SSL_PORT):
Piers Laudera4f83132002-03-08 01:53:24 +00001045 """Setup connection to remote server on "host:port".
Piers Lauderf97b2d72002-06-05 22:31:57 +00001046 (default: localhost:standard IMAP4 SSL port).
Piers Laudera4f83132002-03-08 01:53:24 +00001047 This connection will be used by the routines:
1048 read, readline, send, shutdown.
1049 """
Piers Lauderf97b2d72002-06-05 22:31:57 +00001050 self.host = host
1051 self.port = port
Piers Laudera4f83132002-03-08 01:53:24 +00001052 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +00001053 self.sock.connect((host, port))
1054 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001055
1056
1057 def read(self, size):
1058 """Read 'size' bytes from remote."""
1059 return self.sslobj.read(size)
1060
1061
1062 def readline(self):
1063 """Read line from remote."""
1064 line = ""
1065 while 1:
1066 char = self.sslobj.read(1)
1067 line += char
1068 if char == "\n": return line
1069
1070
1071 def send(self, data):
1072 """Send data to remote."""
1073 self.sslobj.write(data)
1074
1075
1076 def shutdown(self):
1077 """Close I/O established in "open"."""
1078 self.sock.close()
1079
1080
1081 def socket(self):
1082 """Return socket instance used to connect to IMAP4 server.
1083
1084 socket = <instance>.socket()
1085 """
1086 return self.sock
1087
1088
1089 def ssl(self):
1090 """Return SSLObject instance used to communicate with the IMAP4 server.
1091
1092 ssl = <instance>.socket.ssl()
1093 """
1094 return self.sslobj
1095
1096
1097
Guido van Rossumeda960a1998-06-18 14:24:28 +00001098class _Authenticator:
1099
Tim Peters07e99cb2001-01-14 23:47:14 +00001100 """Private class to provide en/decoding
1101 for base64-based authentication conversation.
1102 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001103
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 def __init__(self, mechinst):
1105 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001106
Tim Peters07e99cb2001-01-14 23:47:14 +00001107 def process(self, data):
1108 ret = self.mech(self.decode(data))
1109 if ret is None:
1110 return '*' # Abort conversation
1111 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001112
Tim Peters07e99cb2001-01-14 23:47:14 +00001113 def encode(self, inp):
1114 #
1115 # Invoke binascii.b2a_base64 iteratively with
1116 # short even length buffers, strip the trailing
1117 # line feed from the result and append. "Even"
1118 # means a number that factors to both 6 and 8,
1119 # so when it gets to the end of the 8-bit input
1120 # there's no partial 6-bit output.
1121 #
1122 oup = ''
1123 while inp:
1124 if len(inp) > 48:
1125 t = inp[:48]
1126 inp = inp[48:]
1127 else:
1128 t = inp
1129 inp = ''
1130 e = binascii.b2a_base64(t)
1131 if e:
1132 oup = oup + e[:-1]
1133 return oup
1134
1135 def decode(self, inp):
1136 if not inp:
1137 return ''
1138 return binascii.a2b_base64(inp)
1139
Guido van Rossumeda960a1998-06-18 14:24:28 +00001140
1141
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001142Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001143 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001144
1145def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001146 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001147
Tim Peters07e99cb2001-01-14 23:47:14 +00001148 Returns Python time module tuple.
1149 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001150
Tim Peters07e99cb2001-01-14 23:47:14 +00001151 mo = InternalDate.match(resp)
1152 if not mo:
1153 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001154
Tim Peters07e99cb2001-01-14 23:47:14 +00001155 mon = Mon2num[mo.group('mon')]
1156 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001157
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001158 day = int(mo.group('day'))
1159 year = int(mo.group('year'))
1160 hour = int(mo.group('hour'))
1161 min = int(mo.group('min'))
1162 sec = int(mo.group('sec'))
1163 zoneh = int(mo.group('zoneh'))
1164 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001165
Tim Peters07e99cb2001-01-14 23:47:14 +00001166 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001167
Tim Peters07e99cb2001-01-14 23:47:14 +00001168 zone = (zoneh*60 + zonem)*60
1169 if zonen == '-':
1170 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001171
Tim Peters07e99cb2001-01-14 23:47:14 +00001172 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001173
Tim Peters07e99cb2001-01-14 23:47:14 +00001174 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001175
Tim Peters07e99cb2001-01-14 23:47:14 +00001176 # Following is necessary because the time module has no 'mkgmtime'.
1177 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001178
Tim Peters07e99cb2001-01-14 23:47:14 +00001179 lt = time.localtime(utc)
1180 if time.daylight and lt[-1]:
1181 zone = zone + time.altzone
1182 else:
1183 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001184
Tim Peters07e99cb2001-01-14 23:47:14 +00001185 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001186
1187
1188
1189def Int2AP(num):
1190
Tim Peters07e99cb2001-01-14 23:47:14 +00001191 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001192
Tim Peters07e99cb2001-01-14 23:47:14 +00001193 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1194 num = int(abs(num))
1195 while num:
1196 num, mod = divmod(num, 16)
1197 val = AP[mod] + val
1198 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001199
1200
1201
1202def ParseFlags(resp):
1203
Tim Peters07e99cb2001-01-14 23:47:14 +00001204 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001205
Tim Peters07e99cb2001-01-14 23:47:14 +00001206 mo = Flags.match(resp)
1207 if not mo:
1208 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001209
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001210 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001211
1212
1213def Time2Internaldate(date_time):
1214
Tim Peters07e99cb2001-01-14 23:47:14 +00001215 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001216
Tim Peters07e99cb2001-01-14 23:47:14 +00001217 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1218 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001219
Fred Drakedb519202002-01-05 17:17:09 +00001220 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001221 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001222 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001223 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001224 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001225 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001226 else:
1227 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001228
Tim Peters07e99cb2001-01-14 23:47:14 +00001229 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1230 if dt[0] == '0':
1231 dt = ' ' + dt[1:]
1232 if time.daylight and tt[-1]:
1233 zone = -time.altzone
1234 else:
1235 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001236 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001237
1238
1239
Guido van Rossum8c062211999-12-13 23:27:45 +00001240if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001241
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001242 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001243
Tim Peters07e99cb2001-01-14 23:47:14 +00001244 try:
1245 optlist, args = getopt.getopt(sys.argv[1:], 'd:')
1246 except getopt.error, val:
1247 pass
Guido van Rossum66d45132000-03-28 20:20:53 +00001248
Tim Peters07e99cb2001-01-14 23:47:14 +00001249 for opt,val in optlist:
1250 if opt == '-d':
1251 Debug = int(val)
Guido van Rossum66d45132000-03-28 20:20:53 +00001252
Tim Peters07e99cb2001-01-14 23:47:14 +00001253 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001254
Tim Peters07e99cb2001-01-14 23:47:14 +00001255 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001256
Tim Peters07e99cb2001-01-14 23:47:14 +00001257 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001258 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001259
Piers Laudere02f9042001-08-05 10:43:03 +00001260 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 +00001261 test_seq1 = (
1262 ('login', (USER, PASSWD)),
1263 ('create', ('/tmp/xxx 1',)),
1264 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1265 ('CREATE', ('/tmp/yyz 2',)),
1266 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1267 ('list', ('/tmp', 'yy*')),
1268 ('select', ('/tmp/yyz 2',)),
1269 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001270 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001271 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001272 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001273 ('expunge', ()),
1274 ('recent', ()),
1275 ('close', ()),
1276 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001277
Tim Peters07e99cb2001-01-14 23:47:14 +00001278 test_seq2 = (
1279 ('select', ()),
1280 ('response',('UIDVALIDITY',)),
1281 ('uid', ('SEARCH', 'ALL')),
1282 ('response', ('EXISTS',)),
1283 ('append', (None, None, None, test_mesg)),
1284 ('recent', ()),
1285 ('logout', ()),
1286 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001287
Tim Peters07e99cb2001-01-14 23:47:14 +00001288 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001289 M._mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001290 typ, dat = apply(getattr(M, cmd), args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001291 M._mesg('%s => %s %s' % (cmd, typ, dat))
Tim Peters07e99cb2001-01-14 23:47:14 +00001292 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001293
Tim Peters07e99cb2001-01-14 23:47:14 +00001294 try:
1295 M = IMAP4(host)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001296 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1297 M._mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001298
Tim Peters07e99cb2001-01-14 23:47:14 +00001299 for cmd,args in test_seq1:
1300 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001301
Tim Peters07e99cb2001-01-14 23:47:14 +00001302 for ml in run('list', ('/tmp/', 'yy%')):
1303 mo = re.match(r'.*"([^"]+)"$', ml)
1304 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001305 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001306 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001307
Tim Peters07e99cb2001-01-14 23:47:14 +00001308 for cmd,args in test_seq2:
1309 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001310
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1312 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001313
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001314 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001315 if not uid: continue
1316 run('uid', ('FETCH', '%s' % uid[-1],
1317 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001318
Tim Peters07e99cb2001-01-14 23:47:14 +00001319 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001320
Tim Peters07e99cb2001-01-14 23:47:14 +00001321 except:
1322 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001323
Tim Peters07e99cb2001-01-14 23:47:14 +00001324 if not Debug:
1325 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001326If you would like to see debugging output,
1327try: %s -d5
1328''' % sys.argv[0]
1329
Tim Peters07e99cb2001-01-14 23:47:14 +00001330 raise