blob: 94f4e8fdf5def8391cf6beacf6af2ccd81ae9a35 [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.
Piers Lauderd80ef022005-06-01 23:50:52 +000021# GET/SETANNOTATION contributed by Tomas Lindroos <skitta@abo.fi> June 2005.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000022
Piers Lauderbe5615e2005-08-31 10:50:03 +000023__version__ = "2.58"
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000024
Christian Heimesfb5faf02008-11-05 19:39:50 +000025import binascii, random, re, socket, subprocess, sys, time
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000026
Thomas Wouters47b49bf2007-08-30 22:15:33 +000027__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000028 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000029
Tim Peters07e99cb2001-01-14 23:47:14 +000030# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000031
Christian Heimesfb5faf02008-11-05 19:39:50 +000032CRLF = b'\r\n'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000033Debug = 0
34IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000035IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000036AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000037
Tim Peters07e99cb2001-01-14 23:47:14 +000038# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000039
40Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000041 # name valid states
42 'APPEND': ('AUTH', 'SELECTED'),
43 'AUTHENTICATE': ('NONAUTH',),
44 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
45 'CHECK': ('SELECTED',),
46 'CLOSE': ('SELECTED',),
47 'COPY': ('SELECTED',),
48 'CREATE': ('AUTH', 'SELECTED'),
49 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000050 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000051 'EXAMINE': ('AUTH', 'SELECTED'),
52 'EXPUNGE': ('SELECTED',),
53 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000054 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000055 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000056 'GETQUOTA': ('AUTH', 'SELECTED'),
57 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000058 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000059 'LIST': ('AUTH', 'SELECTED'),
60 'LOGIN': ('NONAUTH',),
61 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
62 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000063 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000064 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000065 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000066 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000067 'RENAME': ('AUTH', 'SELECTED'),
68 'SEARCH': ('SELECTED',),
69 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000070 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000071 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000072 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000073 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000074 'STATUS': ('AUTH', 'SELECTED'),
75 'STORE': ('SELECTED',),
76 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000077 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000078 'UID': ('SELECTED',),
79 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
80 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000081
Tim Peters07e99cb2001-01-14 23:47:14 +000082# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000083
Christian Heimesfb5faf02008-11-05 19:39:50 +000084Continuation = re.compile(br'\+( (?P<data>.*))?')
85Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
86InternalDate = re.compile(br'.*INTERNALDATE "'
87 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
88 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
89 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
90 br'"')
91Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
92MapCRLF = re.compile(br'\r\n|\r|\n')
93Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
94Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +000095Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +000096 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000097
98
99
100class IMAP4:
101
Tim Peters07e99cb2001-01-14 23:47:14 +0000102 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000103
Tim Peters07e99cb2001-01-14 23:47:14 +0000104 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000105
Tim Peters07e99cb2001-01-14 23:47:14 +0000106 host - host's name (default: localhost);
107 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000108
Tim Peters07e99cb2001-01-14 23:47:14 +0000109 All IMAP4rev1 commands are supported by methods of the same
110 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000111
Tim Peters07e99cb2001-01-14 23:47:14 +0000112 All arguments to commands are converted to strings, except for
113 AUTHENTICATE, and the last argument to APPEND which is passed as
114 an IMAP4 literal. If necessary (the string contains any
115 non-printing characters or white-space and isn't enclosed with
116 either parentheses or double quotes) each string is quoted.
117 However, the 'password' argument to the LOGIN command is always
118 quoted. If you want to avoid having an argument string quoted
119 (eg: the 'flags' argument to STORE) then enclose the string in
120 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000121
Tim Peters07e99cb2001-01-14 23:47:14 +0000122 Each command returns a tuple: (type, [data, ...]) where 'type'
123 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000124 tagged response, or untagged results from command. Each 'data'
125 is either a string, or a tuple. If a tuple, then the first part
126 is the header of the response, and the second part contains
127 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 Errors raise the exception class <instance>.error("<reason>").
130 IMAP4 server errors raise <instance>.abort("<reason>"),
131 which is a sub-class of 'error'. Mailbox status changes
132 from READ-WRITE to READ-ONLY raise the exception class
133 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000134
Tim Peters07e99cb2001-01-14 23:47:14 +0000135 "error" exceptions imply a program error.
136 "abort" exceptions imply the connection should be reset, and
137 the command re-tried.
138 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000139
Piers Lauderd80ef022005-06-01 23:50:52 +0000140 Note: to use this module, you must read the RFCs pertaining to the
141 IMAP4 protocol, as the semantics of the arguments to each IMAP4
142 command are left to the invoker, not to mention the results. Also,
143 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000144 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000145
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 class error(Exception): pass # Logical errors - debug required
147 class abort(error): pass # Service errors - close and retry
148 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000149
Tim Peters07e99cb2001-01-14 23:47:14 +0000150 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 self.debug = Debug
152 self.state = 'LOGOUT'
153 self.literal = None # A literal argument to a command
154 self.tagged_commands = {} # Tagged commands awaiting response
155 self.untagged_responses = {} # {typ: [data, ...], ...}
156 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000157 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000163
Tim Peters07e99cb2001-01-14 23:47:14 +0000164 # Create unique tag for this session,
165 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000166
Piers Lauder2dfc1682005-07-05 04:20:07 +0000167 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000168 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000170 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 # Get server welcome message,
173 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000174
Tim Peters07e99cb2001-01-14 23:47:14 +0000175 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000176 self._cmd_log_len = 10
177 self._cmd_log_idx = 0
178 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000180 self._mesg('imaplib version %s' % __version__)
181 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000182
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000184 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000186 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000187 self.state = 'NONAUTH'
188 else:
189 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000190
Piers Lauderd80ef022005-06-01 23:50:52 +0000191 typ, dat = self.capability()
192 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000193 raise self.error('no CAPABILITY response from server')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000194 dat = str(dat[-1], "ASCII")
195 dat = dat.upper()
196 self.capabilities = tuple(dat.split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000197
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 if __debug__:
199 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000200 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000201
Tim Peters07e99cb2001-01-14 23:47:14 +0000202 for version in AllowedVersions:
203 if not version in self.capabilities:
204 continue
205 self.PROTOCOL_VERSION = version
206 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000207
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000209
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000210
Tim Peters07e99cb2001-01-14 23:47:14 +0000211 def __getattr__(self, attr):
212 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000213 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000214 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000216
217
218
Piers Lauder15e5d532001-07-20 10:52:06 +0000219 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000220
221
Christian Heimesfb5faf02008-11-05 19:39:50 +0000222 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000223 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000224
Piers Lauderf97b2d72002-06-05 22:31:57 +0000225 def open(self, host = '', port = IMAP4_PORT):
226 """Setup connection to remote server on "host:port"
227 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000228 This connection will be used by the routines:
229 read, readline, send, shutdown.
230 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000231 self.host = host
232 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000233 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000234 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000235
236
Piers Lauder15e5d532001-07-20 10:52:06 +0000237 def read(self, size):
238 """Read 'size' bytes from remote."""
Christian Heimesfb5faf02008-11-05 19:39:50 +0000239 chunks = []
240 read = 0
241 while read < size:
242 data = self.file.read(min(size-read, 4096))
243 if not data:
244 break
245 read += len(data)
246 chunks.append(data)
247 return b''.join(chunks)
Piers Lauder15e5d532001-07-20 10:52:06 +0000248
249
250 def readline(self):
251 """Read line from remote."""
252 return self.file.readline()
253
254
255 def send(self, data):
256 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000257 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000258
Piers Lauderf2d7d152002-02-22 01:15:17 +0000259
Piers Lauder15e5d532001-07-20 10:52:06 +0000260 def shutdown(self):
261 """Close I/O established in "open"."""
262 self.file.close()
Antoine Pitroud79f3c82010-11-09 23:10:33 +0000263 self.sock.shutdown(socket.SHUT_RDWR)
Piers Lauder15e5d532001-07-20 10:52:06 +0000264 self.sock.close()
265
266
267 def socket(self):
268 """Return socket instance used to connect to IMAP4 server.
269
270 socket = <instance>.socket()
271 """
272 return self.sock
273
274
275
276 # Utility methods
277
278
Tim Peters07e99cb2001-01-14 23:47:14 +0000279 def recent(self):
280 """Return most recent 'RECENT' responses if any exist,
281 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000282
Tim Peters07e99cb2001-01-14 23:47:14 +0000283 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000284
Tim Peters07e99cb2001-01-14 23:47:14 +0000285 'data' is None if no new messages,
286 else list of RECENT responses, most recent last.
287 """
288 name = 'RECENT'
289 typ, dat = self._untagged_response('OK', [None], name)
290 if dat[-1]:
291 return typ, dat
292 typ, dat = self.noop() # Prod server for response
293 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000294
295
Tim Peters07e99cb2001-01-14 23:47:14 +0000296 def response(self, code):
297 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000298
Tim Peters07e99cb2001-01-14 23:47:14 +0000299 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000300
Tim Peters07e99cb2001-01-14 23:47:14 +0000301 (code, [data]) = <instance>.response(code)
302 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000303 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000304
305
Guido van Rossum26367a01998-09-28 15:34:46 +0000306
Tim Peters07e99cb2001-01-14 23:47:14 +0000307 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000308
309
Tim Peters07e99cb2001-01-14 23:47:14 +0000310 def append(self, mailbox, flags, date_time, message):
311 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000312
Tim Peters07e99cb2001-01-14 23:47:14 +0000313 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000314
Tim Peters07e99cb2001-01-14 23:47:14 +0000315 All args except `message' can be None.
316 """
317 name = 'APPEND'
318 if not mailbox:
319 mailbox = 'INBOX'
320 if flags:
321 if (flags[0],flags[-1]) != ('(',')'):
322 flags = '(%s)' % flags
323 else:
324 flags = None
325 if date_time:
326 date_time = Time2Internaldate(date_time)
327 else:
328 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000329 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000330 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000331
332
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 def authenticate(self, mechanism, authobject):
334 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000335
Tim Peters07e99cb2001-01-14 23:47:14 +0000336 'mechanism' specifies which authentication mechanism is to
337 be used - it must appear in <instance>.capabilities in the
338 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000339
Tim Peters07e99cb2001-01-14 23:47:14 +0000340 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000341
Tim Peters07e99cb2001-01-14 23:47:14 +0000342 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000343
Tim Peters07e99cb2001-01-14 23:47:14 +0000344 It will be called to process server continuation responses.
345 It should return data that will be encoded and sent to server.
346 It should return None if the client abort response '*' should
347 be sent instead.
348 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000349 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000350 # XXX: shouldn't this code be removed, not commented out?
351 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000352 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000353 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000354 self.literal = _Authenticator(authobject).process
355 typ, dat = self._simple_command('AUTHENTICATE', mech)
356 if typ != 'OK':
357 raise self.error(dat[-1])
358 self.state = 'AUTH'
359 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000360
361
Piers Lauderd80ef022005-06-01 23:50:52 +0000362 def capability(self):
363 """(typ, [data]) = <instance>.capability()
364 Fetch capabilities list from server."""
365
366 name = 'CAPABILITY'
367 typ, dat = self._simple_command(name)
368 return self._untagged_response(typ, dat, name)
369
370
Tim Peters07e99cb2001-01-14 23:47:14 +0000371 def check(self):
372 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 (typ, [data]) = <instance>.check()
375 """
376 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000377
378
Tim Peters07e99cb2001-01-14 23:47:14 +0000379 def close(self):
380 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 Deleted messages are removed from writable mailbox.
383 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 (typ, [data]) = <instance>.close()
386 """
387 try:
388 typ, dat = self._simple_command('CLOSE')
389 finally:
390 self.state = 'AUTH'
391 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000392
393
Tim Peters07e99cb2001-01-14 23:47:14 +0000394 def copy(self, message_set, new_mailbox):
395 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
398 """
399 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000400
401
Tim Peters07e99cb2001-01-14 23:47:14 +0000402 def create(self, mailbox):
403 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 (typ, [data]) = <instance>.create(mailbox)
406 """
407 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000408
409
Tim Peters07e99cb2001-01-14 23:47:14 +0000410 def delete(self, mailbox):
411 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 (typ, [data]) = <instance>.delete(mailbox)
414 """
415 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000416
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000417 def deleteacl(self, mailbox, who):
418 """Delete the ACLs (remove any rights) set for who on mailbox.
419
420 (typ, [data]) = <instance>.deleteacl(mailbox, who)
421 """
422 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000423
Tim Peters07e99cb2001-01-14 23:47:14 +0000424 def expunge(self):
425 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000426
Tim Peters07e99cb2001-01-14 23:47:14 +0000427 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000428
Tim Peters07e99cb2001-01-14 23:47:14 +0000429 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 'data' is list of 'EXPUNGE'd message numbers in order received.
432 """
433 name = 'EXPUNGE'
434 typ, dat = self._simple_command(name)
435 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000436
437
Tim Peters07e99cb2001-01-14 23:47:14 +0000438 def fetch(self, message_set, message_parts):
439 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000440
Tim Peters07e99cb2001-01-14 23:47:14 +0000441 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
Tim Peters07e99cb2001-01-14 23:47:14 +0000443 'message_parts' should be a string of selected parts
444 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000445
Tim Peters07e99cb2001-01-14 23:47:14 +0000446 'data' are tuples of message part envelope and data.
447 """
448 name = 'FETCH'
449 typ, dat = self._simple_command(name, message_set, message_parts)
450 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000451
452
Piers Lauder15e5d532001-07-20 10:52:06 +0000453 def getacl(self, mailbox):
454 """Get the ACLs for a mailbox.
455
456 (typ, [data]) = <instance>.getacl(mailbox)
457 """
458 typ, dat = self._simple_command('GETACL', mailbox)
459 return self._untagged_response(typ, dat, 'ACL')
460
461
Piers Lauderd80ef022005-06-01 23:50:52 +0000462 def getannotation(self, mailbox, entry, attribute):
463 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
464 Retrieve ANNOTATIONs."""
465
466 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
467 return self._untagged_response(typ, dat, 'ANNOTATION')
468
469
Piers Lauder3fca2912002-06-17 07:07:20 +0000470 def getquota(self, root):
471 """Get the quota root's resource usage and limits.
472
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000473 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000474
475 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000476 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000477 typ, dat = self._simple_command('GETQUOTA', root)
478 return self._untagged_response(typ, dat, 'QUOTA')
479
480
481 def getquotaroot(self, mailbox):
482 """Get the list of quota roots for the named mailbox.
483
484 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000485 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000486 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000487 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
488 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000489 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000490
491
Tim Peters07e99cb2001-01-14 23:47:14 +0000492 def list(self, directory='""', pattern='*'):
493 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000494
Tim Peters07e99cb2001-01-14 23:47:14 +0000495 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000496
Tim Peters07e99cb2001-01-14 23:47:14 +0000497 'data' is list of LIST responses.
498 """
499 name = 'LIST'
500 typ, dat = self._simple_command(name, directory, pattern)
501 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000502
503
Tim Peters07e99cb2001-01-14 23:47:14 +0000504 def login(self, user, password):
505 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000506
Tim Peters07e99cb2001-01-14 23:47:14 +0000507 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000508
Tim Peters07e99cb2001-01-14 23:47:14 +0000509 NB: 'password' will be quoted.
510 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000511 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
512 if typ != 'OK':
513 raise self.error(dat[-1])
514 self.state = 'AUTH'
515 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000516
517
Piers Laudere0273de2002-11-22 05:53:04 +0000518 def login_cram_md5(self, user, password):
519 """ Force use of CRAM-MD5 authentication.
520
521 (typ, [data]) = <instance>.login_cram_md5(user, password)
522 """
523 self.user, self.password = user, password
524 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
525
526
527 def _CRAM_MD5_AUTH(self, challenge):
528 """ Authobject to use with CRAM-MD5 authentication. """
529 import hmac
530 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
531
532
Tim Peters07e99cb2001-01-14 23:47:14 +0000533 def logout(self):
534 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000535
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000537
Tim Peters07e99cb2001-01-14 23:47:14 +0000538 Returns server 'BYE' response.
539 """
540 self.state = 'LOGOUT'
541 try: typ, dat = self._simple_command('LOGOUT')
542 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000543 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000544 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000545 return 'BYE', self.untagged_responses['BYE']
546 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000547
548
Tim Peters07e99cb2001-01-14 23:47:14 +0000549 def lsub(self, directory='""', pattern='*'):
550 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000551
Tim Peters07e99cb2001-01-14 23:47:14 +0000552 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000553
Tim Peters07e99cb2001-01-14 23:47:14 +0000554 'data' are tuples of message part envelope and data.
555 """
556 name = 'LSUB'
557 typ, dat = self._simple_command(name, directory, pattern)
558 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000559
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000560 def myrights(self, mailbox):
561 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
562
563 (typ, [data]) = <instance>.myrights(mailbox)
564 """
565 typ,dat = self._simple_command('MYRIGHTS', mailbox)
566 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000567
Piers Lauder15e5d532001-07-20 10:52:06 +0000568 def namespace(self):
569 """ Returns IMAP namespaces ala rfc2342
570
571 (typ, [data, ...]) = <instance>.namespace()
572 """
573 name = 'NAMESPACE'
574 typ, dat = self._simple_command(name)
575 return self._untagged_response(typ, dat, name)
576
577
Tim Peters07e99cb2001-01-14 23:47:14 +0000578 def noop(self):
579 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000580
Piers Laudere0273de2002-11-22 05:53:04 +0000581 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000582 """
583 if __debug__:
584 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000585 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000586 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000587
588
Tim Peters07e99cb2001-01-14 23:47:14 +0000589 def partial(self, message_num, message_part, start, length):
590 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000591
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000593
Tim Peters07e99cb2001-01-14 23:47:14 +0000594 'data' is tuple of message part envelope and data.
595 """
596 name = 'PARTIAL'
597 typ, dat = self._simple_command(name, message_num, message_part, start, length)
598 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000599
600
Piers Laudere0273de2002-11-22 05:53:04 +0000601 def proxyauth(self, user):
602 """Assume authentication as "user".
603
604 Allows an authorised administrator to proxy into any user's
605 mailbox.
606
607 (typ, [data]) = <instance>.proxyauth(user)
608 """
609
610 name = 'PROXYAUTH'
611 return self._simple_command('PROXYAUTH', user)
612
613
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 def rename(self, oldmailbox, newmailbox):
615 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000616
Piers Laudere0273de2002-11-22 05:53:04 +0000617 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000618 """
619 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000620
621
Tim Peters07e99cb2001-01-14 23:47:14 +0000622 def search(self, charset, *criteria):
623 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000624
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000625 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000626
Tim Peters07e99cb2001-01-14 23:47:14 +0000627 'data' is space separated list of matching message numbers.
628 """
629 name = 'SEARCH'
630 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000631 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000632 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000633 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000634 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
636
Piers Lauder14f39402005-08-31 10:46:29 +0000637 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000638 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000639
Tim Peters07e99cb2001-01-14 23:47:14 +0000640 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000641
Piers Lauder14f39402005-08-31 10:46:29 +0000642 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000643
Tim Peters07e99cb2001-01-14 23:47:14 +0000644 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000645
646 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
647 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000648 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 self.untagged_responses = {} # Flush old responses.
650 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000651 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000652 name = 'EXAMINE'
653 else:
654 name = 'SELECT'
655 typ, dat = self._simple_command(name, mailbox)
656 if typ != 'OK':
657 self.state = 'AUTH' # Might have been 'SELECTED'
658 return typ, dat
659 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000660 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 and not readonly:
662 if __debug__:
663 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000664 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000665 raise self.readonly('%s is not writable' % mailbox)
666 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000667
668
Piers Lauder15e5d532001-07-20 10:52:06 +0000669 def setacl(self, mailbox, who, what):
670 """Set a mailbox acl.
671
Piers Lauderf167dc32004-03-25 00:12:21 +0000672 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000673 """
674 return self._simple_command('SETACL', mailbox, who, what)
675
676
Piers Lauderd80ef022005-06-01 23:50:52 +0000677 def setannotation(self, *args):
678 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
679 Set ANNOTATIONs."""
680
681 typ, dat = self._simple_command('SETANNOTATION', *args)
682 return self._untagged_response(typ, dat, 'ANNOTATION')
683
684
Piers Lauder3fca2912002-06-17 07:07:20 +0000685 def setquota(self, root, limits):
686 """Set the quota root's resource limits.
687
688 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000689 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000690 typ, dat = self._simple_command('SETQUOTA', root, limits)
691 return self._untagged_response(typ, dat, 'QUOTA')
692
693
Piers Lauder15e5d532001-07-20 10:52:06 +0000694 def sort(self, sort_criteria, charset, *search_criteria):
695 """IMAP4rev1 extension SORT command.
696
697 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
698 """
699 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000700 #if not name in self.capabilities: # Let the server decide!
701 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000702 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000703 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000704 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000705 return self._untagged_response(typ, dat, name)
706
707
Tim Peters07e99cb2001-01-14 23:47:14 +0000708 def status(self, mailbox, names):
709 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000710
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 (typ, [data]) = <instance>.status(mailbox, names)
712 """
713 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000714 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000715 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 typ, dat = self._simple_command(name, mailbox, names)
717 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000718
719
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 def store(self, message_set, command, flags):
721 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000722
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 (typ, [data]) = <instance>.store(message_set, command, flags)
724 """
725 if (flags[0],flags[-1]) != ('(',')'):
726 flags = '(%s)' % flags # Avoid quoting the flags
727 typ, dat = self._simple_command('STORE', message_set, command, flags)
728 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000729
730
Tim Peters07e99cb2001-01-14 23:47:14 +0000731 def subscribe(self, mailbox):
732 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000733
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 (typ, [data]) = <instance>.subscribe(mailbox)
735 """
736 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000737
738
Martin v. Löwisd8921372003-11-10 06:44:44 +0000739 def thread(self, threading_algorithm, charset, *search_criteria):
740 """IMAPrev1 extension THREAD command.
741
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000742 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000743 """
744 name = 'THREAD'
745 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
746 return self._untagged_response(typ, dat, name)
747
748
Tim Peters07e99cb2001-01-14 23:47:14 +0000749 def uid(self, command, *args):
750 """Execute "command arg ..." with messages identified by UID,
751 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000752
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000754
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 Returns response appropriate to 'command'.
756 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000757 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000758 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 raise self.error("Unknown IMAP4 UID command: %s" % command)
760 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000761 raise self.error("command %s illegal in state %s, "
762 "only allowed in states %s" %
763 (command, self.state,
764 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000765 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000766 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000767 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000768 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000769 else:
770 name = 'FETCH'
771 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000772
773
Tim Peters07e99cb2001-01-14 23:47:14 +0000774 def unsubscribe(self, mailbox):
775 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000776
Tim Peters07e99cb2001-01-14 23:47:14 +0000777 (typ, [data]) = <instance>.unsubscribe(mailbox)
778 """
779 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000780
781
Tim Peters07e99cb2001-01-14 23:47:14 +0000782 def xatom(self, name, *args):
783 """Allow simple extension commands
784 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000785
Piers Lauder15e5d532001-07-20 10:52:06 +0000786 Assumes command is legal in current state.
787
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000789
790 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000791 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000792 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000793 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000794 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000795 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000796 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000797 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000798
799
800
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000802
803
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000805 if dat is None:
806 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 ur = self.untagged_responses
808 if __debug__:
809 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000810 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000811 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000812 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 ur[typ].append(dat)
814 else:
815 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000816
817
Tim Peters07e99cb2001-01-14 23:47:14 +0000818 def _check_bye(self):
819 bye = self.untagged_responses.get('BYE')
820 if bye:
Antoine Pitroudac47912010-11-10 00:18:40 +0000821 raise self.abort(bye[-1].decode('ascii', 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000822
823
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000825
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 if self.state not in Commands[name]:
827 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000828 raise self.error("command %s illegal in state %s, "
829 "only allowed in states %s" %
830 (name, self.state,
831 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000834 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000836
Raymond Hettinger54f02222002-06-01 14:18:47 +0000837 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000838 and not self.is_readonly:
839 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000840
Tim Peters07e99cb2001-01-14 23:47:14 +0000841 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000842 name = bytes(name, 'ASCII')
843 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 for arg in args:
845 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000846 if isinstance(arg, str):
847 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000848 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 literal = self.literal
851 if literal is not None:
852 self.literal = None
853 if type(literal) is type(self._command):
854 literator = literal
855 else:
856 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000857 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000858
Tim Peters07e99cb2001-01-14 23:47:14 +0000859 if __debug__:
860 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000861 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000863 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000864
Tim Peters07e99cb2001-01-14 23:47:14 +0000865 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000866 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000867 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 if literal is None:
871 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000872
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 while 1:
874 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 while self._get_response():
877 if self.tagged_commands[tag]: # BAD/NO?
878 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 if literator:
883 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 if __debug__:
886 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000887 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000890 self.send(literal)
891 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000892 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000894
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 if not literator:
896 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000897
Tim Peters07e99cb2001-01-14 23:47:14 +0000898 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000902 # BYE is expected after LOGOUT
903 if name != 'LOGOUT':
904 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 try:
906 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000907 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000909 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +0000911 if name != 'LOGOUT':
912 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 if typ == 'BAD':
914 raise self.error('%s command error: %s %s' % (name, typ, data))
915 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000916
917
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000919
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 # Read response and store.
921 #
922 # Returns None for continuation responses,
923 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 if self._match(self.tagre, resp):
930 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000931 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000935 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 dat = self.mo.group('data')
937 self.tagged_commands[tag] = (typ, [dat])
938 else:
939 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 if not self._match(Untagged_response, resp):
944 if self._match(Untagged_status, resp):
945 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 if self.mo is None:
948 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000949
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 if self._match(Continuation, resp):
951 self.continuation_response = self.mo.group('data')
952 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000953
Tim Peters07e99cb2001-01-14 23:47:14 +0000954 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000957 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000959 if dat is None: dat = b'' # Null untagged response
960 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000961
Tim Peters07e99cb2001-01-14 23:47:14 +0000962 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000963
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000965
Tim Peters07e99cb2001-01-14 23:47:14 +0000966 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000967
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000968 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 if __debug__:
970 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000971 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000972 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000973
Tim Peters07e99cb2001-01-14 23:47:14 +0000974 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000975
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000983
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000985
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000987 typ = self.mo.group('type')
988 typ = str(typ, "ASCII")
989 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 if __debug__:
992 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000993 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000996
997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 while 1:
1001 result = self.tagged_commands[tag]
1002 if result is not None:
1003 del self.tagged_commands[tag]
1004 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001005
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 # Some have reported "unexpected response" exceptions.
1007 # Note that ignoring them here causes loops.
1008 # Instead, send me details of the unexpected response and
1009 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001010
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 try:
1012 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001013 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 if __debug__:
1015 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001016 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001017 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001018
1019
Tim Peters07e99cb2001-01-14 23:47:14 +00001020 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Piers Lauder15e5d532001-07-20 10:52:06 +00001022 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001023 if not line:
1024 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001027 if not line.endswith(b'\r\n'):
1028 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 line = line[:-2]
1031 if __debug__:
1032 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001033 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001035 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
1038
Tim Peters07e99cb2001-01-14 23:47:14 +00001039 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001040
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 # Run compiled regular expression match method on 's'.
1042 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001043
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 self.mo = cre.match(s)
1045 if __debug__:
1046 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001047 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001049
1050
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001052
Christian Heimesfb5faf02008-11-05 19:39:50 +00001053 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 self.tagnum = self.tagnum + 1
1055 self.tagged_commands[tag] = None
1056 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001057
1058
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001060
Antoine Pitroub1436f12010-11-09 22:55:55 +00001061 arg = arg.replace('\\', '\\\\')
1062 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001063
Antoine Pitroub1436f12010-11-09 22:55:55 +00001064 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001065
1066
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001068
Guido van Rossum68468eb2003-02-27 20:14:51 +00001069 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001070
1071
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001073 if typ == 'NO':
1074 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001075 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001076 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001077 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 if __debug__:
1079 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001080 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001081 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001082
1083
Piers Lauderf2d7d152002-02-22 01:15:17 +00001084 if __debug__:
1085
1086 def _mesg(self, s, secs=None):
1087 if secs is None:
1088 secs = time.time()
1089 tm = time.strftime('%M:%S', time.localtime(secs))
1090 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1091 sys.stderr.flush()
1092
1093 def _dump_ur(self, dict):
1094 # Dump untagged responses (in `dict').
1095 l = dict.items()
1096 if not l: return
1097 t = '\n\t\t'
1098 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1099 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1100
1101 def _log(self, line):
1102 # Keep log of last `_cmd_log_len' interactions for debugging.
1103 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1104 self._cmd_log_idx += 1
1105 if self._cmd_log_idx >= self._cmd_log_len:
1106 self._cmd_log_idx = 0
1107
1108 def print_log(self):
1109 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1110 i, n = self._cmd_log_idx, self._cmd_log_len
1111 while n:
1112 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001113 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001114 except:
1115 pass
1116 i += 1
1117 if i >= self._cmd_log_len:
1118 i = 0
1119 n -= 1
1120
1121
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001122
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001123try:
1124 import ssl
1125except ImportError:
1126 pass
1127else:
1128 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001129
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001130 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001131
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001132 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001133
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001134 host - host's name (default: localhost);
1135 port - port number (default: standard IMAP4 SSL port).
1136 keyfile - PEM formatted file that contains your private key (default: None);
1137 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001138
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001139 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001140 """
Piers Laudera4f83132002-03-08 01:53:24 +00001141
1142
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001143 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1144 self.keyfile = keyfile
1145 self.certfile = certfile
1146 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001147
Christian Heimesfb5faf02008-11-05 19:39:50 +00001148 def _create_socket(self):
1149 sock = IMAP4._create_socket(self)
1150 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001151
Christian Heimesfb5faf02008-11-05 19:39:50 +00001152 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001153 """Setup connection to remote server on "host:port".
1154 (default: localhost:standard IMAP4 SSL port).
1155 This connection will be used by the routines:
1156 read, readline, send, shutdown.
1157 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001158 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001159
1160 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001161
1162
Piers Laudere0273de2002-11-22 05:53:04 +00001163class IMAP4_stream(IMAP4):
1164
1165 """IMAP4 client class over a stream
1166
1167 Instantiate with: IMAP4_stream(command)
1168
Christian Heimesfb5faf02008-11-05 19:39:50 +00001169 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001170
1171 for more documentation see the docstring of the parent class IMAP4.
1172 """
1173
1174
1175 def __init__(self, command):
1176 self.command = command
1177 IMAP4.__init__(self)
1178
1179
1180 def open(self, host = None, port = None):
1181 """Setup a stream connection.
1182 This connection will be used by the routines:
1183 read, readline, send, shutdown.
1184 """
1185 self.host = None # For compatibility with parent class
1186 self.port = None
1187 self.sock = None
1188 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001189 self.process = subprocess.Popen(self.command,
1190 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1191 shell=True, close_fds=True)
1192 self.writefile = self.process.stdin
1193 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001194
1195 def read(self, size):
1196 """Read 'size' bytes from remote."""
1197 return self.readfile.read(size)
1198
1199
1200 def readline(self):
1201 """Read line from remote."""
1202 return self.readfile.readline()
1203
1204
1205 def send(self, data):
1206 """Send data to remote."""
1207 self.writefile.write(data)
1208 self.writefile.flush()
1209
1210
1211 def shutdown(self):
1212 """Close I/O established in "open"."""
1213 self.readfile.close()
1214 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001215 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001216
1217
1218
Guido van Rossumeda960a1998-06-18 14:24:28 +00001219class _Authenticator:
1220
Tim Peters07e99cb2001-01-14 23:47:14 +00001221 """Private class to provide en/decoding
1222 for base64-based authentication conversation.
1223 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001224
Tim Peters07e99cb2001-01-14 23:47:14 +00001225 def __init__(self, mechinst):
1226 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001227
Tim Peters07e99cb2001-01-14 23:47:14 +00001228 def process(self, data):
1229 ret = self.mech(self.decode(data))
1230 if ret is None:
1231 return '*' # Abort conversation
1232 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001233
Tim Peters07e99cb2001-01-14 23:47:14 +00001234 def encode(self, inp):
1235 #
1236 # Invoke binascii.b2a_base64 iteratively with
1237 # short even length buffers, strip the trailing
1238 # line feed from the result and append. "Even"
1239 # means a number that factors to both 6 and 8,
1240 # so when it gets to the end of the 8-bit input
1241 # there's no partial 6-bit output.
1242 #
1243 oup = ''
1244 while inp:
1245 if len(inp) > 48:
1246 t = inp[:48]
1247 inp = inp[48:]
1248 else:
1249 t = inp
1250 inp = ''
1251 e = binascii.b2a_base64(t)
1252 if e:
1253 oup = oup + e[:-1]
1254 return oup
1255
1256 def decode(self, inp):
1257 if not inp:
1258 return ''
1259 return binascii.a2b_base64(inp)
1260
Guido van Rossumeda960a1998-06-18 14:24:28 +00001261
1262
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001263Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001264 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001265
1266def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001267 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001268
Tim Peters07e99cb2001-01-14 23:47:14 +00001269 Returns Python time module tuple.
1270 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001271
Tim Peters07e99cb2001-01-14 23:47:14 +00001272 mo = InternalDate.match(resp)
1273 if not mo:
1274 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001275
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 mon = Mon2num[mo.group('mon')]
1277 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001278
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001279 day = int(mo.group('day'))
1280 year = int(mo.group('year'))
1281 hour = int(mo.group('hour'))
1282 min = int(mo.group('min'))
1283 sec = int(mo.group('sec'))
1284 zoneh = int(mo.group('zoneh'))
1285 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001286
Tim Peters07e99cb2001-01-14 23:47:14 +00001287 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001288
Tim Peters07e99cb2001-01-14 23:47:14 +00001289 zone = (zoneh*60 + zonem)*60
1290 if zonen == '-':
1291 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001292
Tim Peters07e99cb2001-01-14 23:47:14 +00001293 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001294
Tim Peters07e99cb2001-01-14 23:47:14 +00001295 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001296
Tim Peters07e99cb2001-01-14 23:47:14 +00001297 # Following is necessary because the time module has no 'mkgmtime'.
1298 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001299
Tim Peters07e99cb2001-01-14 23:47:14 +00001300 lt = time.localtime(utc)
1301 if time.daylight and lt[-1]:
1302 zone = zone + time.altzone
1303 else:
1304 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001305
Tim Peters07e99cb2001-01-14 23:47:14 +00001306 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001307
1308
1309
1310def Int2AP(num):
1311
Tim Peters07e99cb2001-01-14 23:47:14 +00001312 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001313
Christian Heimesfb5faf02008-11-05 19:39:50 +00001314 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001315 num = int(abs(num))
1316 while num:
1317 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001318 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001319 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001320
1321
1322
1323def ParseFlags(resp):
1324
Tim Peters07e99cb2001-01-14 23:47:14 +00001325 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001326
Tim Peters07e99cb2001-01-14 23:47:14 +00001327 mo = Flags.match(resp)
1328 if not mo:
1329 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001330
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001331 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001332
1333
1334def Time2Internaldate(date_time):
1335
Tim Peters07e99cb2001-01-14 23:47:14 +00001336 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Tim Peters07e99cb2001-01-14 23:47:14 +00001338 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1339 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001340
Fred Drakedb519202002-01-05 17:17:09 +00001341 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001342 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001343 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001345 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001347 else:
1348 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001349
Tim Peters07e99cb2001-01-14 23:47:14 +00001350 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1351 if dt[0] == '0':
1352 dt = ' ' + dt[1:]
1353 if time.daylight and tt[-1]:
1354 zone = -time.altzone
1355 else:
1356 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001357 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
1359
1360
Guido van Rossum8c062211999-12-13 23:27:45 +00001361if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001362
Piers Laudere0273de2002-11-22 05:53:04 +00001363 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1364 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1365 # to test the IMAP4_stream class
1366
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001367 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001368
Tim Peters07e99cb2001-01-14 23:47:14 +00001369 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001370 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001371 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001372 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001373
Piers Laudere0273de2002-11-22 05:53:04 +00001374 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001375 for opt,val in optlist:
1376 if opt == '-d':
1377 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001378 elif opt == '-s':
1379 stream_command = val
1380 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001381
Tim Peters07e99cb2001-01-14 23:47:14 +00001382 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001383
Tim Peters07e99cb2001-01-14 23:47:14 +00001384 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001385
Tim Peters07e99cb2001-01-14 23:47:14 +00001386 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001387 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001388
Piers Lauder47404ff2003-04-29 23:40:59 +00001389 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 +00001390 test_seq1 = (
1391 ('login', (USER, PASSWD)),
1392 ('create', ('/tmp/xxx 1',)),
1393 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1394 ('CREATE', ('/tmp/yyz 2',)),
1395 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1396 ('list', ('/tmp', 'yy*')),
1397 ('select', ('/tmp/yyz 2',)),
1398 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001399 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001400 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001401 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001402 ('expunge', ()),
1403 ('recent', ()),
1404 ('close', ()),
1405 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001406
Tim Peters07e99cb2001-01-14 23:47:14 +00001407 test_seq2 = (
1408 ('select', ()),
1409 ('response',('UIDVALIDITY',)),
1410 ('uid', ('SEARCH', 'ALL')),
1411 ('response', ('EXISTS',)),
1412 ('append', (None, None, None, test_mesg)),
1413 ('recent', ()),
1414 ('logout', ()),
1415 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001416
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001418 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001419 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001420 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001421 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001422 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001425 if stream_command:
1426 M = IMAP4_stream(stream_command)
1427 else:
1428 M = IMAP4(host)
1429 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001430 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001431 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001432 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001433
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 for cmd,args in test_seq1:
1435 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001436
Tim Peters07e99cb2001-01-14 23:47:14 +00001437 for ml in run('list', ('/tmp/', 'yy%')):
1438 mo = re.match(r'.*"([^"]+)"$', ml)
1439 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001440 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001442
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 for cmd,args in test_seq2:
1444 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001445
Tim Peters07e99cb2001-01-14 23:47:14 +00001446 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1447 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001448
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001449 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001450 if not uid: continue
1451 run('uid', ('FETCH', '%s' % uid[-1],
1452 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001453
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001454 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001455
Tim Peters07e99cb2001-01-14 23:47:14 +00001456 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001457 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001458
Tim Peters07e99cb2001-01-14 23:47:14 +00001459 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001460 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001461If you would like to see debugging output,
1462try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001463''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001464
Tim Peters07e99cb2001-01-14 23:47:14 +00001465 raise