blob: d9166e08530317b6b4cecf013c5e94f9e52f697e [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()
Neal Norwitzb2071702003-06-29 04:21:43 +0000333 # XXX: shouldn't this code be removed, not commented out?
334 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000335 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000336 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000337 self.literal = _Authenticator(authobject).process
338 typ, dat = self._simple_command('AUTHENTICATE', mech)
339 if typ != 'OK':
340 raise self.error(dat[-1])
341 self.state = 'AUTH'
342 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000343
344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 def check(self):
346 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000347
Tim Peters07e99cb2001-01-14 23:47:14 +0000348 (typ, [data]) = <instance>.check()
349 """
350 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000351
352
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 def close(self):
354 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000355
Tim Peters07e99cb2001-01-14 23:47:14 +0000356 Deleted messages are removed from writable mailbox.
357 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000358
Tim Peters07e99cb2001-01-14 23:47:14 +0000359 (typ, [data]) = <instance>.close()
360 """
361 try:
362 typ, dat = self._simple_command('CLOSE')
363 finally:
364 self.state = 'AUTH'
365 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000366
367
Tim Peters07e99cb2001-01-14 23:47:14 +0000368 def copy(self, message_set, new_mailbox):
369 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000370
Tim Peters07e99cb2001-01-14 23:47:14 +0000371 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
372 """
373 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000374
375
Tim Peters07e99cb2001-01-14 23:47:14 +0000376 def create(self, mailbox):
377 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
Tim Peters07e99cb2001-01-14 23:47:14 +0000379 (typ, [data]) = <instance>.create(mailbox)
380 """
381 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000382
383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 def delete(self, mailbox):
385 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000386
Tim Peters07e99cb2001-01-14 23:47:14 +0000387 (typ, [data]) = <instance>.delete(mailbox)
388 """
389 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000390
391
Tim Peters07e99cb2001-01-14 23:47:14 +0000392 def expunge(self):
393 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 'data' is list of 'EXPUNGE'd message numbers in order received.
400 """
401 name = 'EXPUNGE'
402 typ, dat = self._simple_command(name)
403 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000404
405
Tim Peters07e99cb2001-01-14 23:47:14 +0000406 def fetch(self, message_set, message_parts):
407 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000408
Tim Peters07e99cb2001-01-14 23:47:14 +0000409 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 'message_parts' should be a string of selected parts
412 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000413
Tim Peters07e99cb2001-01-14 23:47:14 +0000414 'data' are tuples of message part envelope and data.
415 """
416 name = 'FETCH'
417 typ, dat = self._simple_command(name, message_set, message_parts)
418 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000419
420
Piers Lauder15e5d532001-07-20 10:52:06 +0000421 def getacl(self, mailbox):
422 """Get the ACLs for a mailbox.
423
424 (typ, [data]) = <instance>.getacl(mailbox)
425 """
426 typ, dat = self._simple_command('GETACL', mailbox)
427 return self._untagged_response(typ, dat, 'ACL')
428
429
Piers Lauder3fca2912002-06-17 07:07:20 +0000430 def getquota(self, root):
431 """Get the quota root's resource usage and limits.
432
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000433 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000434
435 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000436 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000437 typ, dat = self._simple_command('GETQUOTA', root)
438 return self._untagged_response(typ, dat, 'QUOTA')
439
440
441 def getquotaroot(self, mailbox):
442 """Get the list of quota roots for the named mailbox.
443
444 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000445 """
Guido van Rossum41b71b22003-01-13 15:04:26 +0000446 typ, dat = self._simple_command('GETQUOTA', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000447 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
448 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000449 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000450
451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 def list(self, directory='""', pattern='*'):
453 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000454
Tim Peters07e99cb2001-01-14 23:47:14 +0000455 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000456
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 'data' is list of LIST responses.
458 """
459 name = 'LIST'
460 typ, dat = self._simple_command(name, directory, pattern)
461 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000462
463
Tim Peters07e99cb2001-01-14 23:47:14 +0000464 def login(self, user, password):
465 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000466
Tim Peters07e99cb2001-01-14 23:47:14 +0000467 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000468
Tim Peters07e99cb2001-01-14 23:47:14 +0000469 NB: 'password' will be quoted.
470 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000471 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
472 if typ != 'OK':
473 raise self.error(dat[-1])
474 self.state = 'AUTH'
475 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000476
477
Piers Laudere0273de2002-11-22 05:53:04 +0000478 def login_cram_md5(self, user, password):
479 """ Force use of CRAM-MD5 authentication.
480
481 (typ, [data]) = <instance>.login_cram_md5(user, password)
482 """
483 self.user, self.password = user, password
484 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
485
486
487 def _CRAM_MD5_AUTH(self, challenge):
488 """ Authobject to use with CRAM-MD5 authentication. """
489 import hmac
490 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
491
492
Tim Peters07e99cb2001-01-14 23:47:14 +0000493 def logout(self):
494 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 Returns server 'BYE' response.
499 """
500 self.state = 'LOGOUT'
501 try: typ, dat = self._simple_command('LOGOUT')
502 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000503 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000504 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 return 'BYE', self.untagged_responses['BYE']
506 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000507
508
Tim Peters07e99cb2001-01-14 23:47:14 +0000509 def lsub(self, directory='""', pattern='*'):
510 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 'data' are tuples of message part envelope and data.
515 """
516 name = 'LSUB'
517 typ, dat = self._simple_command(name, directory, pattern)
518 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000519
520
Piers Lauder15e5d532001-07-20 10:52:06 +0000521 def namespace(self):
522 """ Returns IMAP namespaces ala rfc2342
523
524 (typ, [data, ...]) = <instance>.namespace()
525 """
526 name = 'NAMESPACE'
527 typ, dat = self._simple_command(name)
528 return self._untagged_response(typ, dat, name)
529
530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 def noop(self):
532 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000533
Piers Laudere0273de2002-11-22 05:53:04 +0000534 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000535 """
536 if __debug__:
537 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000538 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000539 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000540
541
Tim Peters07e99cb2001-01-14 23:47:14 +0000542 def partial(self, message_num, message_part, start, length):
543 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000544
Tim Peters07e99cb2001-01-14 23:47:14 +0000545 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000546
Tim Peters07e99cb2001-01-14 23:47:14 +0000547 'data' is tuple of message part envelope and data.
548 """
549 name = 'PARTIAL'
550 typ, dat = self._simple_command(name, message_num, message_part, start, length)
551 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000552
553
Piers Laudere0273de2002-11-22 05:53:04 +0000554 def proxyauth(self, user):
555 """Assume authentication as "user".
556
557 Allows an authorised administrator to proxy into any user's
558 mailbox.
559
560 (typ, [data]) = <instance>.proxyauth(user)
561 """
562
563 name = 'PROXYAUTH'
564 return self._simple_command('PROXYAUTH', user)
565
566
Tim Peters07e99cb2001-01-14 23:47:14 +0000567 def rename(self, oldmailbox, newmailbox):
568 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000569
Piers Laudere0273de2002-11-22 05:53:04 +0000570 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000571 """
572 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000573
574
Tim Peters07e99cb2001-01-14 23:47:14 +0000575 def search(self, charset, *criteria):
576 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000577
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000578 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000579
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 'data' is space separated list of matching message numbers.
581 """
582 name = 'SEARCH'
583 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000584 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000585 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000586 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000587 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000588
589
Tim Peters07e99cb2001-01-14 23:47:14 +0000590 def select(self, mailbox='INBOX', readonly=None):
591 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000592
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000596
Tim Peters07e99cb2001-01-14 23:47:14 +0000597 'data' is count of messages in mailbox ('EXISTS' response).
598 """
599 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
600 self.untagged_responses = {} # Flush old responses.
601 self.is_readonly = readonly
Raymond Hettinger936654b2002-06-01 03:06:31 +0000602 if readonly is not None:
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 name = 'EXAMINE'
604 else:
605 name = 'SELECT'
606 typ, dat = self._simple_command(name, mailbox)
607 if typ != 'OK':
608 self.state = 'AUTH' # Might have been 'SELECTED'
609 return typ, dat
610 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000611 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000612 and not readonly:
613 if __debug__:
614 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000615 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000616 raise self.readonly('%s is not writable' % mailbox)
617 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000618
619
Piers Lauder15e5d532001-07-20 10:52:06 +0000620 def setacl(self, mailbox, who, what):
621 """Set a mailbox acl.
622
623 (typ, [data]) = <instance>.create(mailbox, who, what)
624 """
625 return self._simple_command('SETACL', mailbox, who, what)
626
627
Piers Lauder3fca2912002-06-17 07:07:20 +0000628 def setquota(self, root, limits):
629 """Set the quota root's resource limits.
630
631 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000632 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000633 typ, dat = self._simple_command('SETQUOTA', root, limits)
634 return self._untagged_response(typ, dat, 'QUOTA')
635
636
Piers Lauder15e5d532001-07-20 10:52:06 +0000637 def sort(self, sort_criteria, charset, *search_criteria):
638 """IMAP4rev1 extension SORT command.
639
640 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
641 """
642 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000643 #if not name in self.capabilities: # Let the server decide!
644 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000645 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000646 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000647 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000648 return self._untagged_response(typ, dat, name)
649
650
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 def status(self, mailbox, names):
652 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000653
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 (typ, [data]) = <instance>.status(mailbox, names)
655 """
656 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000657 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000658 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 typ, dat = self._simple_command(name, mailbox, names)
660 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000661
662
Tim Peters07e99cb2001-01-14 23:47:14 +0000663 def store(self, message_set, command, flags):
664 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000665
Tim Peters07e99cb2001-01-14 23:47:14 +0000666 (typ, [data]) = <instance>.store(message_set, command, flags)
667 """
668 if (flags[0],flags[-1]) != ('(',')'):
669 flags = '(%s)' % flags # Avoid quoting the flags
670 typ, dat = self._simple_command('STORE', message_set, command, flags)
671 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000672
673
Tim Peters07e99cb2001-01-14 23:47:14 +0000674 def subscribe(self, mailbox):
675 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000676
Tim Peters07e99cb2001-01-14 23:47:14 +0000677 (typ, [data]) = <instance>.subscribe(mailbox)
678 """
679 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000680
681
Tim Peters07e99cb2001-01-14 23:47:14 +0000682 def uid(self, command, *args):
683 """Execute "command arg ..." with messages identified by UID,
684 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000685
Tim Peters07e99cb2001-01-14 23:47:14 +0000686 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000687
Tim Peters07e99cb2001-01-14 23:47:14 +0000688 Returns response appropriate to 'command'.
689 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000690 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000691 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000692 raise self.error("Unknown IMAP4 UID command: %s" % command)
693 if self.state not in Commands[command]:
694 raise self.error('command %s illegal in state %s'
695 % (command, self.state))
696 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000697 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000698 if command in ('SEARCH', 'SORT'):
699 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000700 else:
701 name = 'FETCH'
702 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000703
704
Tim Peters07e99cb2001-01-14 23:47:14 +0000705 def unsubscribe(self, mailbox):
706 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000707
Tim Peters07e99cb2001-01-14 23:47:14 +0000708 (typ, [data]) = <instance>.unsubscribe(mailbox)
709 """
710 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000711
712
Tim Peters07e99cb2001-01-14 23:47:14 +0000713 def xatom(self, name, *args):
714 """Allow simple extension commands
715 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000716
Piers Lauder15e5d532001-07-20 10:52:06 +0000717 Assumes command is legal in current state.
718
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000720
721 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000723 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000724 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000725 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000726 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000727 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000728 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000729
730
731
Tim Peters07e99cb2001-01-14 23:47:14 +0000732 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000733
734
Tim Peters07e99cb2001-01-14 23:47:14 +0000735 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 if dat is None: dat = ''
738 ur = self.untagged_responses
739 if __debug__:
740 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000741 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000742 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000743 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000744 ur[typ].append(dat)
745 else:
746 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000747
748
Tim Peters07e99cb2001-01-14 23:47:14 +0000749 def _check_bye(self):
750 bye = self.untagged_responses.get('BYE')
751 if bye:
752 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000753
754
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000756
Tim Peters07e99cb2001-01-14 23:47:14 +0000757 if self.state not in Commands[name]:
758 self.literal = None
759 raise self.error(
760 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000761
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000763 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000764 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000765
Raymond Hettinger54f02222002-06-01 14:18:47 +0000766 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000767 and not self.is_readonly:
768 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000769
Tim Peters07e99cb2001-01-14 23:47:14 +0000770 tag = self._new_tag()
771 data = '%s %s' % (tag, name)
772 for arg in args:
773 if arg is None: continue
774 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000775
Tim Peters07e99cb2001-01-14 23:47:14 +0000776 literal = self.literal
777 if literal is not None:
778 self.literal = None
779 if type(literal) is type(self._command):
780 literator = literal
781 else:
782 literator = None
783 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000784
Tim Peters07e99cb2001-01-14 23:47:14 +0000785 if __debug__:
786 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000787 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000789 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000790
Tim Peters07e99cb2001-01-14 23:47:14 +0000791 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000792 self.send('%s%s' % (data, CRLF))
793 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000795
Tim Peters07e99cb2001-01-14 23:47:14 +0000796 if literal is None:
797 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000798
Tim Peters07e99cb2001-01-14 23:47:14 +0000799 while 1:
800 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000801
Tim Peters07e99cb2001-01-14 23:47:14 +0000802 while self._get_response():
803 if self.tagged_commands[tag]: # BAD/NO?
804 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000805
Tim Peters07e99cb2001-01-14 23:47:14 +0000806 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000807
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 if literator:
809 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000810
Tim Peters07e99cb2001-01-14 23:47:14 +0000811 if __debug__:
812 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000813 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000814
Tim Peters07e99cb2001-01-14 23:47:14 +0000815 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000816 self.send(literal)
817 self.send(CRLF)
818 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000820
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 if not literator:
822 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000825
826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 def _command_complete(self, name, tag):
828 self._check_bye()
829 try:
830 typ, data = self._get_tagged_response(tag)
831 except self.abort, val:
832 raise self.abort('command: %s => %s' % (name, val))
833 except self.error, val:
834 raise self.error('command: %s => %s' % (name, val))
835 self._check_bye()
836 if typ == 'BAD':
837 raise self.error('%s command error: %s %s' % (name, typ, data))
838 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000839
840
Tim Peters07e99cb2001-01-14 23:47:14 +0000841 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 # Read response and store.
844 #
845 # Returns None for continuation responses,
846 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000847
Tim Peters07e99cb2001-01-14 23:47:14 +0000848 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000851
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 if self._match(self.tagre, resp):
853 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000854 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000855 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000856
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 typ = self.mo.group('type')
858 dat = self.mo.group('data')
859 self.tagged_commands[tag] = (typ, [dat])
860 else:
861 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000864
Tim Peters07e99cb2001-01-14 23:47:14 +0000865 if not self._match(Untagged_response, resp):
866 if self._match(Untagged_status, resp):
867 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 if self.mo is None:
870 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 if self._match(Continuation, resp):
873 self.continuation_response = self.mo.group('data')
874 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 typ = self.mo.group('type')
879 dat = self.mo.group('data')
880 if dat is None: dat = '' # Null untagged response
881 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000888
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000889 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000890 if __debug__:
891 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000892 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000893 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000894
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000898
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000902
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000906
Tim Peters07e99cb2001-01-14 23:47:14 +0000907 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
908 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 if __debug__:
911 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000912 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000913
Tim Peters07e99cb2001-01-14 23:47:14 +0000914 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
916
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 while 1:
920 result = self.tagged_commands[tag]
921 if result is not None:
922 del self.tagged_commands[tag]
923 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 # Some have reported "unexpected response" exceptions.
926 # Note that ignoring them here causes loops.
927 # Instead, send me details of the unexpected response and
928 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 try:
931 self._get_response()
932 except self.abort, val:
933 if __debug__:
934 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000935 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000937
938
Tim Peters07e99cb2001-01-14 23:47:14 +0000939 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
Piers Lauder15e5d532001-07-20 10:52:06 +0000941 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000942 if not line:
943 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 line = line[:-2]
948 if __debug__:
949 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000950 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000951 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000952 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000957
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 # Run compiled regular expression match method on 's'.
959 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000960
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 self.mo = cre.match(s)
962 if __debug__:
963 if self.mo is not None and self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000964 self._mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
967
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000969
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 tag = '%s%s' % (self.tagpre, self.tagnum)
971 self.tagnum = self.tagnum + 1
972 self.tagged_commands[tag] = None
973 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
975
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 # Must quote command args if non-alphanumeric chars present,
979 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 if type(arg) is not type(''):
982 return arg
983 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
984 return arg
985 if self.mustquote.search(arg) is None:
986 return arg
987 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000988
989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000991
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000992 arg = arg.replace('\\', '\\\\')
993 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000996
997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
Guido van Rossum68468eb2003-02-27 20:14:51 +00001000 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001001
1002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001004
Tim Peters07e99cb2001-01-14 23:47:14 +00001005 if typ == 'NO':
1006 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001007 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001008 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001009 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 if __debug__:
1011 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001012 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001014
1015
Piers Lauderf2d7d152002-02-22 01:15:17 +00001016 if __debug__:
1017
1018 def _mesg(self, s, secs=None):
1019 if secs is None:
1020 secs = time.time()
1021 tm = time.strftime('%M:%S', time.localtime(secs))
1022 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1023 sys.stderr.flush()
1024
1025 def _dump_ur(self, dict):
1026 # Dump untagged responses (in `dict').
1027 l = dict.items()
1028 if not l: return
1029 t = '\n\t\t'
1030 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1031 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1032
1033 def _log(self, line):
1034 # Keep log of last `_cmd_log_len' interactions for debugging.
1035 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1036 self._cmd_log_idx += 1
1037 if self._cmd_log_idx >= self._cmd_log_len:
1038 self._cmd_log_idx = 0
1039
1040 def print_log(self):
1041 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1042 i, n = self._cmd_log_idx, self._cmd_log_len
1043 while n:
1044 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001045 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001046 except:
1047 pass
1048 i += 1
1049 if i >= self._cmd_log_len:
1050 i = 0
1051 n -= 1
1052
1053
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001054
Piers Laudera4f83132002-03-08 01:53:24 +00001055class IMAP4_SSL(IMAP4):
1056
1057 """IMAP4 client class over SSL connection
1058
Piers Lauder95f84952002-03-08 09:05:12 +00001059 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001060
1061 host - host's name (default: localhost);
1062 port - port number (default: standard IMAP4 SSL port).
1063 keyfile - PEM formatted file that contains your private key (default: None);
1064 certfile - PEM formatted certificate chain file (default: None);
1065
1066 for more documentation see the docstring of the parent class IMAP4.
1067 """
1068
1069
1070 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1071 self.keyfile = keyfile
1072 self.certfile = certfile
1073 IMAP4.__init__(self, host, port)
1074
1075
Piers Lauderf97b2d72002-06-05 22:31:57 +00001076 def open(self, host = '', port = IMAP4_SSL_PORT):
Piers Laudera4f83132002-03-08 01:53:24 +00001077 """Setup connection to remote server on "host:port".
Piers Lauderf97b2d72002-06-05 22:31:57 +00001078 (default: localhost:standard IMAP4 SSL port).
Piers Laudera4f83132002-03-08 01:53:24 +00001079 This connection will be used by the routines:
1080 read, readline, send, shutdown.
1081 """
Piers Lauderf97b2d72002-06-05 22:31:57 +00001082 self.host = host
1083 self.port = port
Piers Laudera4f83132002-03-08 01:53:24 +00001084 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +00001085 self.sock.connect((host, port))
1086 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001087
1088
1089 def read(self, size):
1090 """Read 'size' bytes from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001091 # sslobj.read() sometimes returns < size bytes
1092 data = self.sslobj.read(size)
1093 while len(data) < size:
Tim Petersc411dba2002-07-16 21:35:23 +00001094 data += self.sslobj.read(size-len(data))
Piers Lauder0c092932002-06-23 10:47:13 +00001095
1096 return data
Piers Laudera4f83132002-03-08 01:53:24 +00001097
1098
1099 def readline(self):
1100 """Read line from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001101 # NB: socket.ssl needs a "readline" method, or perhaps a "makefile" method.
Piers Laudera4f83132002-03-08 01:53:24 +00001102 line = ""
1103 while 1:
1104 char = self.sslobj.read(1)
1105 line += char
1106 if char == "\n": return line
1107
1108
1109 def send(self, data):
1110 """Send data to remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001111 # NB: socket.ssl needs a "sendall" method to match socket objects.
1112 bytes = len(data)
1113 while bytes > 0:
1114 sent = self.sslobj.write(data)
1115 if sent == bytes:
1116 break # avoid copy
1117 data = data[sent:]
1118 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001119
1120
1121 def shutdown(self):
1122 """Close I/O established in "open"."""
1123 self.sock.close()
1124
1125
1126 def socket(self):
1127 """Return socket instance used to connect to IMAP4 server.
1128
1129 socket = <instance>.socket()
1130 """
1131 return self.sock
1132
1133
1134 def ssl(self):
1135 """Return SSLObject instance used to communicate with the IMAP4 server.
1136
1137 ssl = <instance>.socket.ssl()
1138 """
1139 return self.sslobj
1140
1141
1142
Piers Laudere0273de2002-11-22 05:53:04 +00001143class IMAP4_stream(IMAP4):
1144
1145 """IMAP4 client class over a stream
1146
1147 Instantiate with: IMAP4_stream(command)
1148
1149 where "command" is a string that can be passed to os.popen2()
1150
1151 for more documentation see the docstring of the parent class IMAP4.
1152 """
1153
1154
1155 def __init__(self, command):
1156 self.command = command
1157 IMAP4.__init__(self)
1158
1159
1160 def open(self, host = None, port = None):
1161 """Setup a stream connection.
1162 This connection will be used by the routines:
1163 read, readline, send, shutdown.
1164 """
1165 self.host = None # For compatibility with parent class
1166 self.port = None
1167 self.sock = None
1168 self.file = None
1169 self.writefile, self.readfile = os.popen2(self.command)
1170
1171
1172 def read(self, size):
1173 """Read 'size' bytes from remote."""
1174 return self.readfile.read(size)
1175
1176
1177 def readline(self):
1178 """Read line from remote."""
1179 return self.readfile.readline()
1180
1181
1182 def send(self, data):
1183 """Send data to remote."""
1184 self.writefile.write(data)
1185 self.writefile.flush()
1186
1187
1188 def shutdown(self):
1189 """Close I/O established in "open"."""
1190 self.readfile.close()
1191 self.writefile.close()
1192
1193
1194
Guido van Rossumeda960a1998-06-18 14:24:28 +00001195class _Authenticator:
1196
Tim Peters07e99cb2001-01-14 23:47:14 +00001197 """Private class to provide en/decoding
1198 for base64-based authentication conversation.
1199 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001200
Tim Peters07e99cb2001-01-14 23:47:14 +00001201 def __init__(self, mechinst):
1202 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001203
Tim Peters07e99cb2001-01-14 23:47:14 +00001204 def process(self, data):
1205 ret = self.mech(self.decode(data))
1206 if ret is None:
1207 return '*' # Abort conversation
1208 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001209
Tim Peters07e99cb2001-01-14 23:47:14 +00001210 def encode(self, inp):
1211 #
1212 # Invoke binascii.b2a_base64 iteratively with
1213 # short even length buffers, strip the trailing
1214 # line feed from the result and append. "Even"
1215 # means a number that factors to both 6 and 8,
1216 # so when it gets to the end of the 8-bit input
1217 # there's no partial 6-bit output.
1218 #
1219 oup = ''
1220 while inp:
1221 if len(inp) > 48:
1222 t = inp[:48]
1223 inp = inp[48:]
1224 else:
1225 t = inp
1226 inp = ''
1227 e = binascii.b2a_base64(t)
1228 if e:
1229 oup = oup + e[:-1]
1230 return oup
1231
1232 def decode(self, inp):
1233 if not inp:
1234 return ''
1235 return binascii.a2b_base64(inp)
1236
Guido van Rossumeda960a1998-06-18 14:24:28 +00001237
1238
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001239Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001240 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001241
1242def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001243 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001244
Tim Peters07e99cb2001-01-14 23:47:14 +00001245 Returns Python time module tuple.
1246 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001247
Tim Peters07e99cb2001-01-14 23:47:14 +00001248 mo = InternalDate.match(resp)
1249 if not mo:
1250 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001251
Tim Peters07e99cb2001-01-14 23:47:14 +00001252 mon = Mon2num[mo.group('mon')]
1253 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001254
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001255 day = int(mo.group('day'))
1256 year = int(mo.group('year'))
1257 hour = int(mo.group('hour'))
1258 min = int(mo.group('min'))
1259 sec = int(mo.group('sec'))
1260 zoneh = int(mo.group('zoneh'))
1261 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001262
Tim Peters07e99cb2001-01-14 23:47:14 +00001263 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001264
Tim Peters07e99cb2001-01-14 23:47:14 +00001265 zone = (zoneh*60 + zonem)*60
1266 if zonen == '-':
1267 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001268
Tim Peters07e99cb2001-01-14 23:47:14 +00001269 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001270
Tim Peters07e99cb2001-01-14 23:47:14 +00001271 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001272
Tim Peters07e99cb2001-01-14 23:47:14 +00001273 # Following is necessary because the time module has no 'mkgmtime'.
1274 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001275
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 lt = time.localtime(utc)
1277 if time.daylight and lt[-1]:
1278 zone = zone + time.altzone
1279 else:
1280 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001281
Tim Peters07e99cb2001-01-14 23:47:14 +00001282 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001283
1284
1285
1286def Int2AP(num):
1287
Tim Peters07e99cb2001-01-14 23:47:14 +00001288 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001289
Tim Peters07e99cb2001-01-14 23:47:14 +00001290 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1291 num = int(abs(num))
1292 while num:
1293 num, mod = divmod(num, 16)
1294 val = AP[mod] + val
1295 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001296
1297
1298
1299def ParseFlags(resp):
1300
Tim Peters07e99cb2001-01-14 23:47:14 +00001301 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001302
Tim Peters07e99cb2001-01-14 23:47:14 +00001303 mo = Flags.match(resp)
1304 if not mo:
1305 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001306
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001307 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001308
1309
1310def Time2Internaldate(date_time):
1311
Tim Peters07e99cb2001-01-14 23:47:14 +00001312 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001313
Tim Peters07e99cb2001-01-14 23:47:14 +00001314 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1315 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001316
Fred Drakedb519202002-01-05 17:17:09 +00001317 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001318 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001319 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001320 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001321 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001322 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001323 else:
1324 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001325
Tim Peters07e99cb2001-01-14 23:47:14 +00001326 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1327 if dt[0] == '0':
1328 dt = ' ' + dt[1:]
1329 if time.daylight and tt[-1]:
1330 zone = -time.altzone
1331 else:
1332 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001333 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001334
1335
1336
Guido van Rossum8c062211999-12-13 23:27:45 +00001337if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001338
Piers Laudere0273de2002-11-22 05:53:04 +00001339 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1340 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1341 # to test the IMAP4_stream class
1342
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001343 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001344
Tim Peters07e99cb2001-01-14 23:47:14 +00001345 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001346 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001348 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001349
Piers Laudere0273de2002-11-22 05:53:04 +00001350 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001351 for opt,val in optlist:
1352 if opt == '-d':
1353 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001354 elif opt == '-s':
1355 stream_command = val
1356 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001357
Tim Peters07e99cb2001-01-14 23:47:14 +00001358 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001359
Tim Peters07e99cb2001-01-14 23:47:14 +00001360 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001361
Tim Peters07e99cb2001-01-14 23:47:14 +00001362 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001363 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001364
Piers Lauder47404ff2003-04-29 23:40:59 +00001365 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 +00001366 test_seq1 = (
1367 ('login', (USER, PASSWD)),
1368 ('create', ('/tmp/xxx 1',)),
1369 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1370 ('CREATE', ('/tmp/yyz 2',)),
1371 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1372 ('list', ('/tmp', 'yy*')),
1373 ('select', ('/tmp/yyz 2',)),
1374 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001375 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001377 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 ('expunge', ()),
1379 ('recent', ()),
1380 ('close', ()),
1381 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001382
Tim Peters07e99cb2001-01-14 23:47:14 +00001383 test_seq2 = (
1384 ('select', ()),
1385 ('response',('UIDVALIDITY',)),
1386 ('uid', ('SEARCH', 'ALL')),
1387 ('response', ('EXISTS',)),
1388 ('append', (None, None, None, test_mesg)),
1389 ('recent', ()),
1390 ('logout', ()),
1391 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001392
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001394 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001395 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001396 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001397 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001398 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001399
Tim Peters07e99cb2001-01-14 23:47:14 +00001400 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001401 if stream_command:
1402 M = IMAP4_stream(stream_command)
1403 else:
1404 M = IMAP4(host)
1405 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001406 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001407 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1408 M._mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001409
Tim Peters07e99cb2001-01-14 23:47:14 +00001410 for cmd,args in test_seq1:
1411 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 for ml in run('list', ('/tmp/', 'yy%')):
1414 mo = re.match(r'.*"([^"]+)"$', ml)
1415 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001416 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001418
Tim Peters07e99cb2001-01-14 23:47:14 +00001419 for cmd,args in test_seq2:
1420 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001421
Tim Peters07e99cb2001-01-14 23:47:14 +00001422 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1423 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001424
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001425 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001426 if not uid: continue
1427 run('uid', ('FETCH', '%s' % uid[-1],
1428 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001431
Tim Peters07e99cb2001-01-14 23:47:14 +00001432 except:
1433 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001434
Tim Peters07e99cb2001-01-14 23:47:14 +00001435 if not Debug:
1436 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001437If you would like to see debugging output,
1438try: %s -d5
1439''' % sys.argv[0]
1440
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 raise