blob: 91e9cb99f70c34126529e25a5d0e0df83178af9c [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 Lauder0c092932002-06-23 10:47:13 +000021__version__ = "2.53"
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]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +0000976 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 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 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
982
Piers Lauderf2d7d152002-02-22 01:15:17 +0000983 if __debug__:
984
985 def _mesg(self, s, secs=None):
986 if secs is None:
987 secs = time.time()
988 tm = time.strftime('%M:%S', time.localtime(secs))
989 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
990 sys.stderr.flush()
991
992 def _dump_ur(self, dict):
993 # Dump untagged responses (in `dict').
994 l = dict.items()
995 if not l: return
996 t = '\n\t\t'
997 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
998 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
999
1000 def _log(self, line):
1001 # Keep log of last `_cmd_log_len' interactions for debugging.
1002 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1003 self._cmd_log_idx += 1
1004 if self._cmd_log_idx >= self._cmd_log_len:
1005 self._cmd_log_idx = 0
1006
1007 def print_log(self):
1008 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1009 i, n = self._cmd_log_idx, self._cmd_log_len
1010 while n:
1011 try:
1012 apply(self._mesg, self._cmd_log[i])
1013 except:
1014 pass
1015 i += 1
1016 if i >= self._cmd_log_len:
1017 i = 0
1018 n -= 1
1019
1020
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Piers Laudera4f83132002-03-08 01:53:24 +00001022class IMAP4_SSL(IMAP4):
1023
1024 """IMAP4 client class over SSL connection
1025
Piers Lauder95f84952002-03-08 09:05:12 +00001026 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001027
1028 host - host's name (default: localhost);
1029 port - port number (default: standard IMAP4 SSL port).
1030 keyfile - PEM formatted file that contains your private key (default: None);
1031 certfile - PEM formatted certificate chain file (default: None);
1032
1033 for more documentation see the docstring of the parent class IMAP4.
1034 """
1035
1036
1037 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1038 self.keyfile = keyfile
1039 self.certfile = certfile
1040 IMAP4.__init__(self, host, port)
1041
1042
Piers Lauderf97b2d72002-06-05 22:31:57 +00001043 def open(self, host = '', port = IMAP4_SSL_PORT):
Piers Laudera4f83132002-03-08 01:53:24 +00001044 """Setup connection to remote server on "host:port".
Piers Lauderf97b2d72002-06-05 22:31:57 +00001045 (default: localhost:standard IMAP4 SSL port).
Piers Laudera4f83132002-03-08 01:53:24 +00001046 This connection will be used by the routines:
1047 read, readline, send, shutdown.
1048 """
Piers Lauderf97b2d72002-06-05 22:31:57 +00001049 self.host = host
1050 self.port = port
Piers Laudera4f83132002-03-08 01:53:24 +00001051 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +00001052 self.sock.connect((host, port))
1053 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001054
1055
1056 def read(self, size):
1057 """Read 'size' bytes from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001058 # sslobj.read() sometimes returns < size bytes
1059 data = self.sslobj.read(size)
1060 while len(data) < size:
Tim Petersc411dba2002-07-16 21:35:23 +00001061 data += self.sslobj.read(size-len(data))
Piers Lauder0c092932002-06-23 10:47:13 +00001062
1063 return data
Piers Laudera4f83132002-03-08 01:53:24 +00001064
1065
1066 def readline(self):
1067 """Read line from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001068 # NB: socket.ssl needs a "readline" method, or perhaps a "makefile" method.
Piers Laudera4f83132002-03-08 01:53:24 +00001069 line = ""
1070 while 1:
1071 char = self.sslobj.read(1)
1072 line += char
1073 if char == "\n": return line
1074
1075
1076 def send(self, data):
1077 """Send data to remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001078 # NB: socket.ssl needs a "sendall" method to match socket objects.
1079 bytes = len(data)
1080 while bytes > 0:
1081 sent = self.sslobj.write(data)
1082 if sent == bytes:
1083 break # avoid copy
1084 data = data[sent:]
1085 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001086
1087
1088 def shutdown(self):
1089 """Close I/O established in "open"."""
1090 self.sock.close()
1091
1092
1093 def socket(self):
1094 """Return socket instance used to connect to IMAP4 server.
1095
1096 socket = <instance>.socket()
1097 """
1098 return self.sock
1099
1100
1101 def ssl(self):
1102 """Return SSLObject instance used to communicate with the IMAP4 server.
1103
1104 ssl = <instance>.socket.ssl()
1105 """
1106 return self.sslobj
1107
1108
1109
Guido van Rossumeda960a1998-06-18 14:24:28 +00001110class _Authenticator:
1111
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 """Private class to provide en/decoding
1113 for base64-based authentication conversation.
1114 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 def __init__(self, mechinst):
1117 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001118
Tim Peters07e99cb2001-01-14 23:47:14 +00001119 def process(self, data):
1120 ret = self.mech(self.decode(data))
1121 if ret is None:
1122 return '*' # Abort conversation
1123 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001124
Tim Peters07e99cb2001-01-14 23:47:14 +00001125 def encode(self, inp):
1126 #
1127 # Invoke binascii.b2a_base64 iteratively with
1128 # short even length buffers, strip the trailing
1129 # line feed from the result and append. "Even"
1130 # means a number that factors to both 6 and 8,
1131 # so when it gets to the end of the 8-bit input
1132 # there's no partial 6-bit output.
1133 #
1134 oup = ''
1135 while inp:
1136 if len(inp) > 48:
1137 t = inp[:48]
1138 inp = inp[48:]
1139 else:
1140 t = inp
1141 inp = ''
1142 e = binascii.b2a_base64(t)
1143 if e:
1144 oup = oup + e[:-1]
1145 return oup
1146
1147 def decode(self, inp):
1148 if not inp:
1149 return ''
1150 return binascii.a2b_base64(inp)
1151
Guido van Rossumeda960a1998-06-18 14:24:28 +00001152
1153
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001154Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001155 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001156
1157def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001158 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001159
Tim Peters07e99cb2001-01-14 23:47:14 +00001160 Returns Python time module tuple.
1161 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001162
Tim Peters07e99cb2001-01-14 23:47:14 +00001163 mo = InternalDate.match(resp)
1164 if not mo:
1165 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001166
Tim Peters07e99cb2001-01-14 23:47:14 +00001167 mon = Mon2num[mo.group('mon')]
1168 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001169
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001170 day = int(mo.group('day'))
1171 year = int(mo.group('year'))
1172 hour = int(mo.group('hour'))
1173 min = int(mo.group('min'))
1174 sec = int(mo.group('sec'))
1175 zoneh = int(mo.group('zoneh'))
1176 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001177
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001179
Tim Peters07e99cb2001-01-14 23:47:14 +00001180 zone = (zoneh*60 + zonem)*60
1181 if zonen == '-':
1182 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001183
Tim Peters07e99cb2001-01-14 23:47:14 +00001184 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001185
Tim Peters07e99cb2001-01-14 23:47:14 +00001186 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001187
Tim Peters07e99cb2001-01-14 23:47:14 +00001188 # Following is necessary because the time module has no 'mkgmtime'.
1189 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001190
Tim Peters07e99cb2001-01-14 23:47:14 +00001191 lt = time.localtime(utc)
1192 if time.daylight and lt[-1]:
1193 zone = zone + time.altzone
1194 else:
1195 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001196
Tim Peters07e99cb2001-01-14 23:47:14 +00001197 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001198
1199
1200
1201def Int2AP(num):
1202
Tim Peters07e99cb2001-01-14 23:47:14 +00001203 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001204
Tim Peters07e99cb2001-01-14 23:47:14 +00001205 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1206 num = int(abs(num))
1207 while num:
1208 num, mod = divmod(num, 16)
1209 val = AP[mod] + val
1210 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001211
1212
1213
1214def ParseFlags(resp):
1215
Tim Peters07e99cb2001-01-14 23:47:14 +00001216 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001217
Tim Peters07e99cb2001-01-14 23:47:14 +00001218 mo = Flags.match(resp)
1219 if not mo:
1220 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001221
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001222 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001223
1224
1225def Time2Internaldate(date_time):
1226
Tim Peters07e99cb2001-01-14 23:47:14 +00001227 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001228
Tim Peters07e99cb2001-01-14 23:47:14 +00001229 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1230 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001231
Fred Drakedb519202002-01-05 17:17:09 +00001232 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001233 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001234 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001235 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001236 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001237 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001238 else:
1239 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001240
Tim Peters07e99cb2001-01-14 23:47:14 +00001241 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1242 if dt[0] == '0':
1243 dt = ' ' + dt[1:]
1244 if time.daylight and tt[-1]:
1245 zone = -time.altzone
1246 else:
1247 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001248 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001249
1250
1251
Guido van Rossum8c062211999-12-13 23:27:45 +00001252if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001253
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001254 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001255
Tim Peters07e99cb2001-01-14 23:47:14 +00001256 try:
1257 optlist, args = getopt.getopt(sys.argv[1:], 'd:')
1258 except getopt.error, val:
1259 pass
Guido van Rossum66d45132000-03-28 20:20:53 +00001260
Tim Peters07e99cb2001-01-14 23:47:14 +00001261 for opt,val in optlist:
1262 if opt == '-d':
1263 Debug = int(val)
Guido van Rossum66d45132000-03-28 20:20:53 +00001264
Tim Peters07e99cb2001-01-14 23:47:14 +00001265 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001266
Tim Peters07e99cb2001-01-14 23:47:14 +00001267 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001268
Tim Peters07e99cb2001-01-14 23:47:14 +00001269 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001270 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001271
Piers Laudere02f9042001-08-05 10:43:03 +00001272 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 +00001273 test_seq1 = (
1274 ('login', (USER, PASSWD)),
1275 ('create', ('/tmp/xxx 1',)),
1276 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1277 ('CREATE', ('/tmp/yyz 2',)),
1278 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1279 ('list', ('/tmp', 'yy*')),
1280 ('select', ('/tmp/yyz 2',)),
1281 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001282 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001283 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001284 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001285 ('expunge', ()),
1286 ('recent', ()),
1287 ('close', ()),
1288 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001289
Tim Peters07e99cb2001-01-14 23:47:14 +00001290 test_seq2 = (
1291 ('select', ()),
1292 ('response',('UIDVALIDITY',)),
1293 ('uid', ('SEARCH', 'ALL')),
1294 ('response', ('EXISTS',)),
1295 ('append', (None, None, None, test_mesg)),
1296 ('recent', ()),
1297 ('logout', ()),
1298 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001299
Tim Peters07e99cb2001-01-14 23:47:14 +00001300 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001301 M._mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001302 typ, dat = apply(getattr(M, cmd), args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001303 M._mesg('%s => %s %s' % (cmd, typ, dat))
Tim Peters07e99cb2001-01-14 23:47:14 +00001304 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001305
Tim Peters07e99cb2001-01-14 23:47:14 +00001306 try:
1307 M = IMAP4(host)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001308 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1309 M._mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001310
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 for cmd,args in test_seq1:
1312 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001313
Tim Peters07e99cb2001-01-14 23:47:14 +00001314 for ml in run('list', ('/tmp/', 'yy%')):
1315 mo = re.match(r'.*"([^"]+)"$', ml)
1316 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001317 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001318 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001319
Tim Peters07e99cb2001-01-14 23:47:14 +00001320 for cmd,args in test_seq2:
1321 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1324 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001325
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001326 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001327 if not uid: continue
1328 run('uid', ('FETCH', '%s' % uid[-1],
1329 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001330
Tim Peters07e99cb2001-01-14 23:47:14 +00001331 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001332
Tim Peters07e99cb2001-01-14 23:47:14 +00001333 except:
1334 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001335
Tim Peters07e99cb2001-01-14 23:47:14 +00001336 if not Debug:
1337 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001338If you would like to see debugging output,
1339try: %s -d5
1340''' % sys.argv[0]
1341
Tim Peters07e99cb2001-01-14 23:47:14 +00001342 raise