blob: 0112ddc4846451a2e1dab924343dc0fdb4841862 [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'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000072 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000073 'UID': ('SELECTED',),
74 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
75 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000076
Tim Peters07e99cb2001-01-14 23:47:14 +000077# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000078
Guido van Rossumeda960a1998-06-18 14:24:28 +000079Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000080Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
81InternalDate = re.compile(r'.*INTERNALDATE "'
Tim Peters07e99cb2001-01-14 23:47:14 +000082 r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
83 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
84 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
85 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000086Literal = re.compile(r'.*{(?P<size>\d+)}$')
Piers Lauder533366b2003-04-29 23:58:08 +000087MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000088Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000089Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000090Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
91
92
93
94class IMAP4:
95
Tim Peters07e99cb2001-01-14 23:47:14 +000096 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000097
Tim Peters07e99cb2001-01-14 23:47:14 +000098 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000099
Tim Peters07e99cb2001-01-14 23:47:14 +0000100 host - host's name (default: localhost);
101 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102
Tim Peters07e99cb2001-01-14 23:47:14 +0000103 All IMAP4rev1 commands are supported by methods of the same
104 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000105
Tim Peters07e99cb2001-01-14 23:47:14 +0000106 All arguments to commands are converted to strings, except for
107 AUTHENTICATE, and the last argument to APPEND which is passed as
108 an IMAP4 literal. If necessary (the string contains any
109 non-printing characters or white-space and isn't enclosed with
110 either parentheses or double quotes) each string is quoted.
111 However, the 'password' argument to the LOGIN command is always
112 quoted. If you want to avoid having an argument string quoted
113 (eg: the 'flags' argument to STORE) then enclose the string in
114 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000115
Tim Peters07e99cb2001-01-14 23:47:14 +0000116 Each command returns a tuple: (type, [data, ...]) where 'type'
117 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000118 tagged response, or untagged results from command. Each 'data'
119 is either a string, or a tuple. If a tuple, then the first part
120 is the header of the response, and the second part contains
121 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000122
Tim Peters07e99cb2001-01-14 23:47:14 +0000123 Errors raise the exception class <instance>.error("<reason>").
124 IMAP4 server errors raise <instance>.abort("<reason>"),
125 which is a sub-class of 'error'. Mailbox status changes
126 from READ-WRITE to READ-ONLY raise the exception class
127 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 "error" exceptions imply a program error.
130 "abort" exceptions imply the connection should be reset, and
131 the command re-tried.
132 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000133
Tim Peters07e99cb2001-01-14 23:47:14 +0000134 Note: to use this module, you must read the RFCs pertaining
135 to the IMAP4 protocol, as the semantics of the arguments to
136 each IMAP4 command are left to the invoker, not to mention
137 the results.
138 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000139
Tim Peters07e99cb2001-01-14 23:47:14 +0000140 class error(Exception): pass # Logical errors - debug required
141 class abort(error): pass # Service errors - close and retry
142 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000143
Tim Peters07e99cb2001-01-14 23:47:14 +0000144 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000145
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000147 self.debug = Debug
148 self.state = 'LOGOUT'
149 self.literal = None # A literal argument to a command
150 self.tagged_commands = {} # Tagged commands awaiting response
151 self.untagged_responses = {} # {typ: [data, ...], ...}
152 self.continuation_response = '' # Last continuation response
153 self.is_readonly = None # READ-ONLY desired state
154 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000155
Tim Peters07e99cb2001-01-14 23:47:14 +0000156 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000157
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 # Create unique tag for this session,
161 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000162
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 self.tagpre = Int2AP(random.randint(0, 31999))
164 self.tagre = re.compile(r'(?P<tag>'
165 + self.tagpre
166 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000167
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 # Get server welcome message,
169 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000170
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000172 self._cmd_log_len = 10
173 self._cmd_log_idx = 0
174 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000175 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000176 self._mesg('imaplib version %s' % __version__)
177 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000178
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000180 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000181 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000182 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 self.state = 'NONAUTH'
184 else:
185 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000186
Tim Peters07e99cb2001-01-14 23:47:14 +0000187 cap = 'CAPABILITY'
188 self._simple_command(cap)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000189 if not cap in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000190 raise self.error('no CAPABILITY response from server')
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000191 self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000192
Tim Peters07e99cb2001-01-14 23:47:14 +0000193 if __debug__:
194 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000195 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000196
Tim Peters07e99cb2001-01-14 23:47:14 +0000197 for version in AllowedVersions:
198 if not version in self.capabilities:
199 continue
200 self.PROTOCOL_VERSION = version
201 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000202
Tim Peters07e99cb2001-01-14 23:47:14 +0000203 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000204
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000205
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 def __getattr__(self, attr):
207 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000208 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000209 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000211
212
213
Piers Lauder15e5d532001-07-20 10:52:06 +0000214 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000215
216
Piers Lauderf97b2d72002-06-05 22:31:57 +0000217 def open(self, host = '', port = IMAP4_PORT):
218 """Setup connection to remote server on "host:port"
219 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000220 This connection will be used by the routines:
221 read, readline, send, shutdown.
222 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000223 self.host = host
224 self.port = port
Tim Peters07e99cb2001-01-14 23:47:14 +0000225 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +0000226 self.sock.connect((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000227 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000228
229
Piers Lauder15e5d532001-07-20 10:52:06 +0000230 def read(self, size):
231 """Read 'size' bytes from remote."""
232 return self.file.read(size)
233
234
235 def readline(self):
236 """Read line from remote."""
237 return self.file.readline()
238
239
240 def send(self, data):
241 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000242 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000243
Piers Lauderf2d7d152002-02-22 01:15:17 +0000244
Piers Lauder15e5d532001-07-20 10:52:06 +0000245 def shutdown(self):
246 """Close I/O established in "open"."""
247 self.file.close()
248 self.sock.close()
249
250
251 def socket(self):
252 """Return socket instance used to connect to IMAP4 server.
253
254 socket = <instance>.socket()
255 """
256 return self.sock
257
258
259
260 # Utility methods
261
262
Tim Peters07e99cb2001-01-14 23:47:14 +0000263 def recent(self):
264 """Return most recent 'RECENT' responses if any exist,
265 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000266
Tim Peters07e99cb2001-01-14 23:47:14 +0000267 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000268
Tim Peters07e99cb2001-01-14 23:47:14 +0000269 'data' is None if no new messages,
270 else list of RECENT responses, most recent last.
271 """
272 name = 'RECENT'
273 typ, dat = self._untagged_response('OK', [None], name)
274 if dat[-1]:
275 return typ, dat
276 typ, dat = self.noop() # Prod server for response
277 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000278
279
Tim Peters07e99cb2001-01-14 23:47:14 +0000280 def response(self, code):
281 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000282
Tim Peters07e99cb2001-01-14 23:47:14 +0000283 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000284
Tim Peters07e99cb2001-01-14 23:47:14 +0000285 (code, [data]) = <instance>.response(code)
286 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000287 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000288
289
Guido van Rossum26367a01998-09-28 15:34:46 +0000290
Tim Peters07e99cb2001-01-14 23:47:14 +0000291 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000292
293
Tim Peters07e99cb2001-01-14 23:47:14 +0000294 def append(self, mailbox, flags, date_time, message):
295 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000296
Tim Peters07e99cb2001-01-14 23:47:14 +0000297 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000298
Tim Peters07e99cb2001-01-14 23:47:14 +0000299 All args except `message' can be None.
300 """
301 name = 'APPEND'
302 if not mailbox:
303 mailbox = 'INBOX'
304 if flags:
305 if (flags[0],flags[-1]) != ('(',')'):
306 flags = '(%s)' % flags
307 else:
308 flags = None
309 if date_time:
310 date_time = Time2Internaldate(date_time)
311 else:
312 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000313 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000315
316
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 def authenticate(self, mechanism, authobject):
318 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000319
Tim Peters07e99cb2001-01-14 23:47:14 +0000320 'mechanism' specifies which authentication mechanism is to
321 be used - it must appear in <instance>.capabilities in the
322 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000323
Tim Peters07e99cb2001-01-14 23:47:14 +0000324 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000325
Tim Peters07e99cb2001-01-14 23:47:14 +0000326 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000327
Tim Peters07e99cb2001-01-14 23:47:14 +0000328 It will be called to process server continuation responses.
329 It should return data that will be encoded and sent to server.
330 It should return None if the client abort response '*' should
331 be sent instead.
332 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000333 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000334 # XXX: shouldn't this code be removed, not commented out?
335 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000336 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000337 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000338 self.literal = _Authenticator(authobject).process
339 typ, dat = self._simple_command('AUTHENTICATE', mech)
340 if typ != 'OK':
341 raise self.error(dat[-1])
342 self.state = 'AUTH'
343 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000344
345
Tim Peters07e99cb2001-01-14 23:47:14 +0000346 def check(self):
347 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000348
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 (typ, [data]) = <instance>.check()
350 """
351 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000352
353
Tim Peters07e99cb2001-01-14 23:47:14 +0000354 def close(self):
355 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000356
Tim Peters07e99cb2001-01-14 23:47:14 +0000357 Deleted messages are removed from writable mailbox.
358 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000359
Tim Peters07e99cb2001-01-14 23:47:14 +0000360 (typ, [data]) = <instance>.close()
361 """
362 try:
363 typ, dat = self._simple_command('CLOSE')
364 finally:
365 self.state = 'AUTH'
366 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000367
368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 def copy(self, message_set, new_mailbox):
370 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
373 """
374 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000375
376
Tim Peters07e99cb2001-01-14 23:47:14 +0000377 def create(self, mailbox):
378 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 (typ, [data]) = <instance>.create(mailbox)
381 """
382 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000383
384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 def delete(self, mailbox):
386 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 (typ, [data]) = <instance>.delete(mailbox)
389 """
390 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000391
392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 def expunge(self):
394 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
Tim Peters07e99cb2001-01-14 23:47:14 +0000400 'data' is list of 'EXPUNGE'd message numbers in order received.
401 """
402 name = 'EXPUNGE'
403 typ, dat = self._simple_command(name)
404 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000405
406
Tim Peters07e99cb2001-01-14 23:47:14 +0000407 def fetch(self, message_set, message_parts):
408 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000409
Tim Peters07e99cb2001-01-14 23:47:14 +0000410 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 'message_parts' should be a string of selected parts
413 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000414
Tim Peters07e99cb2001-01-14 23:47:14 +0000415 'data' are tuples of message part envelope and data.
416 """
417 name = 'FETCH'
418 typ, dat = self._simple_command(name, message_set, message_parts)
419 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000420
421
Piers Lauder15e5d532001-07-20 10:52:06 +0000422 def getacl(self, mailbox):
423 """Get the ACLs for a mailbox.
424
425 (typ, [data]) = <instance>.getacl(mailbox)
426 """
427 typ, dat = self._simple_command('GETACL', mailbox)
428 return self._untagged_response(typ, dat, 'ACL')
429
430
Piers Lauder3fca2912002-06-17 07:07:20 +0000431 def getquota(self, root):
432 """Get the quota root's resource usage and limits.
433
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000434 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000435
436 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000437 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000438 typ, dat = self._simple_command('GETQUOTA', root)
439 return self._untagged_response(typ, dat, 'QUOTA')
440
441
442 def getquotaroot(self, mailbox):
443 """Get the list of quota roots for the named mailbox.
444
445 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000446 """
Guido van Rossum41b71b22003-01-13 15:04:26 +0000447 typ, dat = self._simple_command('GETQUOTA', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000448 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
449 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000450 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000451
452
Tim Peters07e99cb2001-01-14 23:47:14 +0000453 def list(self, directory='""', pattern='*'):
454 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000455
Tim Peters07e99cb2001-01-14 23:47:14 +0000456 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000457
Tim Peters07e99cb2001-01-14 23:47:14 +0000458 'data' is list of LIST responses.
459 """
460 name = 'LIST'
461 typ, dat = self._simple_command(name, directory, pattern)
462 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000463
464
Tim Peters07e99cb2001-01-14 23:47:14 +0000465 def login(self, user, password):
466 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000467
Tim Peters07e99cb2001-01-14 23:47:14 +0000468 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000469
Tim Peters07e99cb2001-01-14 23:47:14 +0000470 NB: 'password' will be quoted.
471 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000472 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
473 if typ != 'OK':
474 raise self.error(dat[-1])
475 self.state = 'AUTH'
476 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000477
478
Piers Laudere0273de2002-11-22 05:53:04 +0000479 def login_cram_md5(self, user, password):
480 """ Force use of CRAM-MD5 authentication.
481
482 (typ, [data]) = <instance>.login_cram_md5(user, password)
483 """
484 self.user, self.password = user, password
485 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
486
487
488 def _CRAM_MD5_AUTH(self, challenge):
489 """ Authobject to use with CRAM-MD5 authentication. """
490 import hmac
491 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
492
493
Tim Peters07e99cb2001-01-14 23:47:14 +0000494 def logout(self):
495 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000496
Tim Peters07e99cb2001-01-14 23:47:14 +0000497 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000498
Tim Peters07e99cb2001-01-14 23:47:14 +0000499 Returns server 'BYE' response.
500 """
501 self.state = 'LOGOUT'
502 try: typ, dat = self._simple_command('LOGOUT')
503 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000504 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000505 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000506 return 'BYE', self.untagged_responses['BYE']
507 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000508
509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 def lsub(self, directory='""', pattern='*'):
511 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000512
Tim Peters07e99cb2001-01-14 23:47:14 +0000513 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 'data' are tuples of message part envelope and data.
516 """
517 name = 'LSUB'
518 typ, dat = self._simple_command(name, directory, pattern)
519 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000520
521
Piers Lauder15e5d532001-07-20 10:52:06 +0000522 def namespace(self):
523 """ Returns IMAP namespaces ala rfc2342
524
525 (typ, [data, ...]) = <instance>.namespace()
526 """
527 name = 'NAMESPACE'
528 typ, dat = self._simple_command(name)
529 return self._untagged_response(typ, dat, name)
530
531
Tim Peters07e99cb2001-01-14 23:47:14 +0000532 def noop(self):
533 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000534
Piers Laudere0273de2002-11-22 05:53:04 +0000535 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 """
537 if __debug__:
538 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000539 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000540 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000541
542
Tim Peters07e99cb2001-01-14 23:47:14 +0000543 def partial(self, message_num, message_part, start, length):
544 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000545
Tim Peters07e99cb2001-01-14 23:47:14 +0000546 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000547
Tim Peters07e99cb2001-01-14 23:47:14 +0000548 'data' is tuple of message part envelope and data.
549 """
550 name = 'PARTIAL'
551 typ, dat = self._simple_command(name, message_num, message_part, start, length)
552 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000553
554
Piers Laudere0273de2002-11-22 05:53:04 +0000555 def proxyauth(self, user):
556 """Assume authentication as "user".
557
558 Allows an authorised administrator to proxy into any user's
559 mailbox.
560
561 (typ, [data]) = <instance>.proxyauth(user)
562 """
563
564 name = 'PROXYAUTH'
565 return self._simple_command('PROXYAUTH', user)
566
567
Tim Peters07e99cb2001-01-14 23:47:14 +0000568 def rename(self, oldmailbox, newmailbox):
569 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000570
Piers Laudere0273de2002-11-22 05:53:04 +0000571 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000572 """
573 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000574
575
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 def search(self, charset, *criteria):
577 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000578
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000579 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000580
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 'data' is space separated list of matching message numbers.
582 """
583 name = 'SEARCH'
584 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000585 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000586 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000587 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000589
590
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 def select(self, mailbox='INBOX', readonly=None):
592 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000593
Tim Peters07e99cb2001-01-14 23:47:14 +0000594 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000595
Tim Peters07e99cb2001-01-14 23:47:14 +0000596 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000597
Tim Peters07e99cb2001-01-14 23:47:14 +0000598 'data' is count of messages in mailbox ('EXISTS' response).
599 """
600 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
601 self.untagged_responses = {} # Flush old responses.
602 self.is_readonly = readonly
Raymond Hettinger936654b2002-06-01 03:06:31 +0000603 if readonly is not None:
Tim Peters07e99cb2001-01-14 23:47:14 +0000604 name = 'EXAMINE'
605 else:
606 name = 'SELECT'
607 typ, dat = self._simple_command(name, mailbox)
608 if typ != 'OK':
609 self.state = 'AUTH' # Might have been 'SELECTED'
610 return typ, dat
611 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000612 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 and not readonly:
614 if __debug__:
615 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000616 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 raise self.readonly('%s is not writable' % mailbox)
618 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000619
620
Piers Lauder15e5d532001-07-20 10:52:06 +0000621 def setacl(self, mailbox, who, what):
622 """Set a mailbox acl.
623
624 (typ, [data]) = <instance>.create(mailbox, who, what)
625 """
626 return self._simple_command('SETACL', mailbox, who, what)
627
628
Piers Lauder3fca2912002-06-17 07:07:20 +0000629 def setquota(self, root, limits):
630 """Set the quota root's resource limits.
631
632 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000633 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000634 typ, dat = self._simple_command('SETQUOTA', root, limits)
635 return self._untagged_response(typ, dat, 'QUOTA')
636
637
Piers Lauder15e5d532001-07-20 10:52:06 +0000638 def sort(self, sort_criteria, charset, *search_criteria):
639 """IMAP4rev1 extension SORT command.
640
641 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
642 """
643 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000644 #if not name in self.capabilities: # Let the server decide!
645 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000646 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000647 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000648 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000649 return self._untagged_response(typ, dat, name)
650
651
Tim Peters07e99cb2001-01-14 23:47:14 +0000652 def status(self, mailbox, names):
653 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 (typ, [data]) = <instance>.status(mailbox, names)
656 """
657 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000658 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000659 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 typ, dat = self._simple_command(name, mailbox, names)
661 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000662
663
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 def store(self, message_set, command, flags):
665 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000666
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 (typ, [data]) = <instance>.store(message_set, command, flags)
668 """
669 if (flags[0],flags[-1]) != ('(',')'):
670 flags = '(%s)' % flags # Avoid quoting the flags
671 typ, dat = self._simple_command('STORE', message_set, command, flags)
672 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000673
674
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 def subscribe(self, mailbox):
676 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000677
Tim Peters07e99cb2001-01-14 23:47:14 +0000678 (typ, [data]) = <instance>.subscribe(mailbox)
679 """
680 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000681
682
Martin v. Löwisd8921372003-11-10 06:44:44 +0000683 def thread(self, threading_algorithm, charset, *search_criteria):
684 """IMAPrev1 extension THREAD command.
685
686 (type, [data]) = <instance>.thread(threading_alogrithm, charset, search_criteria, ...)
687 """
688 name = 'THREAD'
689 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
690 return self._untagged_response(typ, dat, name)
691
692
Tim Peters07e99cb2001-01-14 23:47:14 +0000693 def uid(self, command, *args):
694 """Execute "command arg ..." with messages identified by UID,
695 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000696
Tim Peters07e99cb2001-01-14 23:47:14 +0000697 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000698
Tim Peters07e99cb2001-01-14 23:47:14 +0000699 Returns response appropriate to 'command'.
700 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000701 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000702 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000703 raise self.error("Unknown IMAP4 UID command: %s" % command)
704 if self.state not in Commands[command]:
705 raise self.error('command %s illegal in state %s'
706 % (command, self.state))
707 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000708 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000709 if command in ('SEARCH', 'SORT'):
710 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 else:
712 name = 'FETCH'
713 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000714
715
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 def unsubscribe(self, mailbox):
717 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000718
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 (typ, [data]) = <instance>.unsubscribe(mailbox)
720 """
721 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000722
723
Tim Peters07e99cb2001-01-14 23:47:14 +0000724 def xatom(self, name, *args):
725 """Allow simple extension commands
726 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
Piers Lauder15e5d532001-07-20 10:52:06 +0000728 Assumes command is legal in current state.
729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000731
732 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000734 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000735 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000736 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000737 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000738 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000739 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000740
741
742
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000744
745
Tim Peters07e99cb2001-01-14 23:47:14 +0000746 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000747
Tim Peters07e99cb2001-01-14 23:47:14 +0000748 if dat is None: dat = ''
749 ur = self.untagged_responses
750 if __debug__:
751 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000752 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000754 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 ur[typ].append(dat)
756 else:
757 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000758
759
Tim Peters07e99cb2001-01-14 23:47:14 +0000760 def _check_bye(self):
761 bye = self.untagged_responses.get('BYE')
762 if bye:
763 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000764
765
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000767
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 if self.state not in Commands[name]:
769 self.literal = None
770 raise self.error(
771 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000772
Tim Peters07e99cb2001-01-14 23:47:14 +0000773 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000774 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000776
Raymond Hettinger54f02222002-06-01 14:18:47 +0000777 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000778 and not self.is_readonly:
779 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000780
Tim Peters07e99cb2001-01-14 23:47:14 +0000781 tag = self._new_tag()
782 data = '%s %s' % (tag, name)
783 for arg in args:
784 if arg is None: continue
785 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000786
Tim Peters07e99cb2001-01-14 23:47:14 +0000787 literal = self.literal
788 if literal is not None:
789 self.literal = None
790 if type(literal) is type(self._command):
791 literator = literal
792 else:
793 literator = None
794 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000795
Tim Peters07e99cb2001-01-14 23:47:14 +0000796 if __debug__:
797 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000798 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000799 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000800 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000801
Tim Peters07e99cb2001-01-14 23:47:14 +0000802 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000803 self.send('%s%s' % (data, CRLF))
804 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000806
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 if literal is None:
808 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000809
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 while 1:
811 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 while self._get_response():
814 if self.tagged_commands[tag]: # BAD/NO?
815 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 if literator:
820 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000821
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 if __debug__:
823 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000824 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000825
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000827 self.send(literal)
828 self.send(CRLF)
829 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 if not literator:
833 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000836
837
Tim Peters07e99cb2001-01-14 23:47:14 +0000838 def _command_complete(self, name, tag):
839 self._check_bye()
840 try:
841 typ, data = self._get_tagged_response(tag)
842 except self.abort, val:
843 raise self.abort('command: %s => %s' % (name, val))
844 except self.error, val:
845 raise self.error('command: %s => %s' % (name, val))
846 self._check_bye()
847 if typ == 'BAD':
848 raise self.error('%s command error: %s %s' % (name, typ, data))
849 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000850
851
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000853
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 # Read response and store.
855 #
856 # Returns None for continuation responses,
857 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000858
Tim Peters07e99cb2001-01-14 23:47:14 +0000859 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 if self._match(self.tagre, resp):
864 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000865 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 typ = self.mo.group('type')
869 dat = self.mo.group('data')
870 self.tagged_commands[tag] = (typ, [dat])
871 else:
872 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000873
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 if not self._match(Untagged_response, resp):
877 if self._match(Untagged_status, resp):
878 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 if self.mo is None:
881 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 if self._match(Continuation, resp):
884 self.continuation_response = self.mo.group('data')
885 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 typ = self.mo.group('type')
890 dat = self.mo.group('data')
891 if dat is None: dat = '' # Null untagged response
892 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000893
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000895
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000897
Tim Peters07e99cb2001-01-14 23:47:14 +0000898 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000900 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 if __debug__:
902 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000903 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000904 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000905
Tim Peters07e99cb2001-01-14 23:47:14 +0000906 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000911
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000913
Tim Peters07e99cb2001-01-14 23:47:14 +0000914 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000917
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
919 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 if __debug__:
922 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000923 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 while 1:
931 result = self.tagged_commands[tag]
932 if result is not None:
933 del self.tagged_commands[tag]
934 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000935
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 # Some have reported "unexpected response" exceptions.
937 # Note that ignoring them here causes loops.
938 # Instead, send me details of the unexpected response and
939 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 try:
942 self._get_response()
943 except self.abort, val:
944 if __debug__:
945 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000946 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
949
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000951
Piers Lauder15e5d532001-07-20 10:52:06 +0000952 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 if not line:
954 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000957
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 line = line[:-2]
959 if __debug__:
960 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000961 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000962 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000963 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000965
966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 # Run compiled regular expression match method on 's'.
970 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000971
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 self.mo = cre.match(s)
973 if __debug__:
974 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000975 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 tag = '%s%s' % (self.tagpre, self.tagnum)
982 self.tagnum = self.tagnum + 1
983 self.tagged_commands[tag] = None
984 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000985
986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 # Must quote command args if non-alphanumeric chars present,
990 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000991
Tim Peters07e99cb2001-01-14 23:47:14 +0000992 if type(arg) is not type(''):
993 return arg
994 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
995 return arg
996 if self.mustquote.search(arg) is None:
997 return arg
998 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000999
1000
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001002
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001003 arg = arg.replace('\\', '\\\\')
1004 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001005
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001007
1008
Tim Peters07e99cb2001-01-14 23:47:14 +00001009 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001010
Guido van Rossum68468eb2003-02-27 20:14:51 +00001011 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001012
1013
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 if typ == 'NO':
1017 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001018 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001020 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 if __debug__:
1022 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001023 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
1026
Piers Lauderf2d7d152002-02-22 01:15:17 +00001027 if __debug__:
1028
1029 def _mesg(self, s, secs=None):
1030 if secs is None:
1031 secs = time.time()
1032 tm = time.strftime('%M:%S', time.localtime(secs))
1033 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1034 sys.stderr.flush()
1035
1036 def _dump_ur(self, dict):
1037 # Dump untagged responses (in `dict').
1038 l = dict.items()
1039 if not l: return
1040 t = '\n\t\t'
1041 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1042 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1043
1044 def _log(self, line):
1045 # Keep log of last `_cmd_log_len' interactions for debugging.
1046 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1047 self._cmd_log_idx += 1
1048 if self._cmd_log_idx >= self._cmd_log_len:
1049 self._cmd_log_idx = 0
1050
1051 def print_log(self):
1052 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1053 i, n = self._cmd_log_idx, self._cmd_log_len
1054 while n:
1055 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001056 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001057 except:
1058 pass
1059 i += 1
1060 if i >= self._cmd_log_len:
1061 i = 0
1062 n -= 1
1063
1064
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001065
Piers Laudera4f83132002-03-08 01:53:24 +00001066class IMAP4_SSL(IMAP4):
1067
1068 """IMAP4 client class over SSL connection
1069
Piers Lauder95f84952002-03-08 09:05:12 +00001070 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001071
1072 host - host's name (default: localhost);
1073 port - port number (default: standard IMAP4 SSL port).
1074 keyfile - PEM formatted file that contains your private key (default: None);
1075 certfile - PEM formatted certificate chain file (default: None);
1076
1077 for more documentation see the docstring of the parent class IMAP4.
1078 """
1079
1080
1081 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1082 self.keyfile = keyfile
1083 self.certfile = certfile
1084 IMAP4.__init__(self, host, port)
1085
1086
Piers Lauderf97b2d72002-06-05 22:31:57 +00001087 def open(self, host = '', port = IMAP4_SSL_PORT):
Piers Laudera4f83132002-03-08 01:53:24 +00001088 """Setup connection to remote server on "host:port".
Piers Lauderf97b2d72002-06-05 22:31:57 +00001089 (default: localhost:standard IMAP4 SSL port).
Piers Laudera4f83132002-03-08 01:53:24 +00001090 This connection will be used by the routines:
1091 read, readline, send, shutdown.
1092 """
Piers Lauderf97b2d72002-06-05 22:31:57 +00001093 self.host = host
1094 self.port = port
Piers Laudera4f83132002-03-08 01:53:24 +00001095 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +00001096 self.sock.connect((host, port))
1097 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001098
1099
1100 def read(self, size):
1101 """Read 'size' bytes from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001102 # sslobj.read() sometimes returns < size bytes
1103 data = self.sslobj.read(size)
1104 while len(data) < size:
Tim Petersc411dba2002-07-16 21:35:23 +00001105 data += self.sslobj.read(size-len(data))
Piers Lauder0c092932002-06-23 10:47:13 +00001106
1107 return data
Piers Laudera4f83132002-03-08 01:53:24 +00001108
1109
1110 def readline(self):
1111 """Read line from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001112 # NB: socket.ssl needs a "readline" method, or perhaps a "makefile" method.
Piers Laudera4f83132002-03-08 01:53:24 +00001113 line = ""
1114 while 1:
1115 char = self.sslobj.read(1)
1116 line += char
1117 if char == "\n": return line
1118
1119
1120 def send(self, data):
1121 """Send data to remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001122 # NB: socket.ssl needs a "sendall" method to match socket objects.
1123 bytes = len(data)
1124 while bytes > 0:
1125 sent = self.sslobj.write(data)
1126 if sent == bytes:
1127 break # avoid copy
1128 data = data[sent:]
1129 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001130
1131
1132 def shutdown(self):
1133 """Close I/O established in "open"."""
1134 self.sock.close()
1135
1136
1137 def socket(self):
1138 """Return socket instance used to connect to IMAP4 server.
1139
1140 socket = <instance>.socket()
1141 """
1142 return self.sock
1143
1144
1145 def ssl(self):
1146 """Return SSLObject instance used to communicate with the IMAP4 server.
1147
1148 ssl = <instance>.socket.ssl()
1149 """
1150 return self.sslobj
1151
1152
1153
Piers Laudere0273de2002-11-22 05:53:04 +00001154class IMAP4_stream(IMAP4):
1155
1156 """IMAP4 client class over a stream
1157
1158 Instantiate with: IMAP4_stream(command)
1159
1160 where "command" is a string that can be passed to os.popen2()
1161
1162 for more documentation see the docstring of the parent class IMAP4.
1163 """
1164
1165
1166 def __init__(self, command):
1167 self.command = command
1168 IMAP4.__init__(self)
1169
1170
1171 def open(self, host = None, port = None):
1172 """Setup a stream connection.
1173 This connection will be used by the routines:
1174 read, readline, send, shutdown.
1175 """
1176 self.host = None # For compatibility with parent class
1177 self.port = None
1178 self.sock = None
1179 self.file = None
1180 self.writefile, self.readfile = os.popen2(self.command)
1181
1182
1183 def read(self, size):
1184 """Read 'size' bytes from remote."""
1185 return self.readfile.read(size)
1186
1187
1188 def readline(self):
1189 """Read line from remote."""
1190 return self.readfile.readline()
1191
1192
1193 def send(self, data):
1194 """Send data to remote."""
1195 self.writefile.write(data)
1196 self.writefile.flush()
1197
1198
1199 def shutdown(self):
1200 """Close I/O established in "open"."""
1201 self.readfile.close()
1202 self.writefile.close()
1203
1204
1205
Guido van Rossumeda960a1998-06-18 14:24:28 +00001206class _Authenticator:
1207
Tim Peters07e99cb2001-01-14 23:47:14 +00001208 """Private class to provide en/decoding
1209 for base64-based authentication conversation.
1210 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001211
Tim Peters07e99cb2001-01-14 23:47:14 +00001212 def __init__(self, mechinst):
1213 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001214
Tim Peters07e99cb2001-01-14 23:47:14 +00001215 def process(self, data):
1216 ret = self.mech(self.decode(data))
1217 if ret is None:
1218 return '*' # Abort conversation
1219 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001220
Tim Peters07e99cb2001-01-14 23:47:14 +00001221 def encode(self, inp):
1222 #
1223 # Invoke binascii.b2a_base64 iteratively with
1224 # short even length buffers, strip the trailing
1225 # line feed from the result and append. "Even"
1226 # means a number that factors to both 6 and 8,
1227 # so when it gets to the end of the 8-bit input
1228 # there's no partial 6-bit output.
1229 #
1230 oup = ''
1231 while inp:
1232 if len(inp) > 48:
1233 t = inp[:48]
1234 inp = inp[48:]
1235 else:
1236 t = inp
1237 inp = ''
1238 e = binascii.b2a_base64(t)
1239 if e:
1240 oup = oup + e[:-1]
1241 return oup
1242
1243 def decode(self, inp):
1244 if not inp:
1245 return ''
1246 return binascii.a2b_base64(inp)
1247
Guido van Rossumeda960a1998-06-18 14:24:28 +00001248
1249
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001250Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001251 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001252
1253def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001254 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001255
Tim Peters07e99cb2001-01-14 23:47:14 +00001256 Returns Python time module tuple.
1257 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001258
Tim Peters07e99cb2001-01-14 23:47:14 +00001259 mo = InternalDate.match(resp)
1260 if not mo:
1261 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001262
Tim Peters07e99cb2001-01-14 23:47:14 +00001263 mon = Mon2num[mo.group('mon')]
1264 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001265
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001266 day = int(mo.group('day'))
1267 year = int(mo.group('year'))
1268 hour = int(mo.group('hour'))
1269 min = int(mo.group('min'))
1270 sec = int(mo.group('sec'))
1271 zoneh = int(mo.group('zoneh'))
1272 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001273
Tim Peters07e99cb2001-01-14 23:47:14 +00001274 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001275
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 zone = (zoneh*60 + zonem)*60
1277 if zonen == '-':
1278 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001279
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001281
Tim Peters07e99cb2001-01-14 23:47:14 +00001282 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001283
Tim Peters07e99cb2001-01-14 23:47:14 +00001284 # Following is necessary because the time module has no 'mkgmtime'.
1285 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001286
Tim Peters07e99cb2001-01-14 23:47:14 +00001287 lt = time.localtime(utc)
1288 if time.daylight and lt[-1]:
1289 zone = zone + time.altzone
1290 else:
1291 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001292
Tim Peters07e99cb2001-01-14 23:47:14 +00001293 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001294
1295
1296
1297def Int2AP(num):
1298
Tim Peters07e99cb2001-01-14 23:47:14 +00001299 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001300
Tim Peters07e99cb2001-01-14 23:47:14 +00001301 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1302 num = int(abs(num))
1303 while num:
1304 num, mod = divmod(num, 16)
1305 val = AP[mod] + val
1306 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001307
1308
1309
1310def ParseFlags(resp):
1311
Tim Peters07e99cb2001-01-14 23:47:14 +00001312 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001313
Tim Peters07e99cb2001-01-14 23:47:14 +00001314 mo = Flags.match(resp)
1315 if not mo:
1316 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001317
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001318 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001319
1320
1321def Time2Internaldate(date_time):
1322
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001324
Tim Peters07e99cb2001-01-14 23:47:14 +00001325 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1326 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001327
Fred Drakedb519202002-01-05 17:17:09 +00001328 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001329 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001330 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001331 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001332 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001333 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001334 else:
1335 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001336
Tim Peters07e99cb2001-01-14 23:47:14 +00001337 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1338 if dt[0] == '0':
1339 dt = ' ' + dt[1:]
1340 if time.daylight and tt[-1]:
1341 zone = -time.altzone
1342 else:
1343 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001344 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001345
1346
1347
Guido van Rossum8c062211999-12-13 23:27:45 +00001348if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001349
Piers Laudere0273de2002-11-22 05:53:04 +00001350 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1351 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1352 # to test the IMAP4_stream class
1353
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001354 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001355
Tim Peters07e99cb2001-01-14 23:47:14 +00001356 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001357 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001358 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001359 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001360
Piers Laudere0273de2002-11-22 05:53:04 +00001361 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001362 for opt,val in optlist:
1363 if opt == '-d':
1364 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001365 elif opt == '-s':
1366 stream_command = val
1367 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001368
Tim Peters07e99cb2001-01-14 23:47:14 +00001369 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001370
Tim Peters07e99cb2001-01-14 23:47:14 +00001371 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001372
Tim Peters07e99cb2001-01-14 23:47:14 +00001373 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001374 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001375
Piers Lauder47404ff2003-04-29 23:40:59 +00001376 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 +00001377 test_seq1 = (
1378 ('login', (USER, PASSWD)),
1379 ('create', ('/tmp/xxx 1',)),
1380 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1381 ('CREATE', ('/tmp/yyz 2',)),
1382 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1383 ('list', ('/tmp', 'yy*')),
1384 ('select', ('/tmp/yyz 2',)),
1385 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001386 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001387 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001388 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001389 ('expunge', ()),
1390 ('recent', ()),
1391 ('close', ()),
1392 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001393
Tim Peters07e99cb2001-01-14 23:47:14 +00001394 test_seq2 = (
1395 ('select', ()),
1396 ('response',('UIDVALIDITY',)),
1397 ('uid', ('SEARCH', 'ALL')),
1398 ('response', ('EXISTS',)),
1399 ('append', (None, None, None, test_mesg)),
1400 ('recent', ()),
1401 ('logout', ()),
1402 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001403
Tim Peters07e99cb2001-01-14 23:47:14 +00001404 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001405 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001406 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001407 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001408 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001410
Tim Peters07e99cb2001-01-14 23:47:14 +00001411 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001412 if stream_command:
1413 M = IMAP4_stream(stream_command)
1414 else:
1415 M = IMAP4(host)
1416 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001417 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001418 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001419 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001420
Tim Peters07e99cb2001-01-14 23:47:14 +00001421 for cmd,args in test_seq1:
1422 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 for ml in run('list', ('/tmp/', 'yy%')):
1425 mo = re.match(r'.*"([^"]+)"$', ml)
1426 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001427 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 for cmd,args in test_seq2:
1431 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1434 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001435
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001436 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001437 if not uid: continue
1438 run('uid', ('FETCH', '%s' % uid[-1],
1439 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001440
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001442
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 except:
1444 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001445
Tim Peters07e99cb2001-01-14 23:47:14 +00001446 if not Debug:
1447 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001448If you would like to see debugging output,
1449try: %s -d5
1450''' % sys.argv[0]
1451
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 raise