blob: 6a6886197c8fbef90e3f002ceb1f20c62ecd1bd8 [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.
Piers Laudere0273de2002-11-22 05:53:04 +000020# PROXYAUTH contributed by Rick Holbert <holbert.13@osu.edu> November 2002.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000021
Piers Laudere0273de2002-11-22 05:53:04 +000022__version__ = "2.54"
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000023
Piers Laudere0273de2002-11-22 05:53:04 +000024import binascii, os, random, re, socket, sys, time
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000025
Raymond Hettingerd8ea2e02002-10-30 06:20:37 +000026__all__ = ["IMAP4", "IMAP4_SSL", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000027 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000028
Tim Peters07e99cb2001-01-14 23:47:14 +000029# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000030
31CRLF = '\r\n'
32Debug = 0
33IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000034IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000035AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000036
Tim Peters07e99cb2001-01-14 23:47:14 +000037# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000038
39Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000040 # name valid states
41 'APPEND': ('AUTH', 'SELECTED'),
42 'AUTHENTICATE': ('NONAUTH',),
43 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
44 'CHECK': ('SELECTED',),
45 'CLOSE': ('SELECTED',),
46 'COPY': ('SELECTED',),
47 'CREATE': ('AUTH', 'SELECTED'),
48 'DELETE': ('AUTH', 'SELECTED'),
49 'EXAMINE': ('AUTH', 'SELECTED'),
50 'EXPUNGE': ('SELECTED',),
51 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000052 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000053 'GETQUOTA': ('AUTH', 'SELECTED'),
54 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000055 'LIST': ('AUTH', 'SELECTED'),
56 'LOGIN': ('NONAUTH',),
57 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
58 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000059 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000060 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000061 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000062 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000063 'RENAME': ('AUTH', 'SELECTED'),
64 'SEARCH': ('SELECTED',),
65 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000066 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000067 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000068 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000069 'STATUS': ('AUTH', 'SELECTED'),
70 'STORE': ('SELECTED',),
71 'SUBSCRIBE': ('AUTH', 'SELECTED'),
72 'UID': ('SELECTED',),
73 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
74 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000075
Tim Peters07e99cb2001-01-14 23:47:14 +000076# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000077
Guido van Rossumeda960a1998-06-18 14:24:28 +000078Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000079Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
80InternalDate = re.compile(r'.*INTERNALDATE "'
Tim Peters07e99cb2001-01-14 23:47:14 +000081 r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
82 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
83 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
84 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000085Literal = re.compile(r'.*{(?P<size>\d+)}$')
Piers Lauder533366b2003-04-29 23:58:08 +000086MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000087Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000088Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000089Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
90
91
92
93class IMAP4:
94
Tim Peters07e99cb2001-01-14 23:47:14 +000095 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000096
Tim Peters07e99cb2001-01-14 23:47:14 +000097 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000098
Tim Peters07e99cb2001-01-14 23:47:14 +000099 host - host's name (default: localhost);
100 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000101
Tim Peters07e99cb2001-01-14 23:47:14 +0000102 All IMAP4rev1 commands are supported by methods of the same
103 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000104
Tim Peters07e99cb2001-01-14 23:47:14 +0000105 All arguments to commands are converted to strings, except for
106 AUTHENTICATE, and the last argument to APPEND which is passed as
107 an IMAP4 literal. If necessary (the string contains any
108 non-printing characters or white-space and isn't enclosed with
109 either parentheses or double quotes) each string is quoted.
110 However, the 'password' argument to the LOGIN command is always
111 quoted. If you want to avoid having an argument string quoted
112 (eg: the 'flags' argument to STORE) then enclose the string in
113 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000114
Tim Peters07e99cb2001-01-14 23:47:14 +0000115 Each command returns a tuple: (type, [data, ...]) where 'type'
116 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000117 tagged response, or untagged results from command. Each 'data'
118 is either a string, or a tuple. If a tuple, then the first part
119 is the header of the response, and the second part contains
120 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000121
Tim Peters07e99cb2001-01-14 23:47:14 +0000122 Errors raise the exception class <instance>.error("<reason>").
123 IMAP4 server errors raise <instance>.abort("<reason>"),
124 which is a sub-class of 'error'. Mailbox status changes
125 from READ-WRITE to READ-ONLY raise the exception class
126 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000127
Tim Peters07e99cb2001-01-14 23:47:14 +0000128 "error" exceptions imply a program error.
129 "abort" exceptions imply the connection should be reset, and
130 the command re-tried.
131 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000132
Tim Peters07e99cb2001-01-14 23:47:14 +0000133 Note: to use this module, you must read the RFCs pertaining
134 to the IMAP4 protocol, as the semantics of the arguments to
135 each IMAP4 command are left to the invoker, not to mention
136 the results.
137 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000138
Tim Peters07e99cb2001-01-14 23:47:14 +0000139 class error(Exception): pass # Logical errors - debug required
140 class abort(error): pass # Service errors - close and retry
141 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000142
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000144
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 self.debug = Debug
147 self.state = 'LOGOUT'
148 self.literal = None # A literal argument to a command
149 self.tagged_commands = {} # Tagged commands awaiting response
150 self.untagged_responses = {} # {typ: [data, ...], ...}
151 self.continuation_response = '' # Last continuation response
152 self.is_readonly = None # READ-ONLY desired state
153 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000154
Tim Peters07e99cb2001-01-14 23:47:14 +0000155 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000156
Tim Peters07e99cb2001-01-14 23:47:14 +0000157 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000158
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 # Create unique tag for this session,
160 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 self.tagpre = Int2AP(random.randint(0, 31999))
163 self.tagre = re.compile(r'(?P<tag>'
164 + self.tagpre
165 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000166
Tim Peters07e99cb2001-01-14 23:47:14 +0000167 # Get server welcome message,
168 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000169
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000171 self._cmd_log_len = 10
172 self._cmd_log_idx = 0
173 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000174 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000175 self._mesg('imaplib version %s' % __version__)
176 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000177
Tim Peters07e99cb2001-01-14 23:47:14 +0000178 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000179 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000181 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 self.state = 'NONAUTH'
183 else:
184 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000185
Tim Peters07e99cb2001-01-14 23:47:14 +0000186 cap = 'CAPABILITY'
187 self._simple_command(cap)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000188 if not cap in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 raise self.error('no CAPABILITY response from server')
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000190 self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000191
Tim Peters07e99cb2001-01-14 23:47:14 +0000192 if __debug__:
193 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000194 self._mesg('CAPABILITIES: %s' % `self.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000195
Tim Peters07e99cb2001-01-14 23:47:14 +0000196 for version in AllowedVersions:
197 if not version in self.capabilities:
198 continue
199 self.PROTOCOL_VERSION = version
200 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000201
Tim Peters07e99cb2001-01-14 23:47:14 +0000202 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000203
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000204
Tim Peters07e99cb2001-01-14 23:47:14 +0000205 def __getattr__(self, attr):
206 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000207 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000208 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000209 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000210
211
212
Piers Lauder15e5d532001-07-20 10:52:06 +0000213 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000214
215
Piers Lauderf97b2d72002-06-05 22:31:57 +0000216 def open(self, host = '', port = IMAP4_PORT):
217 """Setup connection to remote server on "host:port"
218 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000219 This connection will be used by the routines:
220 read, readline, send, shutdown.
221 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000222 self.host = host
223 self.port = port
Tim Peters07e99cb2001-01-14 23:47:14 +0000224 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +0000225 self.sock.connect((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000226 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000227
228
Piers Lauder15e5d532001-07-20 10:52:06 +0000229 def read(self, size):
230 """Read 'size' bytes from remote."""
231 return self.file.read(size)
232
233
234 def readline(self):
235 """Read line from remote."""
236 return self.file.readline()
237
238
239 def send(self, data):
240 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000241 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000242
Piers Lauderf2d7d152002-02-22 01:15:17 +0000243
Piers Lauder15e5d532001-07-20 10:52:06 +0000244 def shutdown(self):
245 """Close I/O established in "open"."""
246 self.file.close()
247 self.sock.close()
248
249
250 def socket(self):
251 """Return socket instance used to connect to IMAP4 server.
252
253 socket = <instance>.socket()
254 """
255 return self.sock
256
257
258
259 # Utility methods
260
261
Tim Peters07e99cb2001-01-14 23:47:14 +0000262 def recent(self):
263 """Return most recent 'RECENT' responses if any exist,
264 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000265
Tim Peters07e99cb2001-01-14 23:47:14 +0000266 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000267
Tim Peters07e99cb2001-01-14 23:47:14 +0000268 'data' is None if no new messages,
269 else list of RECENT responses, most recent last.
270 """
271 name = 'RECENT'
272 typ, dat = self._untagged_response('OK', [None], name)
273 if dat[-1]:
274 return typ, dat
275 typ, dat = self.noop() # Prod server for response
276 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000277
278
Tim Peters07e99cb2001-01-14 23:47:14 +0000279 def response(self, code):
280 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000281
Tim Peters07e99cb2001-01-14 23:47:14 +0000282 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000283
Tim Peters07e99cb2001-01-14 23:47:14 +0000284 (code, [data]) = <instance>.response(code)
285 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000286 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000287
288
Guido van Rossum26367a01998-09-28 15:34:46 +0000289
Tim Peters07e99cb2001-01-14 23:47:14 +0000290 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000291
292
Tim Peters07e99cb2001-01-14 23:47:14 +0000293 def append(self, mailbox, flags, date_time, message):
294 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000295
Tim Peters07e99cb2001-01-14 23:47:14 +0000296 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000297
Tim Peters07e99cb2001-01-14 23:47:14 +0000298 All args except `message' can be None.
299 """
300 name = 'APPEND'
301 if not mailbox:
302 mailbox = 'INBOX'
303 if flags:
304 if (flags[0],flags[-1]) != ('(',')'):
305 flags = '(%s)' % flags
306 else:
307 flags = None
308 if date_time:
309 date_time = Time2Internaldate(date_time)
310 else:
311 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000312 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000313 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000314
315
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 def authenticate(self, mechanism, authobject):
317 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000318
Tim Peters07e99cb2001-01-14 23:47:14 +0000319 'mechanism' specifies which authentication mechanism is to
320 be used - it must appear in <instance>.capabilities in the
321 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000322
Tim Peters07e99cb2001-01-14 23:47:14 +0000323 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000324
Tim Peters07e99cb2001-01-14 23:47:14 +0000325 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000326
Tim Peters07e99cb2001-01-14 23:47:14 +0000327 It will be called to process server continuation responses.
328 It should return data that will be encoded and sent to server.
329 It should return None if the client abort response '*' should
330 be sent instead.
331 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000332 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000334 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000335 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000336 self.literal = _Authenticator(authobject).process
337 typ, dat = self._simple_command('AUTHENTICATE', mech)
338 if typ != 'OK':
339 raise self.error(dat[-1])
340 self.state = 'AUTH'
341 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000342
343
Tim Peters07e99cb2001-01-14 23:47:14 +0000344 def check(self):
345 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000346
Tim Peters07e99cb2001-01-14 23:47:14 +0000347 (typ, [data]) = <instance>.check()
348 """
349 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000350
351
Tim Peters07e99cb2001-01-14 23:47:14 +0000352 def close(self):
353 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000354
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 Deleted messages are removed from writable mailbox.
356 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000357
Tim Peters07e99cb2001-01-14 23:47:14 +0000358 (typ, [data]) = <instance>.close()
359 """
360 try:
361 typ, dat = self._simple_command('CLOSE')
362 finally:
363 self.state = 'AUTH'
364 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000365
366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 def copy(self, message_set, new_mailbox):
368 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000369
Tim Peters07e99cb2001-01-14 23:47:14 +0000370 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
371 """
372 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000373
374
Tim Peters07e99cb2001-01-14 23:47:14 +0000375 def create(self, mailbox):
376 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000377
Tim Peters07e99cb2001-01-14 23:47:14 +0000378 (typ, [data]) = <instance>.create(mailbox)
379 """
380 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000381
382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 def delete(self, mailbox):
384 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
Tim Peters07e99cb2001-01-14 23:47:14 +0000386 (typ, [data]) = <instance>.delete(mailbox)
387 """
388 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000389
390
Tim Peters07e99cb2001-01-14 23:47:14 +0000391 def expunge(self):
392 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000393
Tim Peters07e99cb2001-01-14 23:47:14 +0000394 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 'data' is list of 'EXPUNGE'd message numbers in order received.
399 """
400 name = 'EXPUNGE'
401 typ, dat = self._simple_command(name)
402 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 def fetch(self, message_set, message_parts):
406 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000409
Tim Peters07e99cb2001-01-14 23:47:14 +0000410 'message_parts' should be a string of selected parts
411 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 'data' are tuples of message part envelope and data.
414 """
415 name = 'FETCH'
416 typ, dat = self._simple_command(name, message_set, message_parts)
417 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000418
419
Piers Lauder15e5d532001-07-20 10:52:06 +0000420 def getacl(self, mailbox):
421 """Get the ACLs for a mailbox.
422
423 (typ, [data]) = <instance>.getacl(mailbox)
424 """
425 typ, dat = self._simple_command('GETACL', mailbox)
426 return self._untagged_response(typ, dat, 'ACL')
427
428
Piers Lauder3fca2912002-06-17 07:07:20 +0000429 def getquota(self, root):
430 """Get the quota root's resource usage and limits.
431
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000432 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000433
434 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000435 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000436 typ, dat = self._simple_command('GETQUOTA', root)
437 return self._untagged_response(typ, dat, 'QUOTA')
438
439
440 def getquotaroot(self, mailbox):
441 """Get the list of quota roots for the named mailbox.
442
443 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000444 """
Guido van Rossum41b71b22003-01-13 15:04:26 +0000445 typ, dat = self._simple_command('GETQUOTA', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000446 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
447 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000448 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000449
450
Tim Peters07e99cb2001-01-14 23:47:14 +0000451 def list(self, directory='""', pattern='*'):
452 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000453
Tim Peters07e99cb2001-01-14 23:47:14 +0000454 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000455
Tim Peters07e99cb2001-01-14 23:47:14 +0000456 'data' is list of LIST responses.
457 """
458 name = 'LIST'
459 typ, dat = self._simple_command(name, directory, pattern)
460 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 def login(self, user, password):
464 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
Tim Peters07e99cb2001-01-14 23:47:14 +0000466 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000467
Tim Peters07e99cb2001-01-14 23:47:14 +0000468 NB: 'password' will be quoted.
469 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000470 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
471 if typ != 'OK':
472 raise self.error(dat[-1])
473 self.state = 'AUTH'
474 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000475
476
Piers Laudere0273de2002-11-22 05:53:04 +0000477 def login_cram_md5(self, user, password):
478 """ Force use of CRAM-MD5 authentication.
479
480 (typ, [data]) = <instance>.login_cram_md5(user, password)
481 """
482 self.user, self.password = user, password
483 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
484
485
486 def _CRAM_MD5_AUTH(self, challenge):
487 """ Authobject to use with CRAM-MD5 authentication. """
488 import hmac
489 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
490
491
Tim Peters07e99cb2001-01-14 23:47:14 +0000492 def logout(self):
493 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000494
Tim Peters07e99cb2001-01-14 23:47:14 +0000495 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000496
Tim Peters07e99cb2001-01-14 23:47:14 +0000497 Returns server 'BYE' response.
498 """
499 self.state = 'LOGOUT'
500 try: typ, dat = self._simple_command('LOGOUT')
501 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000502 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000503 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000504 return 'BYE', self.untagged_responses['BYE']
505 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000506
507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 def lsub(self, directory='""', pattern='*'):
509 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000510
Tim Peters07e99cb2001-01-14 23:47:14 +0000511 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000512
Tim Peters07e99cb2001-01-14 23:47:14 +0000513 'data' are tuples of message part envelope and data.
514 """
515 name = 'LSUB'
516 typ, dat = self._simple_command(name, directory, pattern)
517 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000518
519
Piers Lauder15e5d532001-07-20 10:52:06 +0000520 def namespace(self):
521 """ Returns IMAP namespaces ala rfc2342
522
523 (typ, [data, ...]) = <instance>.namespace()
524 """
525 name = 'NAMESPACE'
526 typ, dat = self._simple_command(name)
527 return self._untagged_response(typ, dat, name)
528
529
Tim Peters07e99cb2001-01-14 23:47:14 +0000530 def noop(self):
531 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000532
Piers Laudere0273de2002-11-22 05:53:04 +0000533 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 """
535 if __debug__:
536 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000537 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000538 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000539
540
Tim Peters07e99cb2001-01-14 23:47:14 +0000541 def partial(self, message_num, message_part, start, length):
542 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000543
Tim Peters07e99cb2001-01-14 23:47:14 +0000544 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000545
Tim Peters07e99cb2001-01-14 23:47:14 +0000546 'data' is tuple of message part envelope and data.
547 """
548 name = 'PARTIAL'
549 typ, dat = self._simple_command(name, message_num, message_part, start, length)
550 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000551
552
Piers Laudere0273de2002-11-22 05:53:04 +0000553 def proxyauth(self, user):
554 """Assume authentication as "user".
555
556 Allows an authorised administrator to proxy into any user's
557 mailbox.
558
559 (typ, [data]) = <instance>.proxyauth(user)
560 """
561
562 name = 'PROXYAUTH'
563 return self._simple_command('PROXYAUTH', user)
564
565
Tim Peters07e99cb2001-01-14 23:47:14 +0000566 def rename(self, oldmailbox, newmailbox):
567 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000568
Piers Laudere0273de2002-11-22 05:53:04 +0000569 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000570 """
571 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000572
573
Tim Peters07e99cb2001-01-14 23:47:14 +0000574 def search(self, charset, *criteria):
575 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000576
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000577 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000578
Tim Peters07e99cb2001-01-14 23:47:14 +0000579 'data' is space separated list of matching message numbers.
580 """
581 name = 'SEARCH'
582 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000583 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000584 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000585 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000586 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000587
588
Tim Peters07e99cb2001-01-14 23:47:14 +0000589 def select(self, mailbox='INBOX', readonly=None):
590 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000591
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000593
Tim Peters07e99cb2001-01-14 23:47:14 +0000594 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000595
Tim Peters07e99cb2001-01-14 23:47:14 +0000596 'data' is count of messages in mailbox ('EXISTS' response).
597 """
598 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
599 self.untagged_responses = {} # Flush old responses.
600 self.is_readonly = readonly
Raymond Hettinger936654b2002-06-01 03:06:31 +0000601 if readonly is not None:
Tim Peters07e99cb2001-01-14 23:47:14 +0000602 name = 'EXAMINE'
603 else:
604 name = 'SELECT'
605 typ, dat = self._simple_command(name, mailbox)
606 if typ != 'OK':
607 self.state = 'AUTH' # Might have been 'SELECTED'
608 return typ, dat
609 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000610 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000611 and not readonly:
612 if __debug__:
613 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000614 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000615 raise self.readonly('%s is not writable' % mailbox)
616 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000617
618
Piers Lauder15e5d532001-07-20 10:52:06 +0000619 def setacl(self, mailbox, who, what):
620 """Set a mailbox acl.
621
622 (typ, [data]) = <instance>.create(mailbox, who, what)
623 """
624 return self._simple_command('SETACL', mailbox, who, what)
625
626
Piers Lauder3fca2912002-06-17 07:07:20 +0000627 def setquota(self, root, limits):
628 """Set the quota root's resource limits.
629
630 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000631 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000632 typ, dat = self._simple_command('SETQUOTA', root, limits)
633 return self._untagged_response(typ, dat, 'QUOTA')
634
635
Piers Lauder15e5d532001-07-20 10:52:06 +0000636 def sort(self, sort_criteria, charset, *search_criteria):
637 """IMAP4rev1 extension SORT command.
638
639 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
640 """
641 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000642 #if not name in self.capabilities: # Let the server decide!
643 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000644 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000645 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000646 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000647 return self._untagged_response(typ, dat, name)
648
649
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 def status(self, mailbox, names):
651 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000652
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 (typ, [data]) = <instance>.status(mailbox, names)
654 """
655 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000656 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000657 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000658 typ, dat = self._simple_command(name, mailbox, names)
659 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000660
661
Tim Peters07e99cb2001-01-14 23:47:14 +0000662 def store(self, message_set, command, flags):
663 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000664
Tim Peters07e99cb2001-01-14 23:47:14 +0000665 (typ, [data]) = <instance>.store(message_set, command, flags)
666 """
667 if (flags[0],flags[-1]) != ('(',')'):
668 flags = '(%s)' % flags # Avoid quoting the flags
669 typ, dat = self._simple_command('STORE', message_set, command, flags)
670 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000671
672
Tim Peters07e99cb2001-01-14 23:47:14 +0000673 def subscribe(self, mailbox):
674 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000675
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 (typ, [data]) = <instance>.subscribe(mailbox)
677 """
678 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000679
680
Tim Peters07e99cb2001-01-14 23:47:14 +0000681 def uid(self, command, *args):
682 """Execute "command arg ..." with messages identified by UID,
683 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000684
Tim Peters07e99cb2001-01-14 23:47:14 +0000685 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000686
Tim Peters07e99cb2001-01-14 23:47:14 +0000687 Returns response appropriate to 'command'.
688 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000689 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000690 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000691 raise self.error("Unknown IMAP4 UID command: %s" % command)
692 if self.state not in Commands[command]:
693 raise self.error('command %s illegal in state %s'
694 % (command, self.state))
695 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000696 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000697 if command in ('SEARCH', 'SORT'):
698 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000699 else:
700 name = 'FETCH'
701 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000702
703
Tim Peters07e99cb2001-01-14 23:47:14 +0000704 def unsubscribe(self, mailbox):
705 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000706
Tim Peters07e99cb2001-01-14 23:47:14 +0000707 (typ, [data]) = <instance>.unsubscribe(mailbox)
708 """
709 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000710
711
Tim Peters07e99cb2001-01-14 23:47:14 +0000712 def xatom(self, name, *args):
713 """Allow simple extension commands
714 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000715
Piers Lauder15e5d532001-07-20 10:52:06 +0000716 Assumes command is legal in current state.
717
Tim Peters07e99cb2001-01-14 23:47:14 +0000718 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000719
720 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000721 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000722 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000723 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000724 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000725 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000726 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000727 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000728
729
730
Tim Peters07e99cb2001-01-14 23:47:14 +0000731 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000732
733
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000735
Tim Peters07e99cb2001-01-14 23:47:14 +0000736 if dat is None: dat = ''
737 ur = self.untagged_responses
738 if __debug__:
739 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000740 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000741 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000742 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 ur[typ].append(dat)
744 else:
745 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000746
747
Tim Peters07e99cb2001-01-14 23:47:14 +0000748 def _check_bye(self):
749 bye = self.untagged_responses.get('BYE')
750 if bye:
751 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000752
753
Tim Peters07e99cb2001-01-14 23:47:14 +0000754 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 if self.state not in Commands[name]:
757 self.literal = None
758 raise self.error(
759 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000760
Tim Peters07e99cb2001-01-14 23:47:14 +0000761 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000762 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000763 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000764
Raymond Hettinger54f02222002-06-01 14:18:47 +0000765 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 and not self.is_readonly:
767 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000768
Tim Peters07e99cb2001-01-14 23:47:14 +0000769 tag = self._new_tag()
770 data = '%s %s' % (tag, name)
771 for arg in args:
772 if arg is None: continue
773 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000774
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 literal = self.literal
776 if literal is not None:
777 self.literal = None
778 if type(literal) is type(self._command):
779 literator = literal
780 else:
781 literator = None
782 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000783
Tim Peters07e99cb2001-01-14 23:47:14 +0000784 if __debug__:
785 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000786 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000787 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000788 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000789
Tim Peters07e99cb2001-01-14 23:47:14 +0000790 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000791 self.send('%s%s' % (data, CRLF))
792 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000793 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000794
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 if literal is None:
796 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000797
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 while 1:
799 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000800
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 while self._get_response():
802 if self.tagged_commands[tag]: # BAD/NO?
803 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000804
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000806
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 if literator:
808 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000809
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 if __debug__:
811 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000812 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000813
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000815 self.send(literal)
816 self.send(CRLF)
817 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000818 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000819
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 if not literator:
821 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000822
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000824
825
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 def _command_complete(self, name, tag):
827 self._check_bye()
828 try:
829 typ, data = self._get_tagged_response(tag)
830 except self.abort, val:
831 raise self.abort('command: %s => %s' % (name, val))
832 except self.error, val:
833 raise self.error('command: %s => %s' % (name, val))
834 self._check_bye()
835 if typ == 'BAD':
836 raise self.error('%s command error: %s %s' % (name, typ, data))
837 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
839
Tim Peters07e99cb2001-01-14 23:47:14 +0000840 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 # Read response and store.
843 #
844 # Returns None for continuation responses,
845 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000846
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000848
Tim Peters07e99cb2001-01-14 23:47:14 +0000849 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000850
Tim Peters07e99cb2001-01-14 23:47:14 +0000851 if self._match(self.tagre, resp):
852 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000853 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000855
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 typ = self.mo.group('type')
857 dat = self.mo.group('data')
858 self.tagged_commands[tag] = (typ, [dat])
859 else:
860 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000861
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 if not self._match(Untagged_response, resp):
865 if self._match(Untagged_status, resp):
866 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 if self.mo is None:
869 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 if self._match(Continuation, resp):
872 self.continuation_response = self.mo.group('data')
873 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000874
Tim Peters07e99cb2001-01-14 23:47:14 +0000875 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 typ = self.mo.group('type')
878 dat = self.mo.group('data')
879 if dat is None: dat = '' # Null untagged response
880 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000887
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000888 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 if __debug__:
890 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000891 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000892 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000893
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000895
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000897
Tim Peters07e99cb2001-01-14 23:47:14 +0000898 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
Tim Peters07e99cb2001-01-14 23:47:14 +0000902 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000905
Tim Peters07e99cb2001-01-14 23:47:14 +0000906 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
907 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000908
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 if __debug__:
910 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000911 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000912
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000914
915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000917
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 while 1:
919 result = self.tagged_commands[tag]
920 if result is not None:
921 del self.tagged_commands[tag]
922 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 # Some have reported "unexpected response" exceptions.
925 # Note that ignoring them here causes loops.
926 # Instead, send me details of the unexpected response and
927 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 try:
930 self._get_response()
931 except self.abort, val:
932 if __debug__:
933 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000934 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000936
937
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000939
Piers Lauder15e5d532001-07-20 10:52:06 +0000940 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 if not line:
942 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000943
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 line = line[:-2]
947 if __debug__:
948 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000949 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000951 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000953
954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 # Run compiled regular expression match method on 's'.
958 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000959
Tim Peters07e99cb2001-01-14 23:47:14 +0000960 self.mo = cre.match(s)
961 if __debug__:
962 if self.mo is not None and self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000963 self._mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000965
966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 tag = '%s%s' % (self.tagpre, self.tagnum)
970 self.tagnum = self.tagnum + 1
971 self.tagged_commands[tag] = None
972 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000973
974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 # Must quote command args if non-alphanumeric chars present,
978 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 if type(arg) is not type(''):
981 return arg
982 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
983 return arg
984 if self.mustquote.search(arg) is None:
985 return arg
986 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000987
988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000990
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000991 arg = arg.replace('\\', '\\\\')
992 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000995
996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
Guido van Rossum68468eb2003-02-27 20:14:51 +0000999 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001000
1001
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001003
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 if typ == 'NO':
1005 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001006 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001008 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001009 if __debug__:
1010 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001011 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001012 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001013
1014
Piers Lauderf2d7d152002-02-22 01:15:17 +00001015 if __debug__:
1016
1017 def _mesg(self, s, secs=None):
1018 if secs is None:
1019 secs = time.time()
1020 tm = time.strftime('%M:%S', time.localtime(secs))
1021 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1022 sys.stderr.flush()
1023
1024 def _dump_ur(self, dict):
1025 # Dump untagged responses (in `dict').
1026 l = dict.items()
1027 if not l: return
1028 t = '\n\t\t'
1029 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1030 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1031
1032 def _log(self, line):
1033 # Keep log of last `_cmd_log_len' interactions for debugging.
1034 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1035 self._cmd_log_idx += 1
1036 if self._cmd_log_idx >= self._cmd_log_len:
1037 self._cmd_log_idx = 0
1038
1039 def print_log(self):
1040 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1041 i, n = self._cmd_log_idx, self._cmd_log_len
1042 while n:
1043 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001044 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001045 except:
1046 pass
1047 i += 1
1048 if i >= self._cmd_log_len:
1049 i = 0
1050 n -= 1
1051
1052
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001053
Piers Laudera4f83132002-03-08 01:53:24 +00001054class IMAP4_SSL(IMAP4):
1055
1056 """IMAP4 client class over SSL connection
1057
Piers Lauder95f84952002-03-08 09:05:12 +00001058 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001059
1060 host - host's name (default: localhost);
1061 port - port number (default: standard IMAP4 SSL port).
1062 keyfile - PEM formatted file that contains your private key (default: None);
1063 certfile - PEM formatted certificate chain file (default: None);
1064
1065 for more documentation see the docstring of the parent class IMAP4.
1066 """
1067
1068
1069 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1070 self.keyfile = keyfile
1071 self.certfile = certfile
1072 IMAP4.__init__(self, host, port)
1073
1074
Piers Lauderf97b2d72002-06-05 22:31:57 +00001075 def open(self, host = '', port = IMAP4_SSL_PORT):
Piers Laudera4f83132002-03-08 01:53:24 +00001076 """Setup connection to remote server on "host:port".
Piers Lauderf97b2d72002-06-05 22:31:57 +00001077 (default: localhost:standard IMAP4 SSL port).
Piers Laudera4f83132002-03-08 01:53:24 +00001078 This connection will be used by the routines:
1079 read, readline, send, shutdown.
1080 """
Piers Lauderf97b2d72002-06-05 22:31:57 +00001081 self.host = host
1082 self.port = port
Piers Laudera4f83132002-03-08 01:53:24 +00001083 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +00001084 self.sock.connect((host, port))
1085 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001086
1087
1088 def read(self, size):
1089 """Read 'size' bytes from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001090 # sslobj.read() sometimes returns < size bytes
1091 data = self.sslobj.read(size)
1092 while len(data) < size:
Tim Petersc411dba2002-07-16 21:35:23 +00001093 data += self.sslobj.read(size-len(data))
Piers Lauder0c092932002-06-23 10:47:13 +00001094
1095 return data
Piers Laudera4f83132002-03-08 01:53:24 +00001096
1097
1098 def readline(self):
1099 """Read line from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001100 # NB: socket.ssl needs a "readline" method, or perhaps a "makefile" method.
Piers Laudera4f83132002-03-08 01:53:24 +00001101 line = ""
1102 while 1:
1103 char = self.sslobj.read(1)
1104 line += char
1105 if char == "\n": return line
1106
1107
1108 def send(self, data):
1109 """Send data to remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001110 # NB: socket.ssl needs a "sendall" method to match socket objects.
1111 bytes = len(data)
1112 while bytes > 0:
1113 sent = self.sslobj.write(data)
1114 if sent == bytes:
1115 break # avoid copy
1116 data = data[sent:]
1117 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001118
1119
1120 def shutdown(self):
1121 """Close I/O established in "open"."""
1122 self.sock.close()
1123
1124
1125 def socket(self):
1126 """Return socket instance used to connect to IMAP4 server.
1127
1128 socket = <instance>.socket()
1129 """
1130 return self.sock
1131
1132
1133 def ssl(self):
1134 """Return SSLObject instance used to communicate with the IMAP4 server.
1135
1136 ssl = <instance>.socket.ssl()
1137 """
1138 return self.sslobj
1139
1140
1141
Piers Laudere0273de2002-11-22 05:53:04 +00001142class IMAP4_stream(IMAP4):
1143
1144 """IMAP4 client class over a stream
1145
1146 Instantiate with: IMAP4_stream(command)
1147
1148 where "command" is a string that can be passed to os.popen2()
1149
1150 for more documentation see the docstring of the parent class IMAP4.
1151 """
1152
1153
1154 def __init__(self, command):
1155 self.command = command
1156 IMAP4.__init__(self)
1157
1158
1159 def open(self, host = None, port = None):
1160 """Setup a stream connection.
1161 This connection will be used by the routines:
1162 read, readline, send, shutdown.
1163 """
1164 self.host = None # For compatibility with parent class
1165 self.port = None
1166 self.sock = None
1167 self.file = None
1168 self.writefile, self.readfile = os.popen2(self.command)
1169
1170
1171 def read(self, size):
1172 """Read 'size' bytes from remote."""
1173 return self.readfile.read(size)
1174
1175
1176 def readline(self):
1177 """Read line from remote."""
1178 return self.readfile.readline()
1179
1180
1181 def send(self, data):
1182 """Send data to remote."""
1183 self.writefile.write(data)
1184 self.writefile.flush()
1185
1186
1187 def shutdown(self):
1188 """Close I/O established in "open"."""
1189 self.readfile.close()
1190 self.writefile.close()
1191
1192
1193
Guido van Rossumeda960a1998-06-18 14:24:28 +00001194class _Authenticator:
1195
Tim Peters07e99cb2001-01-14 23:47:14 +00001196 """Private class to provide en/decoding
1197 for base64-based authentication conversation.
1198 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001199
Tim Peters07e99cb2001-01-14 23:47:14 +00001200 def __init__(self, mechinst):
1201 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001202
Tim Peters07e99cb2001-01-14 23:47:14 +00001203 def process(self, data):
1204 ret = self.mech(self.decode(data))
1205 if ret is None:
1206 return '*' # Abort conversation
1207 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001208
Tim Peters07e99cb2001-01-14 23:47:14 +00001209 def encode(self, inp):
1210 #
1211 # Invoke binascii.b2a_base64 iteratively with
1212 # short even length buffers, strip the trailing
1213 # line feed from the result and append. "Even"
1214 # means a number that factors to both 6 and 8,
1215 # so when it gets to the end of the 8-bit input
1216 # there's no partial 6-bit output.
1217 #
1218 oup = ''
1219 while inp:
1220 if len(inp) > 48:
1221 t = inp[:48]
1222 inp = inp[48:]
1223 else:
1224 t = inp
1225 inp = ''
1226 e = binascii.b2a_base64(t)
1227 if e:
1228 oup = oup + e[:-1]
1229 return oup
1230
1231 def decode(self, inp):
1232 if not inp:
1233 return ''
1234 return binascii.a2b_base64(inp)
1235
Guido van Rossumeda960a1998-06-18 14:24:28 +00001236
1237
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001238Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001239 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001240
1241def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001242 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001243
Tim Peters07e99cb2001-01-14 23:47:14 +00001244 Returns Python time module tuple.
1245 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001246
Tim Peters07e99cb2001-01-14 23:47:14 +00001247 mo = InternalDate.match(resp)
1248 if not mo:
1249 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001250
Tim Peters07e99cb2001-01-14 23:47:14 +00001251 mon = Mon2num[mo.group('mon')]
1252 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001253
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001254 day = int(mo.group('day'))
1255 year = int(mo.group('year'))
1256 hour = int(mo.group('hour'))
1257 min = int(mo.group('min'))
1258 sec = int(mo.group('sec'))
1259 zoneh = int(mo.group('zoneh'))
1260 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001261
Tim Peters07e99cb2001-01-14 23:47:14 +00001262 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001263
Tim Peters07e99cb2001-01-14 23:47:14 +00001264 zone = (zoneh*60 + zonem)*60
1265 if zonen == '-':
1266 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001267
Tim Peters07e99cb2001-01-14 23:47:14 +00001268 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001269
Tim Peters07e99cb2001-01-14 23:47:14 +00001270 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001271
Tim Peters07e99cb2001-01-14 23:47:14 +00001272 # Following is necessary because the time module has no 'mkgmtime'.
1273 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001274
Tim Peters07e99cb2001-01-14 23:47:14 +00001275 lt = time.localtime(utc)
1276 if time.daylight and lt[-1]:
1277 zone = zone + time.altzone
1278 else:
1279 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001280
Tim Peters07e99cb2001-01-14 23:47:14 +00001281 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001282
1283
1284
1285def Int2AP(num):
1286
Tim Peters07e99cb2001-01-14 23:47:14 +00001287 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001288
Tim Peters07e99cb2001-01-14 23:47:14 +00001289 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1290 num = int(abs(num))
1291 while num:
1292 num, mod = divmod(num, 16)
1293 val = AP[mod] + val
1294 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001295
1296
1297
1298def ParseFlags(resp):
1299
Tim Peters07e99cb2001-01-14 23:47:14 +00001300 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001301
Tim Peters07e99cb2001-01-14 23:47:14 +00001302 mo = Flags.match(resp)
1303 if not mo:
1304 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001305
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001306 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001307
1308
1309def Time2Internaldate(date_time):
1310
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001312
Tim Peters07e99cb2001-01-14 23:47:14 +00001313 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1314 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001315
Fred Drakedb519202002-01-05 17:17:09 +00001316 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001317 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001318 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001319 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001320 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001321 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001322 else:
1323 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001324
Tim Peters07e99cb2001-01-14 23:47:14 +00001325 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1326 if dt[0] == '0':
1327 dt = ' ' + dt[1:]
1328 if time.daylight and tt[-1]:
1329 zone = -time.altzone
1330 else:
1331 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001332 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001333
1334
1335
Guido van Rossum8c062211999-12-13 23:27:45 +00001336if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Piers Laudere0273de2002-11-22 05:53:04 +00001338 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1339 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1340 # to test the IMAP4_stream class
1341
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001342 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001343
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001345 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001347 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001348
Piers Laudere0273de2002-11-22 05:53:04 +00001349 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001350 for opt,val in optlist:
1351 if opt == '-d':
1352 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001353 elif opt == '-s':
1354 stream_command = val
1355 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001356
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001358
Tim Peters07e99cb2001-01-14 23:47:14 +00001359 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001362 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001363
Piers Lauder47404ff2003-04-29 23:40:59 +00001364 test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)sdata...%(lf)s' % {'user':USER, 'lf':'\n'}
Tim Peters07e99cb2001-01-14 23:47:14 +00001365 test_seq1 = (
1366 ('login', (USER, PASSWD)),
1367 ('create', ('/tmp/xxx 1',)),
1368 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1369 ('CREATE', ('/tmp/yyz 2',)),
1370 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1371 ('list', ('/tmp', 'yy*')),
1372 ('select', ('/tmp/yyz 2',)),
1373 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001374 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001375 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001376 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001377 ('expunge', ()),
1378 ('recent', ()),
1379 ('close', ()),
1380 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001381
Tim Peters07e99cb2001-01-14 23:47:14 +00001382 test_seq2 = (
1383 ('select', ()),
1384 ('response',('UIDVALIDITY',)),
1385 ('uid', ('SEARCH', 'ALL')),
1386 ('response', ('EXISTS',)),
1387 ('append', (None, None, None, test_mesg)),
1388 ('recent', ()),
1389 ('logout', ()),
1390 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001391
Tim Peters07e99cb2001-01-14 23:47:14 +00001392 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001393 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001394 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001395 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001396 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001398
Tim Peters07e99cb2001-01-14 23:47:14 +00001399 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001400 if stream_command:
1401 M = IMAP4_stream(stream_command)
1402 else:
1403 M = IMAP4(host)
1404 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001405 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001406 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1407 M._mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001408
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 for cmd,args in test_seq1:
1410 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001411
Tim Peters07e99cb2001-01-14 23:47:14 +00001412 for ml in run('list', ('/tmp/', 'yy%')):
1413 mo = re.match(r'.*"([^"]+)"$', ml)
1414 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001415 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001416 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001417
Tim Peters07e99cb2001-01-14 23:47:14 +00001418 for cmd,args in test_seq2:
1419 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001420
Tim Peters07e99cb2001-01-14 23:47:14 +00001421 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1422 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001423
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001424 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001425 if not uid: continue
1426 run('uid', ('FETCH', '%s' % uid[-1],
1427 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001428
Tim Peters07e99cb2001-01-14 23:47:14 +00001429 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001430
Tim Peters07e99cb2001-01-14 23:47:14 +00001431 except:
1432 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001433
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 if not Debug:
1435 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001436If you would like to see debugging output,
1437try: %s -d5
1438''' % sys.argv[0]
1439
Tim Peters07e99cb2001-01-14 23:47:14 +00001440 raise