blob: f353015c935d9626e42f4833e50a2ecd27598f73 [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 Lauder6a4e6352004-08-10 01:24:54 +000022__version__ = "2.55"
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
Piers Lauder06594522004-06-02 00:42:36 +000026__all__ = ["IMAP4", "IMAP4_SSL", "IMAP4_stream", "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'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000049 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000050 'EXAMINE': ('AUTH', 'SELECTED'),
51 'EXPUNGE': ('SELECTED',),
52 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000053 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000054 'GETQUOTA': ('AUTH', 'SELECTED'),
55 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000056 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000057 'LIST': ('AUTH', 'SELECTED'),
58 'LOGIN': ('NONAUTH',),
59 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
60 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000061 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000062 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000063 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000064 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000065 'RENAME': ('AUTH', 'SELECTED'),
66 'SEARCH': ('SELECTED',),
67 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000068 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000069 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000070 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000071 'STATUS': ('AUTH', 'SELECTED'),
72 'STORE': ('SELECTED',),
73 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000074 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000075 'UID': ('SELECTED',),
76 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
77 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000078
Tim Peters07e99cb2001-01-14 23:47:14 +000079# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000080
Guido van Rossumeda960a1998-06-18 14:24:28 +000081Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000082Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
83InternalDate = re.compile(r'.*INTERNALDATE "'
Tim Peters07e99cb2001-01-14 23:47:14 +000084 r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
85 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
86 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
87 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000088Literal = re.compile(r'.*{(?P<size>\d+)}$')
Piers Lauder533366b2003-04-29 23:58:08 +000089MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000090Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000091Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000092Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
93
94
95
96class IMAP4:
97
Tim Peters07e99cb2001-01-14 23:47:14 +000098 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000099
Tim Peters07e99cb2001-01-14 23:47:14 +0000100 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000101
Tim Peters07e99cb2001-01-14 23:47:14 +0000102 host - host's name (default: localhost);
103 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104
Tim Peters07e99cb2001-01-14 23:47:14 +0000105 All IMAP4rev1 commands are supported by methods of the same
106 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000107
Tim Peters07e99cb2001-01-14 23:47:14 +0000108 All arguments to commands are converted to strings, except for
109 AUTHENTICATE, and the last argument to APPEND which is passed as
110 an IMAP4 literal. If necessary (the string contains any
111 non-printing characters or white-space and isn't enclosed with
112 either parentheses or double quotes) each string is quoted.
113 However, the 'password' argument to the LOGIN command is always
114 quoted. If you want to avoid having an argument string quoted
115 (eg: the 'flags' argument to STORE) then enclose the string in
116 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000117
Tim Peters07e99cb2001-01-14 23:47:14 +0000118 Each command returns a tuple: (type, [data, ...]) where 'type'
119 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000120 tagged response, or untagged results from command. Each 'data'
121 is either a string, or a tuple. If a tuple, then the first part
122 is the header of the response, and the second part contains
123 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000124
Tim Peters07e99cb2001-01-14 23:47:14 +0000125 Errors raise the exception class <instance>.error("<reason>").
126 IMAP4 server errors raise <instance>.abort("<reason>"),
127 which is a sub-class of 'error'. Mailbox status changes
128 from READ-WRITE to READ-ONLY raise the exception class
129 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000130
Tim Peters07e99cb2001-01-14 23:47:14 +0000131 "error" exceptions imply a program error.
132 "abort" exceptions imply the connection should be reset, and
133 the command re-tried.
134 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000135
Tim Peters07e99cb2001-01-14 23:47:14 +0000136 Note: to use this module, you must read the RFCs pertaining
137 to the IMAP4 protocol, as the semantics of the arguments to
138 each IMAP4 command are left to the invoker, not to mention
139 the results.
140 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000141
Tim Peters07e99cb2001-01-14 23:47:14 +0000142 class error(Exception): pass # Logical errors - debug required
143 class abort(error): pass # Service errors - close and retry
144 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000145
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000147
Tim Peters07e99cb2001-01-14 23:47:14 +0000148 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 self.debug = Debug
150 self.state = 'LOGOUT'
151 self.literal = None # A literal argument to a command
152 self.tagged_commands = {} # Tagged commands awaiting response
153 self.untagged_responses = {} # {typ: [data, ...], ...}
154 self.continuation_response = '' # Last continuation response
155 self.is_readonly = None # READ-ONLY desired state
156 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000157
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 # Create unique tag for this session,
163 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000164
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 self.tagpre = Int2AP(random.randint(0, 31999))
166 self.tagre = re.compile(r'(?P<tag>'
167 + self.tagpre
168 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000169
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 # Get server welcome message,
171 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000172
Tim Peters07e99cb2001-01-14 23:47:14 +0000173 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000174 self._cmd_log_len = 10
175 self._cmd_log_idx = 0
176 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000177 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000178 self._mesg('imaplib version %s' % __version__)
179 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000180
Tim Peters07e99cb2001-01-14 23:47:14 +0000181 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000182 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000184 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 self.state = 'NONAUTH'
186 else:
187 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000188
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 cap = 'CAPABILITY'
190 self._simple_command(cap)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000191 if not cap in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000192 raise self.error('no CAPABILITY response from server')
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000193 self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000194
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 if __debug__:
196 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000197 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000198
Tim Peters07e99cb2001-01-14 23:47:14 +0000199 for version in AllowedVersions:
200 if not version in self.capabilities:
201 continue
202 self.PROTOCOL_VERSION = version
203 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000204
Tim Peters07e99cb2001-01-14 23:47:14 +0000205 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000206
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000207
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 def __getattr__(self, attr):
209 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000210 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000211 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000212 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000213
214
215
Piers Lauder15e5d532001-07-20 10:52:06 +0000216 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000217
218
Piers Lauderf97b2d72002-06-05 22:31:57 +0000219 def open(self, host = '', port = IMAP4_PORT):
220 """Setup connection to remote server on "host:port"
221 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000222 This connection will be used by the routines:
223 read, readline, send, shutdown.
224 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000225 self.host = host
226 self.port = port
Tim Peters07e99cb2001-01-14 23:47:14 +0000227 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +0000228 self.sock.connect((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000229 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000230
231
Piers Lauder15e5d532001-07-20 10:52:06 +0000232 def read(self, size):
233 """Read 'size' bytes from remote."""
234 return self.file.read(size)
235
236
237 def readline(self):
238 """Read line from remote."""
239 return self.file.readline()
240
241
242 def send(self, data):
243 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000244 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000245
Piers Lauderf2d7d152002-02-22 01:15:17 +0000246
Piers Lauder15e5d532001-07-20 10:52:06 +0000247 def shutdown(self):
248 """Close I/O established in "open"."""
249 self.file.close()
250 self.sock.close()
251
252
253 def socket(self):
254 """Return socket instance used to connect to IMAP4 server.
255
256 socket = <instance>.socket()
257 """
258 return self.sock
259
260
261
262 # Utility methods
263
264
Tim Peters07e99cb2001-01-14 23:47:14 +0000265 def recent(self):
266 """Return most recent 'RECENT' responses if any exist,
267 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000268
Tim Peters07e99cb2001-01-14 23:47:14 +0000269 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000270
Tim Peters07e99cb2001-01-14 23:47:14 +0000271 'data' is None if no new messages,
272 else list of RECENT responses, most recent last.
273 """
274 name = 'RECENT'
275 typ, dat = self._untagged_response('OK', [None], name)
276 if dat[-1]:
277 return typ, dat
278 typ, dat = self.noop() # Prod server for response
279 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000280
281
Tim Peters07e99cb2001-01-14 23:47:14 +0000282 def response(self, code):
283 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000284
Tim Peters07e99cb2001-01-14 23:47:14 +0000285 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000286
Tim Peters07e99cb2001-01-14 23:47:14 +0000287 (code, [data]) = <instance>.response(code)
288 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000289 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000290
291
Guido van Rossum26367a01998-09-28 15:34:46 +0000292
Tim Peters07e99cb2001-01-14 23:47:14 +0000293 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000294
295
Tim Peters07e99cb2001-01-14 23:47:14 +0000296 def append(self, mailbox, flags, date_time, message):
297 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000298
Tim Peters07e99cb2001-01-14 23:47:14 +0000299 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000300
Tim Peters07e99cb2001-01-14 23:47:14 +0000301 All args except `message' can be None.
302 """
303 name = 'APPEND'
304 if not mailbox:
305 mailbox = 'INBOX'
306 if flags:
307 if (flags[0],flags[-1]) != ('(',')'):
308 flags = '(%s)' % flags
309 else:
310 flags = None
311 if date_time:
312 date_time = Time2Internaldate(date_time)
313 else:
314 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000315 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000317
318
Tim Peters07e99cb2001-01-14 23:47:14 +0000319 def authenticate(self, mechanism, authobject):
320 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000321
Tim Peters07e99cb2001-01-14 23:47:14 +0000322 'mechanism' specifies which authentication mechanism is to
323 be used - it must appear in <instance>.capabilities in the
324 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000325
Tim Peters07e99cb2001-01-14 23:47:14 +0000326 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000327
Tim Peters07e99cb2001-01-14 23:47:14 +0000328 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000329
Tim Peters07e99cb2001-01-14 23:47:14 +0000330 It will be called to process server continuation responses.
331 It should return data that will be encoded and sent to server.
332 It should return None if the client abort response '*' should
333 be sent instead.
334 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000335 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000336 # XXX: shouldn't this code be removed, not commented out?
337 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000338 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000339 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000340 self.literal = _Authenticator(authobject).process
341 typ, dat = self._simple_command('AUTHENTICATE', mech)
342 if typ != 'OK':
343 raise self.error(dat[-1])
344 self.state = 'AUTH'
345 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000346
347
Tim Peters07e99cb2001-01-14 23:47:14 +0000348 def check(self):
349 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000350
Tim Peters07e99cb2001-01-14 23:47:14 +0000351 (typ, [data]) = <instance>.check()
352 """
353 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000354
355
Tim Peters07e99cb2001-01-14 23:47:14 +0000356 def close(self):
357 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000358
Tim Peters07e99cb2001-01-14 23:47:14 +0000359 Deleted messages are removed from writable mailbox.
360 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000361
Tim Peters07e99cb2001-01-14 23:47:14 +0000362 (typ, [data]) = <instance>.close()
363 """
364 try:
365 typ, dat = self._simple_command('CLOSE')
366 finally:
367 self.state = 'AUTH'
368 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000369
370
Tim Peters07e99cb2001-01-14 23:47:14 +0000371 def copy(self, message_set, new_mailbox):
372 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
375 """
376 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000377
378
Tim Peters07e99cb2001-01-14 23:47:14 +0000379 def create(self, mailbox):
380 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 (typ, [data]) = <instance>.create(mailbox)
383 """
384 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
386
Tim Peters07e99cb2001-01-14 23:47:14 +0000387 def delete(self, mailbox):
388 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 (typ, [data]) = <instance>.delete(mailbox)
391 """
392 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000394 def deleteacl(self, mailbox, who):
395 """Delete the ACLs (remove any rights) set for who on mailbox.
396
397 (typ, [data]) = <instance>.deleteacl(mailbox, who)
398 """
399 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000400
Tim Peters07e99cb2001-01-14 23:47:14 +0000401 def expunge(self):
402 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000405
Tim Peters07e99cb2001-01-14 23:47:14 +0000406 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 'data' is list of 'EXPUNGE'd message numbers in order received.
409 """
410 name = 'EXPUNGE'
411 typ, dat = self._simple_command(name)
412 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000413
414
Tim Peters07e99cb2001-01-14 23:47:14 +0000415 def fetch(self, message_set, message_parts):
416 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000419
Tim Peters07e99cb2001-01-14 23:47:14 +0000420 'message_parts' should be a string of selected parts
421 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 'data' are tuples of message part envelope and data.
424 """
425 name = 'FETCH'
426 typ, dat = self._simple_command(name, message_set, message_parts)
427 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000428
429
Piers Lauder15e5d532001-07-20 10:52:06 +0000430 def getacl(self, mailbox):
431 """Get the ACLs for a mailbox.
432
433 (typ, [data]) = <instance>.getacl(mailbox)
434 """
435 typ, dat = self._simple_command('GETACL', mailbox)
436 return self._untagged_response(typ, dat, 'ACL')
437
438
Piers Lauder3fca2912002-06-17 07:07:20 +0000439 def getquota(self, root):
440 """Get the quota root's resource usage and limits.
441
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000442 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000443
444 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000445 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000446 typ, dat = self._simple_command('GETQUOTA', root)
447 return self._untagged_response(typ, dat, 'QUOTA')
448
449
450 def getquotaroot(self, mailbox):
451 """Get the list of quota roots for the named mailbox.
452
453 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000454 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000455 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000456 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
457 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000458 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000459
460
Tim Peters07e99cb2001-01-14 23:47:14 +0000461 def list(self, directory='""', pattern='*'):
462 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000463
Tim Peters07e99cb2001-01-14 23:47:14 +0000464 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
Tim Peters07e99cb2001-01-14 23:47:14 +0000466 'data' is list of LIST responses.
467 """
468 name = 'LIST'
469 typ, dat = self._simple_command(name, directory, pattern)
470 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000471
472
Tim Peters07e99cb2001-01-14 23:47:14 +0000473 def login(self, user, password):
474 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000475
Tim Peters07e99cb2001-01-14 23:47:14 +0000476 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000477
Tim Peters07e99cb2001-01-14 23:47:14 +0000478 NB: 'password' will be quoted.
479 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000480 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
481 if typ != 'OK':
482 raise self.error(dat[-1])
483 self.state = 'AUTH'
484 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000485
486
Piers Laudere0273de2002-11-22 05:53:04 +0000487 def login_cram_md5(self, user, password):
488 """ Force use of CRAM-MD5 authentication.
489
490 (typ, [data]) = <instance>.login_cram_md5(user, password)
491 """
492 self.user, self.password = user, password
493 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
494
495
496 def _CRAM_MD5_AUTH(self, challenge):
497 """ Authobject to use with CRAM-MD5 authentication. """
498 import hmac
499 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
500
501
Tim Peters07e99cb2001-01-14 23:47:14 +0000502 def logout(self):
503 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000504
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000506
Tim Peters07e99cb2001-01-14 23:47:14 +0000507 Returns server 'BYE' response.
508 """
509 self.state = 'LOGOUT'
510 try: typ, dat = self._simple_command('LOGOUT')
511 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000512 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000513 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 return 'BYE', self.untagged_responses['BYE']
515 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000516
517
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 def lsub(self, directory='""', pattern='*'):
519 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000520
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000522
Tim Peters07e99cb2001-01-14 23:47:14 +0000523 'data' are tuples of message part envelope and data.
524 """
525 name = 'LSUB'
526 typ, dat = self._simple_command(name, directory, pattern)
527 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000528
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000529 def myrights(self, mailbox):
530 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
531
532 (typ, [data]) = <instance>.myrights(mailbox)
533 """
534 typ,dat = self._simple_command('MYRIGHTS', mailbox)
535 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
Piers Lauder15e5d532001-07-20 10:52:06 +0000537 def namespace(self):
538 """ Returns IMAP namespaces ala rfc2342
539
540 (typ, [data, ...]) = <instance>.namespace()
541 """
542 name = 'NAMESPACE'
543 typ, dat = self._simple_command(name)
544 return self._untagged_response(typ, dat, name)
545
546
Tim Peters07e99cb2001-01-14 23:47:14 +0000547 def noop(self):
548 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000549
Piers Laudere0273de2002-11-22 05:53:04 +0000550 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000551 """
552 if __debug__:
553 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000554 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000555 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000556
557
Tim Peters07e99cb2001-01-14 23:47:14 +0000558 def partial(self, message_num, message_part, start, length):
559 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000560
Tim Peters07e99cb2001-01-14 23:47:14 +0000561 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000562
Tim Peters07e99cb2001-01-14 23:47:14 +0000563 'data' is tuple of message part envelope and data.
564 """
565 name = 'PARTIAL'
566 typ, dat = self._simple_command(name, message_num, message_part, start, length)
567 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000568
569
Piers Laudere0273de2002-11-22 05:53:04 +0000570 def proxyauth(self, user):
571 """Assume authentication as "user".
572
573 Allows an authorised administrator to proxy into any user's
574 mailbox.
575
576 (typ, [data]) = <instance>.proxyauth(user)
577 """
578
579 name = 'PROXYAUTH'
580 return self._simple_command('PROXYAUTH', user)
581
582
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 def rename(self, oldmailbox, newmailbox):
584 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000585
Piers Laudere0273de2002-11-22 05:53:04 +0000586 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000587 """
588 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000589
590
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 def search(self, charset, *criteria):
592 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000593
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000594 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000595
Tim Peters07e99cb2001-01-14 23:47:14 +0000596 'data' is space separated list of matching message numbers.
597 """
598 name = 'SEARCH'
599 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000600 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000601 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000602 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000604
605
Tim Peters07e99cb2001-01-14 23:47:14 +0000606 def select(self, mailbox='INBOX', readonly=None):
607 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000608
Tim Peters07e99cb2001-01-14 23:47:14 +0000609 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000610
Tim Peters07e99cb2001-01-14 23:47:14 +0000611 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000612
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000614
615 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
616 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000618 self.untagged_responses = {} # Flush old responses.
619 self.is_readonly = readonly
Raymond Hettinger936654b2002-06-01 03:06:31 +0000620 if readonly is not None:
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 name = 'EXAMINE'
622 else:
623 name = 'SELECT'
624 typ, dat = self._simple_command(name, mailbox)
625 if typ != 'OK':
626 self.state = 'AUTH' # Might have been 'SELECTED'
627 return typ, dat
628 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000629 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000630 and not readonly:
631 if __debug__:
632 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000633 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000634 raise self.readonly('%s is not writable' % mailbox)
635 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
637
Piers Lauder15e5d532001-07-20 10:52:06 +0000638 def setacl(self, mailbox, who, what):
639 """Set a mailbox acl.
640
Piers Lauderf167dc32004-03-25 00:12:21 +0000641 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000642 """
643 return self._simple_command('SETACL', mailbox, who, what)
644
645
Piers Lauder3fca2912002-06-17 07:07:20 +0000646 def setquota(self, root, limits):
647 """Set the quota root's resource limits.
648
649 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000650 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000651 typ, dat = self._simple_command('SETQUOTA', root, limits)
652 return self._untagged_response(typ, dat, 'QUOTA')
653
654
Piers Lauder15e5d532001-07-20 10:52:06 +0000655 def sort(self, sort_criteria, charset, *search_criteria):
656 """IMAP4rev1 extension SORT command.
657
658 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
659 """
660 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000661 #if not name in self.capabilities: # Let the server decide!
662 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000663 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000664 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000665 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000666 return self._untagged_response(typ, dat, name)
667
668
Tim Peters07e99cb2001-01-14 23:47:14 +0000669 def status(self, mailbox, names):
670 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000671
Tim Peters07e99cb2001-01-14 23:47:14 +0000672 (typ, [data]) = <instance>.status(mailbox, names)
673 """
674 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000675 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000676 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000677 typ, dat = self._simple_command(name, mailbox, names)
678 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000679
680
Tim Peters07e99cb2001-01-14 23:47:14 +0000681 def store(self, message_set, command, flags):
682 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000683
Tim Peters07e99cb2001-01-14 23:47:14 +0000684 (typ, [data]) = <instance>.store(message_set, command, flags)
685 """
686 if (flags[0],flags[-1]) != ('(',')'):
687 flags = '(%s)' % flags # Avoid quoting the flags
688 typ, dat = self._simple_command('STORE', message_set, command, flags)
689 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000690
691
Tim Peters07e99cb2001-01-14 23:47:14 +0000692 def subscribe(self, mailbox):
693 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000694
Tim Peters07e99cb2001-01-14 23:47:14 +0000695 (typ, [data]) = <instance>.subscribe(mailbox)
696 """
697 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000698
699
Martin v. Löwisd8921372003-11-10 06:44:44 +0000700 def thread(self, threading_algorithm, charset, *search_criteria):
701 """IMAPrev1 extension THREAD command.
702
703 (type, [data]) = <instance>.thread(threading_alogrithm, charset, search_criteria, ...)
704 """
705 name = 'THREAD'
706 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
707 return self._untagged_response(typ, dat, name)
708
709
Tim Peters07e99cb2001-01-14 23:47:14 +0000710 def uid(self, command, *args):
711 """Execute "command arg ..." with messages identified by UID,
712 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000713
Tim Peters07e99cb2001-01-14 23:47:14 +0000714 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000715
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 Returns response appropriate to 'command'.
717 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000718 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000719 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 raise self.error("Unknown IMAP4 UID command: %s" % command)
721 if self.state not in Commands[command]:
722 raise self.error('command %s illegal in state %s'
723 % (command, self.state))
724 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000725 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000726 if command in ('SEARCH', 'SORT'):
727 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000728 else:
729 name = 'FETCH'
730 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000731
732
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 def unsubscribe(self, mailbox):
734 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000735
Tim Peters07e99cb2001-01-14 23:47:14 +0000736 (typ, [data]) = <instance>.unsubscribe(mailbox)
737 """
738 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000739
740
Tim Peters07e99cb2001-01-14 23:47:14 +0000741 def xatom(self, name, *args):
742 """Allow simple extension commands
743 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000744
Piers Lauder15e5d532001-07-20 10:52:06 +0000745 Assumes command is legal in current state.
746
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000748
749 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000751 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000752 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000753 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000754 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000755 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000756 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000757
758
759
Tim Peters07e99cb2001-01-14 23:47:14 +0000760 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000761
762
Tim Peters07e99cb2001-01-14 23:47:14 +0000763 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000764
Tim Peters07e99cb2001-01-14 23:47:14 +0000765 if dat is None: dat = ''
766 ur = self.untagged_responses
767 if __debug__:
768 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000769 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000770 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000771 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000772 ur[typ].append(dat)
773 else:
774 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000775
776
Tim Peters07e99cb2001-01-14 23:47:14 +0000777 def _check_bye(self):
778 bye = self.untagged_responses.get('BYE')
779 if bye:
780 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000781
782
Tim Peters07e99cb2001-01-14 23:47:14 +0000783 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000784
Tim Peters07e99cb2001-01-14 23:47:14 +0000785 if self.state not in Commands[name]:
786 self.literal = None
787 raise self.error(
788 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000789
Tim Peters07e99cb2001-01-14 23:47:14 +0000790 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000791 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000793
Raymond Hettinger54f02222002-06-01 14:18:47 +0000794 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 and not self.is_readonly:
796 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000797
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 tag = self._new_tag()
799 data = '%s %s' % (tag, name)
800 for arg in args:
801 if arg is None: continue
802 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000803
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 literal = self.literal
805 if literal is not None:
806 self.literal = None
807 if type(literal) is type(self._command):
808 literator = literal
809 else:
810 literator = None
811 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 if __debug__:
814 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000815 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000817 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000820 self.send('%s%s' % (data, CRLF))
821 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 if literal is None:
825 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 while 1:
828 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000829
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 while self._get_response():
831 if self.tagged_commands[tag]: # BAD/NO?
832 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 if literator:
837 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 if __debug__:
840 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000841 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000844 self.send(literal)
845 self.send(CRLF)
846 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000848
Tim Peters07e99cb2001-01-14 23:47:14 +0000849 if not literator:
850 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000851
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000853
854
Tim Peters07e99cb2001-01-14 23:47:14 +0000855 def _command_complete(self, name, tag):
856 self._check_bye()
857 try:
858 typ, data = self._get_tagged_response(tag)
859 except self.abort, val:
860 raise self.abort('command: %s => %s' % (name, val))
861 except self.error, val:
862 raise self.error('command: %s => %s' % (name, val))
863 self._check_bye()
864 if typ == 'BAD':
865 raise self.error('%s command error: %s %s' % (name, typ, data))
866 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 # Read response and store.
872 #
873 # Returns None for continuation responses,
874 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 if self._match(self.tagre, resp):
881 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000882 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 typ = self.mo.group('type')
886 dat = self.mo.group('data')
887 self.tagged_commands[tag] = (typ, [dat])
888 else:
889 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000890
Tim Peters07e99cb2001-01-14 23:47:14 +0000891 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 if not self._match(Untagged_response, resp):
894 if self._match(Untagged_status, resp):
895 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 if self.mo is None:
898 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 if self._match(Continuation, resp):
901 self.continuation_response = self.mo.group('data')
902 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000905
Tim Peters07e99cb2001-01-14 23:47:14 +0000906 typ = self.mo.group('type')
907 dat = self.mo.group('data')
908 if dat is None: dat = '' # Null untagged response
909 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000910
Tim Peters07e99cb2001-01-14 23:47:14 +0000911 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000912
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000914
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000916
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000917 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 if __debug__:
919 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000920 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000921 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
936 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000937
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 if __debug__:
939 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000940 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000941
Tim Peters07e99cb2001-01-14 23:47:14 +0000942 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000943
944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 while 1:
948 result = self.tagged_commands[tag]
949 if result is not None:
950 del self.tagged_commands[tag]
951 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 # Some have reported "unexpected response" exceptions.
954 # Note that ignoring them here causes loops.
955 # Instead, send me details of the unexpected response and
956 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000957
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 try:
959 self._get_response()
960 except self.abort, val:
961 if __debug__:
962 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000963 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000965
966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Piers Lauder15e5d532001-07-20 10:52:06 +0000969 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 if not line:
971 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 line = line[:-2]
976 if __debug__:
977 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000978 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000980 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
983
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000985
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 # Run compiled regular expression match method on 's'.
987 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 self.mo = cre.match(s)
990 if __debug__:
991 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000992 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994
995
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 tag = '%s%s' % (self.tagpre, self.tagnum)
999 self.tagnum = self.tagnum + 1
1000 self.tagged_commands[tag] = None
1001 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
1003
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001005
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 # Must quote command args if non-alphanumeric chars present,
1007 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001008
Tim Peters07e99cb2001-01-14 23:47:14 +00001009 if type(arg) is not type(''):
1010 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001011 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001012 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001013 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 return arg
1015 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001016
1017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001019
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001020 arg = arg.replace('\\', '\\\\')
1021 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001022
Tim Peters07e99cb2001-01-14 23:47:14 +00001023 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001024
1025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001027
Guido van Rossum68468eb2003-02-27 20:14:51 +00001028 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
1030
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001032
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 if typ == 'NO':
1034 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001035 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001037 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 if __debug__:
1039 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001040 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001042
1043
Piers Lauderf2d7d152002-02-22 01:15:17 +00001044 if __debug__:
1045
1046 def _mesg(self, s, secs=None):
1047 if secs is None:
1048 secs = time.time()
1049 tm = time.strftime('%M:%S', time.localtime(secs))
1050 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1051 sys.stderr.flush()
1052
1053 def _dump_ur(self, dict):
1054 # Dump untagged responses (in `dict').
1055 l = dict.items()
1056 if not l: return
1057 t = '\n\t\t'
1058 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1059 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1060
1061 def _log(self, line):
1062 # Keep log of last `_cmd_log_len' interactions for debugging.
1063 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1064 self._cmd_log_idx += 1
1065 if self._cmd_log_idx >= self._cmd_log_len:
1066 self._cmd_log_idx = 0
1067
1068 def print_log(self):
1069 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1070 i, n = self._cmd_log_idx, self._cmd_log_len
1071 while n:
1072 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001073 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001074 except:
1075 pass
1076 i += 1
1077 if i >= self._cmd_log_len:
1078 i = 0
1079 n -= 1
1080
1081
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001082
Piers Laudera4f83132002-03-08 01:53:24 +00001083class IMAP4_SSL(IMAP4):
1084
1085 """IMAP4 client class over SSL connection
1086
Piers Lauder95f84952002-03-08 09:05:12 +00001087 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001088
1089 host - host's name (default: localhost);
1090 port - port number (default: standard IMAP4 SSL port).
1091 keyfile - PEM formatted file that contains your private key (default: None);
1092 certfile - PEM formatted certificate chain file (default: None);
1093
1094 for more documentation see the docstring of the parent class IMAP4.
1095 """
1096
1097
1098 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1099 self.keyfile = keyfile
1100 self.certfile = certfile
1101 IMAP4.__init__(self, host, port)
1102
1103
Piers Lauderf97b2d72002-06-05 22:31:57 +00001104 def open(self, host = '', port = IMAP4_SSL_PORT):
Piers Laudera4f83132002-03-08 01:53:24 +00001105 """Setup connection to remote server on "host:port".
Piers Lauderf97b2d72002-06-05 22:31:57 +00001106 (default: localhost:standard IMAP4 SSL port).
Piers Laudera4f83132002-03-08 01:53:24 +00001107 This connection will be used by the routines:
1108 read, readline, send, shutdown.
1109 """
Piers Lauderf97b2d72002-06-05 22:31:57 +00001110 self.host = host
1111 self.port = port
Piers Laudera4f83132002-03-08 01:53:24 +00001112 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +00001113 self.sock.connect((host, port))
1114 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001115
1116
1117 def read(self, size):
1118 """Read 'size' bytes from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001119 # sslobj.read() sometimes returns < size bytes
Piers Lauder8f2b2442004-05-20 01:16:14 +00001120 chunks = []
1121 read = 0
1122 while read < size:
1123 data = self.sslobj.read(size-read)
1124 read += len(data)
Piers Lauderc1e32b62004-05-20 11:32:35 +00001125 chunks.append(data)
Piers Lauder0c092932002-06-23 10:47:13 +00001126
Piers Lauder8f2b2442004-05-20 01:16:14 +00001127 return ''.join(chunks)
Piers Laudera4f83132002-03-08 01:53:24 +00001128
1129
1130 def readline(self):
1131 """Read line from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001132 # NB: socket.ssl needs a "readline" method, or perhaps a "makefile" method.
Piers Lauder8f2b2442004-05-20 01:16:14 +00001133 line = []
Piers Laudera4f83132002-03-08 01:53:24 +00001134 while 1:
1135 char = self.sslobj.read(1)
Piers Lauder8f2b2442004-05-20 01:16:14 +00001136 line.append(char)
1137 if char == "\n": return ''.join(line)
Piers Laudera4f83132002-03-08 01:53:24 +00001138
1139
1140 def send(self, data):
1141 """Send data to remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001142 # NB: socket.ssl needs a "sendall" method to match socket objects.
1143 bytes = len(data)
1144 while bytes > 0:
1145 sent = self.sslobj.write(data)
1146 if sent == bytes:
1147 break # avoid copy
1148 data = data[sent:]
1149 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001150
1151
1152 def shutdown(self):
1153 """Close I/O established in "open"."""
1154 self.sock.close()
1155
1156
1157 def socket(self):
1158 """Return socket instance used to connect to IMAP4 server.
1159
1160 socket = <instance>.socket()
1161 """
1162 return self.sock
1163
1164
1165 def ssl(self):
1166 """Return SSLObject instance used to communicate with the IMAP4 server.
1167
1168 ssl = <instance>.socket.ssl()
1169 """
1170 return self.sslobj
1171
1172
1173
Piers Laudere0273de2002-11-22 05:53:04 +00001174class IMAP4_stream(IMAP4):
1175
1176 """IMAP4 client class over a stream
1177
1178 Instantiate with: IMAP4_stream(command)
1179
1180 where "command" is a string that can be passed to os.popen2()
1181
1182 for more documentation see the docstring of the parent class IMAP4.
1183 """
1184
1185
1186 def __init__(self, command):
1187 self.command = command
1188 IMAP4.__init__(self)
1189
1190
1191 def open(self, host = None, port = None):
1192 """Setup a stream connection.
1193 This connection will be used by the routines:
1194 read, readline, send, shutdown.
1195 """
1196 self.host = None # For compatibility with parent class
1197 self.port = None
1198 self.sock = None
1199 self.file = None
1200 self.writefile, self.readfile = os.popen2(self.command)
1201
1202
1203 def read(self, size):
1204 """Read 'size' bytes from remote."""
1205 return self.readfile.read(size)
1206
1207
1208 def readline(self):
1209 """Read line from remote."""
1210 return self.readfile.readline()
1211
1212
1213 def send(self, data):
1214 """Send data to remote."""
1215 self.writefile.write(data)
1216 self.writefile.flush()
1217
1218
1219 def shutdown(self):
1220 """Close I/O established in "open"."""
1221 self.readfile.close()
1222 self.writefile.close()
1223
1224
1225
Guido van Rossumeda960a1998-06-18 14:24:28 +00001226class _Authenticator:
1227
Tim Peters07e99cb2001-01-14 23:47:14 +00001228 """Private class to provide en/decoding
1229 for base64-based authentication conversation.
1230 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001231
Tim Peters07e99cb2001-01-14 23:47:14 +00001232 def __init__(self, mechinst):
1233 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001234
Tim Peters07e99cb2001-01-14 23:47:14 +00001235 def process(self, data):
1236 ret = self.mech(self.decode(data))
1237 if ret is None:
1238 return '*' # Abort conversation
1239 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001240
Tim Peters07e99cb2001-01-14 23:47:14 +00001241 def encode(self, inp):
1242 #
1243 # Invoke binascii.b2a_base64 iteratively with
1244 # short even length buffers, strip the trailing
1245 # line feed from the result and append. "Even"
1246 # means a number that factors to both 6 and 8,
1247 # so when it gets to the end of the 8-bit input
1248 # there's no partial 6-bit output.
1249 #
1250 oup = ''
1251 while inp:
1252 if len(inp) > 48:
1253 t = inp[:48]
1254 inp = inp[48:]
1255 else:
1256 t = inp
1257 inp = ''
1258 e = binascii.b2a_base64(t)
1259 if e:
1260 oup = oup + e[:-1]
1261 return oup
1262
1263 def decode(self, inp):
1264 if not inp:
1265 return ''
1266 return binascii.a2b_base64(inp)
1267
Guido van Rossumeda960a1998-06-18 14:24:28 +00001268
1269
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001270Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001271 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001272
1273def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001274 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001275
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 Returns Python time module tuple.
1277 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001278
Tim Peters07e99cb2001-01-14 23:47:14 +00001279 mo = InternalDate.match(resp)
1280 if not mo:
1281 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001282
Tim Peters07e99cb2001-01-14 23:47:14 +00001283 mon = Mon2num[mo.group('mon')]
1284 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001285
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001286 day = int(mo.group('day'))
1287 year = int(mo.group('year'))
1288 hour = int(mo.group('hour'))
1289 min = int(mo.group('min'))
1290 sec = int(mo.group('sec'))
1291 zoneh = int(mo.group('zoneh'))
1292 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001293
Tim Peters07e99cb2001-01-14 23:47:14 +00001294 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001295
Tim Peters07e99cb2001-01-14 23:47:14 +00001296 zone = (zoneh*60 + zonem)*60
1297 if zonen == '-':
1298 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001299
Tim Peters07e99cb2001-01-14 23:47:14 +00001300 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001301
Tim Peters07e99cb2001-01-14 23:47:14 +00001302 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001303
Tim Peters07e99cb2001-01-14 23:47:14 +00001304 # Following is necessary because the time module has no 'mkgmtime'.
1305 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001306
Tim Peters07e99cb2001-01-14 23:47:14 +00001307 lt = time.localtime(utc)
1308 if time.daylight and lt[-1]:
1309 zone = zone + time.altzone
1310 else:
1311 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001312
Tim Peters07e99cb2001-01-14 23:47:14 +00001313 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001314
1315
1316
1317def Int2AP(num):
1318
Tim Peters07e99cb2001-01-14 23:47:14 +00001319 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001320
Tim Peters07e99cb2001-01-14 23:47:14 +00001321 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1322 num = int(abs(num))
1323 while num:
1324 num, mod = divmod(num, 16)
1325 val = AP[mod] + val
1326 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001327
1328
1329
1330def ParseFlags(resp):
1331
Tim Peters07e99cb2001-01-14 23:47:14 +00001332 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001333
Tim Peters07e99cb2001-01-14 23:47:14 +00001334 mo = Flags.match(resp)
1335 if not mo:
1336 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001338 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001339
1340
1341def Time2Internaldate(date_time):
1342
Tim Peters07e99cb2001-01-14 23:47:14 +00001343 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001344
Tim Peters07e99cb2001-01-14 23:47:14 +00001345 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1346 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001347
Fred Drakedb519202002-01-05 17:17:09 +00001348 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001349 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001350 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001351 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001352 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001353 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001354 else:
1355 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001356
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1358 if dt[0] == '0':
1359 dt = ' ' + dt[1:]
1360 if time.daylight and tt[-1]:
1361 zone = -time.altzone
1362 else:
1363 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001364 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001365
1366
1367
Guido van Rossum8c062211999-12-13 23:27:45 +00001368if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001369
Piers Laudere0273de2002-11-22 05:53:04 +00001370 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1371 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1372 # to test the IMAP4_stream class
1373
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001374 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001375
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001377 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001379 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001380
Piers Laudere0273de2002-11-22 05:53:04 +00001381 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001382 for opt,val in optlist:
1383 if opt == '-d':
1384 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001385 elif opt == '-s':
1386 stream_command = val
1387 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001388
Tim Peters07e99cb2001-01-14 23:47:14 +00001389 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001390
Tim Peters07e99cb2001-01-14 23:47:14 +00001391 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001392
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001394 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001395
Piers Lauder47404ff2003-04-29 23:40:59 +00001396 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 +00001397 test_seq1 = (
1398 ('login', (USER, PASSWD)),
1399 ('create', ('/tmp/xxx 1',)),
1400 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1401 ('CREATE', ('/tmp/yyz 2',)),
1402 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1403 ('list', ('/tmp', 'yy*')),
1404 ('select', ('/tmp/yyz 2',)),
1405 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001406 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001407 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001408 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 ('expunge', ()),
1410 ('recent', ()),
1411 ('close', ()),
1412 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001413
Tim Peters07e99cb2001-01-14 23:47:14 +00001414 test_seq2 = (
1415 ('select', ()),
1416 ('response',('UIDVALIDITY',)),
1417 ('uid', ('SEARCH', 'ALL')),
1418 ('response', ('EXISTS',)),
1419 ('append', (None, None, None, test_mesg)),
1420 ('recent', ()),
1421 ('logout', ()),
1422 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001425 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001426 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001427 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001428 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001429 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001430
Tim Peters07e99cb2001-01-14 23:47:14 +00001431 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001432 if stream_command:
1433 M = IMAP4_stream(stream_command)
1434 else:
1435 M = IMAP4(host)
1436 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001437 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001438 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001439 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001440
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 for cmd,args in test_seq1:
1442 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001443
Tim Peters07e99cb2001-01-14 23:47:14 +00001444 for ml in run('list', ('/tmp/', 'yy%')):
1445 mo = re.match(r'.*"([^"]+)"$', ml)
1446 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001447 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001448 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001449
Tim Peters07e99cb2001-01-14 23:47:14 +00001450 for cmd,args in test_seq2:
1451 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001452
Tim Peters07e99cb2001-01-14 23:47:14 +00001453 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1454 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001455
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001456 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 if not uid: continue
1458 run('uid', ('FETCH', '%s' % uid[-1],
1459 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001460
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001462
Tim Peters07e99cb2001-01-14 23:47:14 +00001463 except:
1464 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001465
Tim Peters07e99cb2001-01-14 23:47:14 +00001466 if not Debug:
1467 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001468If you would like to see debugging output,
1469try: %s -d5
1470''' % sys.argv[0]
1471
Tim Peters07e99cb2001-01-14 23:47:14 +00001472 raise