blob: ec476572abe9f176363cf33732a5f9b52eb08594 [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
Christian Heimesfb5faf02008-11-05 19:39:50 +0000150 mustquote = re.compile(br"[^\w!#$%&'*+,.:;<=>?^`|~-]", re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000151
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 self.debug = Debug
154 self.state = 'LOGOUT'
155 self.literal = None # A literal argument to a command
156 self.tagged_commands = {} # Tagged commands awaiting response
157 self.untagged_responses = {} # {typ: [data, ...], ...}
158 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000159 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000163
Tim Peters07e99cb2001-01-14 23:47:14 +0000164 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000165
Tim Peters07e99cb2001-01-14 23:47:14 +0000166 # Create unique tag for this session,
167 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000168
Piers Lauder2dfc1682005-07-05 04:20:07 +0000169 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000170 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000172 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000173
Tim Peters07e99cb2001-01-14 23:47:14 +0000174 # Get server welcome message,
175 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000176
Tim Peters07e99cb2001-01-14 23:47:14 +0000177 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000178 self._cmd_log_len = 10
179 self._cmd_log_idx = 0
180 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000181 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000182 self._mesg('imaplib version %s' % __version__)
183 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000184
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000186 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000187 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000188 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 self.state = 'NONAUTH'
190 else:
191 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000192
Piers Lauderd80ef022005-06-01 23:50:52 +0000193 typ, dat = self.capability()
194 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 raise self.error('no CAPABILITY response from server')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000196 dat = str(dat[-1], "ASCII")
197 dat = dat.upper()
198 self.capabilities = tuple(dat.split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000199
Tim Peters07e99cb2001-01-14 23:47:14 +0000200 if __debug__:
201 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000202 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000203
Tim Peters07e99cb2001-01-14 23:47:14 +0000204 for version in AllowedVersions:
205 if not version in self.capabilities:
206 continue
207 self.PROTOCOL_VERSION = version
208 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000209
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000211
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000212
Tim Peters07e99cb2001-01-14 23:47:14 +0000213 def __getattr__(self, attr):
214 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000215 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000216 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000217 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000218
219
220
Piers Lauder15e5d532001-07-20 10:52:06 +0000221 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000222
223
Christian Heimesfb5faf02008-11-05 19:39:50 +0000224 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000225 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000226
Piers Lauderf97b2d72002-06-05 22:31:57 +0000227 def open(self, host = '', port = IMAP4_PORT):
228 """Setup connection to remote server on "host:port"
229 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000230 This connection will be used by the routines:
231 read, readline, send, shutdown.
232 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000233 self.host = host
234 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000235 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000236 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000237
238
Piers Lauder15e5d532001-07-20 10:52:06 +0000239 def read(self, size):
240 """Read 'size' bytes from remote."""
Christian Heimesfb5faf02008-11-05 19:39:50 +0000241 chunks = []
242 read = 0
243 while read < size:
244 data = self.file.read(min(size-read, 4096))
245 if not data:
246 break
247 read += len(data)
248 chunks.append(data)
249 return b''.join(chunks)
Piers Lauder15e5d532001-07-20 10:52:06 +0000250
251
252 def readline(self):
253 """Read line from remote."""
254 return self.file.readline()
255
256
257 def send(self, data):
258 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000259 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000260
Piers Lauderf2d7d152002-02-22 01:15:17 +0000261
Piers Lauder15e5d532001-07-20 10:52:06 +0000262 def shutdown(self):
263 """Close I/O established in "open"."""
264 self.file.close()
265 self.sock.close()
266
267
268 def socket(self):
269 """Return socket instance used to connect to IMAP4 server.
270
271 socket = <instance>.socket()
272 """
273 return self.sock
274
275
276
277 # Utility methods
278
279
Tim Peters07e99cb2001-01-14 23:47:14 +0000280 def recent(self):
281 """Return most recent 'RECENT' responses if any exist,
282 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000283
Tim Peters07e99cb2001-01-14 23:47:14 +0000284 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000285
Tim Peters07e99cb2001-01-14 23:47:14 +0000286 'data' is None if no new messages,
287 else list of RECENT responses, most recent last.
288 """
289 name = 'RECENT'
290 typ, dat = self._untagged_response('OK', [None], name)
291 if dat[-1]:
292 return typ, dat
293 typ, dat = self.noop() # Prod server for response
294 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000295
296
Tim Peters07e99cb2001-01-14 23:47:14 +0000297 def response(self, code):
298 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000299
Tim Peters07e99cb2001-01-14 23:47:14 +0000300 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000301
Tim Peters07e99cb2001-01-14 23:47:14 +0000302 (code, [data]) = <instance>.response(code)
303 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000304 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000305
306
Guido van Rossum26367a01998-09-28 15:34:46 +0000307
Tim Peters07e99cb2001-01-14 23:47:14 +0000308 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000309
310
Tim Peters07e99cb2001-01-14 23:47:14 +0000311 def append(self, mailbox, flags, date_time, message):
312 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000313
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000315
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 All args except `message' can be None.
317 """
318 name = 'APPEND'
319 if not mailbox:
320 mailbox = 'INBOX'
321 if flags:
322 if (flags[0],flags[-1]) != ('(',')'):
323 flags = '(%s)' % flags
324 else:
325 flags = None
326 if date_time:
327 date_time = Time2Internaldate(date_time)
328 else:
329 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000330 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000331 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000332
333
Tim Peters07e99cb2001-01-14 23:47:14 +0000334 def authenticate(self, mechanism, authobject):
335 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000336
Tim Peters07e99cb2001-01-14 23:47:14 +0000337 'mechanism' specifies which authentication mechanism is to
338 be used - it must appear in <instance>.capabilities in the
339 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000342
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 It will be called to process server continuation responses.
346 It should return data that will be encoded and sent to server.
347 It should return None if the client abort response '*' should
348 be sent instead.
349 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000350 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000351 # XXX: shouldn't this code be removed, not commented out?
352 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000353 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000354 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 self.literal = _Authenticator(authobject).process
356 typ, dat = self._simple_command('AUTHENTICATE', mech)
357 if typ != 'OK':
358 raise self.error(dat[-1])
359 self.state = 'AUTH'
360 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000361
362
Piers Lauderd80ef022005-06-01 23:50:52 +0000363 def capability(self):
364 """(typ, [data]) = <instance>.capability()
365 Fetch capabilities list from server."""
366
367 name = 'CAPABILITY'
368 typ, dat = self._simple_command(name)
369 return self._untagged_response(typ, dat, name)
370
371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 def check(self):
373 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000374
Tim Peters07e99cb2001-01-14 23:47:14 +0000375 (typ, [data]) = <instance>.check()
376 """
377 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 def close(self):
381 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 Deleted messages are removed from writable mailbox.
384 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
Tim Peters07e99cb2001-01-14 23:47:14 +0000386 (typ, [data]) = <instance>.close()
387 """
388 try:
389 typ, dat = self._simple_command('CLOSE')
390 finally:
391 self.state = 'AUTH'
392 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 def copy(self, message_set, new_mailbox):
396 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
399 """
400 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000401
402
Tim Peters07e99cb2001-01-14 23:47:14 +0000403 def create(self, mailbox):
404 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000405
Tim Peters07e99cb2001-01-14 23:47:14 +0000406 (typ, [data]) = <instance>.create(mailbox)
407 """
408 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000409
410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 def delete(self, mailbox):
412 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000413
Tim Peters07e99cb2001-01-14 23:47:14 +0000414 (typ, [data]) = <instance>.delete(mailbox)
415 """
416 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000418 def deleteacl(self, mailbox, who):
419 """Delete the ACLs (remove any rights) set for who on mailbox.
420
421 (typ, [data]) = <instance>.deleteacl(mailbox, who)
422 """
423 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000424
Tim Peters07e99cb2001-01-14 23:47:14 +0000425 def expunge(self):
426 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000431
Tim Peters07e99cb2001-01-14 23:47:14 +0000432 'data' is list of 'EXPUNGE'd message numbers in order received.
433 """
434 name = 'EXPUNGE'
435 typ, dat = self._simple_command(name)
436 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000437
438
Tim Peters07e99cb2001-01-14 23:47:14 +0000439 def fetch(self, message_set, message_parts):
440 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Tim Peters07e99cb2001-01-14 23:47:14 +0000442 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 'message_parts' should be a string of selected parts
445 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 'data' are tuples of message part envelope and data.
448 """
449 name = 'FETCH'
450 typ, dat = self._simple_command(name, message_set, message_parts)
451 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000452
453
Piers Lauder15e5d532001-07-20 10:52:06 +0000454 def getacl(self, mailbox):
455 """Get the ACLs for a mailbox.
456
457 (typ, [data]) = <instance>.getacl(mailbox)
458 """
459 typ, dat = self._simple_command('GETACL', mailbox)
460 return self._untagged_response(typ, dat, 'ACL')
461
462
Piers Lauderd80ef022005-06-01 23:50:52 +0000463 def getannotation(self, mailbox, entry, attribute):
464 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
465 Retrieve ANNOTATIONs."""
466
467 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
468 return self._untagged_response(typ, dat, 'ANNOTATION')
469
470
Piers Lauder3fca2912002-06-17 07:07:20 +0000471 def getquota(self, root):
472 """Get the quota root's resource usage and limits.
473
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000474 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000475
476 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000477 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000478 typ, dat = self._simple_command('GETQUOTA', root)
479 return self._untagged_response(typ, dat, 'QUOTA')
480
481
482 def getquotaroot(self, mailbox):
483 """Get the list of quota roots for the named mailbox.
484
485 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000486 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000487 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000488 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
489 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000490 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000491
492
Tim Peters07e99cb2001-01-14 23:47:14 +0000493 def list(self, directory='""', pattern='*'):
494 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 'data' is list of LIST responses.
499 """
500 name = 'LIST'
501 typ, dat = self._simple_command(name, directory, pattern)
502 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000503
504
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 def login(self, user, password):
506 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 NB: 'password' will be quoted.
511 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
513 if typ != 'OK':
514 raise self.error(dat[-1])
515 self.state = 'AUTH'
516 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000517
518
Piers Laudere0273de2002-11-22 05:53:04 +0000519 def login_cram_md5(self, user, password):
520 """ Force use of CRAM-MD5 authentication.
521
522 (typ, [data]) = <instance>.login_cram_md5(user, password)
523 """
524 self.user, self.password = user, password
525 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
526
527
528 def _CRAM_MD5_AUTH(self, challenge):
529 """ Authobject to use with CRAM-MD5 authentication. """
530 import hmac
531 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
532
533
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 def logout(self):
535 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000538
Tim Peters07e99cb2001-01-14 23:47:14 +0000539 Returns server 'BYE' response.
540 """
541 self.state = 'LOGOUT'
542 try: typ, dat = self._simple_command('LOGOUT')
543 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000544 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000545 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000546 return 'BYE', self.untagged_responses['BYE']
547 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000548
549
Tim Peters07e99cb2001-01-14 23:47:14 +0000550 def lsub(self, directory='""', pattern='*'):
551 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000552
Tim Peters07e99cb2001-01-14 23:47:14 +0000553 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000554
Tim Peters07e99cb2001-01-14 23:47:14 +0000555 'data' are tuples of message part envelope and data.
556 """
557 name = 'LSUB'
558 typ, dat = self._simple_command(name, directory, pattern)
559 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000560
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000561 def myrights(self, mailbox):
562 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
563
564 (typ, [data]) = <instance>.myrights(mailbox)
565 """
566 typ,dat = self._simple_command('MYRIGHTS', mailbox)
567 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000568
Piers Lauder15e5d532001-07-20 10:52:06 +0000569 def namespace(self):
570 """ Returns IMAP namespaces ala rfc2342
571
572 (typ, [data, ...]) = <instance>.namespace()
573 """
574 name = 'NAMESPACE'
575 typ, dat = self._simple_command(name)
576 return self._untagged_response(typ, dat, name)
577
578
Tim Peters07e99cb2001-01-14 23:47:14 +0000579 def noop(self):
580 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000581
Piers Laudere0273de2002-11-22 05:53:04 +0000582 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 """
584 if __debug__:
585 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000586 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000587 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000588
589
Tim Peters07e99cb2001-01-14 23:47:14 +0000590 def partial(self, message_num, message_part, start, length):
591 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000592
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 'data' is tuple of message part envelope and data.
596 """
597 name = 'PARTIAL'
598 typ, dat = self._simple_command(name, message_num, message_part, start, length)
599 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000600
601
Piers Laudere0273de2002-11-22 05:53:04 +0000602 def proxyauth(self, user):
603 """Assume authentication as "user".
604
605 Allows an authorised administrator to proxy into any user's
606 mailbox.
607
608 (typ, [data]) = <instance>.proxyauth(user)
609 """
610
611 name = 'PROXYAUTH'
612 return self._simple_command('PROXYAUTH', user)
613
614
Tim Peters07e99cb2001-01-14 23:47:14 +0000615 def rename(self, oldmailbox, newmailbox):
616 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000617
Piers Laudere0273de2002-11-22 05:53:04 +0000618 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000619 """
620 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000621
622
Tim Peters07e99cb2001-01-14 23:47:14 +0000623 def search(self, charset, *criteria):
624 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000625
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000626 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 'data' is space separated list of matching message numbers.
629 """
630 name = 'SEARCH'
631 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000632 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000633 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000634 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000635 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
637
Piers Lauder14f39402005-08-31 10:46:29 +0000638 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000640
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000642
Piers Lauder14f39402005-08-31 10:46:29 +0000643 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000644
Tim Peters07e99cb2001-01-14 23:47:14 +0000645 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000646
647 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
648 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 self.untagged_responses = {} # Flush old responses.
651 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000652 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 name = 'EXAMINE'
654 else:
655 name = 'SELECT'
656 typ, dat = self._simple_command(name, mailbox)
657 if typ != 'OK':
658 self.state = 'AUTH' # Might have been 'SELECTED'
659 return typ, dat
660 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000661 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000662 and not readonly:
663 if __debug__:
664 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000665 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000666 raise self.readonly('%s is not writable' % mailbox)
667 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000668
669
Piers Lauder15e5d532001-07-20 10:52:06 +0000670 def setacl(self, mailbox, who, what):
671 """Set a mailbox acl.
672
Piers Lauderf167dc32004-03-25 00:12:21 +0000673 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000674 """
675 return self._simple_command('SETACL', mailbox, who, what)
676
677
Piers Lauderd80ef022005-06-01 23:50:52 +0000678 def setannotation(self, *args):
679 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
680 Set ANNOTATIONs."""
681
682 typ, dat = self._simple_command('SETANNOTATION', *args)
683 return self._untagged_response(typ, dat, 'ANNOTATION')
684
685
Piers Lauder3fca2912002-06-17 07:07:20 +0000686 def setquota(self, root, limits):
687 """Set the quota root's resource limits.
688
689 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000690 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000691 typ, dat = self._simple_command('SETQUOTA', root, limits)
692 return self._untagged_response(typ, dat, 'QUOTA')
693
694
Piers Lauder15e5d532001-07-20 10:52:06 +0000695 def sort(self, sort_criteria, charset, *search_criteria):
696 """IMAP4rev1 extension SORT command.
697
698 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
699 """
700 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000701 #if not name in self.capabilities: # Let the server decide!
702 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000703 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000704 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000705 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000706 return self._untagged_response(typ, dat, name)
707
708
Tim Peters07e99cb2001-01-14 23:47:14 +0000709 def status(self, mailbox, names):
710 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000711
Tim Peters07e99cb2001-01-14 23:47:14 +0000712 (typ, [data]) = <instance>.status(mailbox, names)
713 """
714 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000715 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000716 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 typ, dat = self._simple_command(name, mailbox, names)
718 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000719
720
Tim Peters07e99cb2001-01-14 23:47:14 +0000721 def store(self, message_set, command, flags):
722 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000723
Tim Peters07e99cb2001-01-14 23:47:14 +0000724 (typ, [data]) = <instance>.store(message_set, command, flags)
725 """
726 if (flags[0],flags[-1]) != ('(',')'):
727 flags = '(%s)' % flags # Avoid quoting the flags
728 typ, dat = self._simple_command('STORE', message_set, command, flags)
729 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000730
731
Tim Peters07e99cb2001-01-14 23:47:14 +0000732 def subscribe(self, mailbox):
733 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000734
Tim Peters07e99cb2001-01-14 23:47:14 +0000735 (typ, [data]) = <instance>.subscribe(mailbox)
736 """
737 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000738
739
Martin v. Löwisd8921372003-11-10 06:44:44 +0000740 def thread(self, threading_algorithm, charset, *search_criteria):
741 """IMAPrev1 extension THREAD command.
742
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000743 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000744 """
745 name = 'THREAD'
746 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
747 return self._untagged_response(typ, dat, name)
748
749
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 def uid(self, command, *args):
751 """Execute "command arg ..." with messages identified by UID,
752 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000753
Tim Peters07e99cb2001-01-14 23:47:14 +0000754 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 Returns response appropriate to 'command'.
757 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000758 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000759 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000760 raise self.error("Unknown IMAP4 UID command: %s" % command)
761 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000762 raise self.error("command %s illegal in state %s, "
763 "only allowed in states %s" %
764 (command, self.state,
765 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000767 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000768 if command in ('SEARCH', 'SORT'):
769 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000770 else:
771 name = 'FETCH'
772 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000773
774
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 def unsubscribe(self, mailbox):
776 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000777
Tim Peters07e99cb2001-01-14 23:47:14 +0000778 (typ, [data]) = <instance>.unsubscribe(mailbox)
779 """
780 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000781
782
Tim Peters07e99cb2001-01-14 23:47:14 +0000783 def xatom(self, name, *args):
784 """Allow simple extension commands
785 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000786
Piers Lauder15e5d532001-07-20 10:52:06 +0000787 Assumes command is legal in current state.
788
Tim Peters07e99cb2001-01-14 23:47:14 +0000789 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000790
791 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000793 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000794 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000795 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000796 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000797 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000798 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000799
800
801
Tim Peters07e99cb2001-01-14 23:47:14 +0000802 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000803
804
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000806 if dat is None:
807 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 ur = self.untagged_responses
809 if __debug__:
810 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000811 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000813 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 ur[typ].append(dat)
815 else:
816 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000817
818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 def _check_bye(self):
820 bye = self.untagged_responses.get('BYE')
821 if bye:
822 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000823
824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 if self.state not in Commands[name]:
828 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000829 raise self.error("command %s illegal in state %s, "
830 "only allowed in states %s" %
831 (name, self.state,
832 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000835 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000837
Raymond Hettinger54f02222002-06-01 14:18:47 +0000838 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 and not self.is_readonly:
840 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000841
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000843 name = bytes(name, 'ASCII')
844 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000845 for arg in args:
846 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000847 if isinstance(arg, str):
848 arg = bytes(arg, "ASCII")
849 #data = data + b' ' + self._checkquote(arg)
850 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000851
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 literal = self.literal
853 if literal is not None:
854 self.literal = None
855 if type(literal) is type(self._command):
856 literator = literal
857 else:
858 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000859 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 if __debug__:
862 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000863 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000865 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000866
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000868 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000869 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 if literal is None:
873 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000874
Tim Peters07e99cb2001-01-14 23:47:14 +0000875 while 1:
876 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 while self._get_response():
879 if self.tagged_commands[tag]: # BAD/NO?
880 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 if literator:
885 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 if __debug__:
888 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000889 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000890
Tim Peters07e99cb2001-01-14 23:47:14 +0000891 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000892 self.send(literal)
893 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000894 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 if not literator:
898 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
902
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 def _command_complete(self, name, tag):
904 self._check_bye()
905 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))
911 self._check_bye()
912 if typ == 'BAD':
913 raise self.error('%s command error: %s %s' % (name, typ, data))
914 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
916
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 # Read response and store.
920 #
921 # Returns None for continuation responses,
922 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 if self._match(self.tagre, resp):
929 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000930 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000934 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 dat = self.mo.group('data')
936 self.tagged_commands[tag] = (typ, [dat])
937 else:
938 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000939
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000941
Tim Peters07e99cb2001-01-14 23:47:14 +0000942 if not self._match(Untagged_response, resp):
943 if self._match(Untagged_status, resp):
944 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 if self.mo is None:
947 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 if self._match(Continuation, resp):
950 self.continuation_response = self.mo.group('data')
951 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000956 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000958 if dat is None: dat = b'' # Null untagged response
959 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000960
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000967 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 if __debug__:
969 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000970 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000971 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000986 typ = self.mo.group('type')
987 typ = str(typ, "ASCII")
988 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 if __debug__:
991 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000992 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000995
996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
Tim Peters07e99cb2001-01-14 23:47:14 +0000999 while 1:
1000 result = self.tagged_commands[tag]
1001 if result is not None:
1002 del self.tagged_commands[tag]
1003 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001004
Tim Peters07e99cb2001-01-14 23:47:14 +00001005 # Some have reported "unexpected response" exceptions.
1006 # Note that ignoring them here causes loops.
1007 # Instead, send me details of the unexpected response and
1008 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 try:
1011 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001012 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 if __debug__:
1014 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001015 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
1018
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001020
Piers Lauder15e5d532001-07-20 10:52:06 +00001021 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 if not line:
1023 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001024
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001026 if not line.endswith(b'\r\n'):
1027 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001028
Tim Peters07e99cb2001-01-14 23:47:14 +00001029 line = line[:-2]
1030 if __debug__:
1031 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001032 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001034 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001036
1037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 # Run compiled regular expression match method on 's'.
1041 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 self.mo = cre.match(s)
1044 if __debug__:
1045 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001046 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001047 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001048
1049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Christian Heimesfb5faf02008-11-05 19:39:50 +00001052 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 self.tagnum = self.tagnum + 1
1054 self.tagged_commands[tag] = None
1055 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001056
1057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 # Must quote command args if non-alphanumeric chars present,
1061 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001062
Piers Lauderc09acfd2004-10-08 04:05:39 +00001063 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001065 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 return arg
1067 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001068
1069
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001071
Christian Heimesfb5faf02008-11-05 19:39:50 +00001072 arg = arg.replace(b'\\', b'\\\\')
1073 arg = arg.replace(b'"', b'\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001074
Christian Heimesfb5faf02008-11-05 19:39:50 +00001075 return b'"' + arg + b'"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001076
1077
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
Guido van Rossum68468eb2003-02-27 20:14:51 +00001080 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001081
1082
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 if typ == 'NO':
1085 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001086 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001087 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001088 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001089 if __debug__:
1090 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001091 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001093
1094
Piers Lauderf2d7d152002-02-22 01:15:17 +00001095 if __debug__:
1096
1097 def _mesg(self, s, secs=None):
1098 if secs is None:
1099 secs = time.time()
1100 tm = time.strftime('%M:%S', time.localtime(secs))
1101 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1102 sys.stderr.flush()
1103
1104 def _dump_ur(self, dict):
1105 # Dump untagged responses (in `dict').
1106 l = dict.items()
1107 if not l: return
1108 t = '\n\t\t'
1109 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1110 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1111
1112 def _log(self, line):
1113 # Keep log of last `_cmd_log_len' interactions for debugging.
1114 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1115 self._cmd_log_idx += 1
1116 if self._cmd_log_idx >= self._cmd_log_len:
1117 self._cmd_log_idx = 0
1118
1119 def print_log(self):
1120 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1121 i, n = self._cmd_log_idx, self._cmd_log_len
1122 while n:
1123 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001124 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001125 except:
1126 pass
1127 i += 1
1128 if i >= self._cmd_log_len:
1129 i = 0
1130 n -= 1
1131
1132
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001133
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001134try:
1135 import ssl
1136except ImportError:
1137 pass
1138else:
1139 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001140
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001141 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001142
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001143 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001144
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001145 host - host's name (default: localhost);
1146 port - port number (default: standard IMAP4 SSL port).
1147 keyfile - PEM formatted file that contains your private key (default: None);
1148 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001149
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001150 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001151 """
Piers Laudera4f83132002-03-08 01:53:24 +00001152
1153
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001154 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1155 self.keyfile = keyfile
1156 self.certfile = certfile
1157 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001158
Christian Heimesfb5faf02008-11-05 19:39:50 +00001159 def _create_socket(self):
1160 sock = IMAP4._create_socket(self)
1161 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001162
Christian Heimesfb5faf02008-11-05 19:39:50 +00001163 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001164 """Setup connection to remote server on "host:port".
1165 (default: localhost:standard IMAP4 SSL port).
1166 This connection will be used by the routines:
1167 read, readline, send, shutdown.
1168 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001169 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001170
1171 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001172
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
Christian Heimesfb5faf02008-11-05 19:39:50 +00001180 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001181
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
Christian Heimesfb5faf02008-11-05 19:39:50 +00001200 self.process = subprocess.Popen(self.command,
1201 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1202 shell=True, close_fds=True)
1203 self.writefile = self.process.stdin
1204 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001205
1206 def read(self, size):
1207 """Read 'size' bytes from remote."""
1208 return self.readfile.read(size)
1209
1210
1211 def readline(self):
1212 """Read line from remote."""
1213 return self.readfile.readline()
1214
1215
1216 def send(self, data):
1217 """Send data to remote."""
1218 self.writefile.write(data)
1219 self.writefile.flush()
1220
1221
1222 def shutdown(self):
1223 """Close I/O established in "open"."""
1224 self.readfile.close()
1225 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001226 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001227
1228
1229
Guido van Rossumeda960a1998-06-18 14:24:28 +00001230class _Authenticator:
1231
Tim Peters07e99cb2001-01-14 23:47:14 +00001232 """Private class to provide en/decoding
1233 for base64-based authentication conversation.
1234 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001235
Tim Peters07e99cb2001-01-14 23:47:14 +00001236 def __init__(self, mechinst):
1237 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001238
Tim Peters07e99cb2001-01-14 23:47:14 +00001239 def process(self, data):
1240 ret = self.mech(self.decode(data))
1241 if ret is None:
1242 return '*' # Abort conversation
1243 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001244
Tim Peters07e99cb2001-01-14 23:47:14 +00001245 def encode(self, inp):
1246 #
1247 # Invoke binascii.b2a_base64 iteratively with
1248 # short even length buffers, strip the trailing
1249 # line feed from the result and append. "Even"
1250 # means a number that factors to both 6 and 8,
1251 # so when it gets to the end of the 8-bit input
1252 # there's no partial 6-bit output.
1253 #
1254 oup = ''
1255 while inp:
1256 if len(inp) > 48:
1257 t = inp[:48]
1258 inp = inp[48:]
1259 else:
1260 t = inp
1261 inp = ''
1262 e = binascii.b2a_base64(t)
1263 if e:
1264 oup = oup + e[:-1]
1265 return oup
1266
1267 def decode(self, inp):
1268 if not inp:
1269 return ''
1270 return binascii.a2b_base64(inp)
1271
Guido van Rossumeda960a1998-06-18 14:24:28 +00001272
1273
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001274Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001275 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001276
1277def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001278 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001279
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 Returns Python time module tuple.
1281 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001282
Tim Peters07e99cb2001-01-14 23:47:14 +00001283 mo = InternalDate.match(resp)
1284 if not mo:
1285 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001286
Tim Peters07e99cb2001-01-14 23:47:14 +00001287 mon = Mon2num[mo.group('mon')]
1288 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001289
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001290 day = int(mo.group('day'))
1291 year = int(mo.group('year'))
1292 hour = int(mo.group('hour'))
1293 min = int(mo.group('min'))
1294 sec = int(mo.group('sec'))
1295 zoneh = int(mo.group('zoneh'))
1296 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001297
Tim Peters07e99cb2001-01-14 23:47:14 +00001298 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001299
Tim Peters07e99cb2001-01-14 23:47:14 +00001300 zone = (zoneh*60 + zonem)*60
1301 if zonen == '-':
1302 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001303
Tim Peters07e99cb2001-01-14 23:47:14 +00001304 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001305
Tim Peters07e99cb2001-01-14 23:47:14 +00001306 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001307
Tim Peters07e99cb2001-01-14 23:47:14 +00001308 # Following is necessary because the time module has no 'mkgmtime'.
1309 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001310
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 lt = time.localtime(utc)
1312 if time.daylight and lt[-1]:
1313 zone = zone + time.altzone
1314 else:
1315 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001316
Tim Peters07e99cb2001-01-14 23:47:14 +00001317 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001318
1319
1320
1321def Int2AP(num):
1322
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001324
Christian Heimesfb5faf02008-11-05 19:39:50 +00001325 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001326 num = int(abs(num))
1327 while num:
1328 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001329 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001330 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001331
1332
1333
1334def ParseFlags(resp):
1335
Tim Peters07e99cb2001-01-14 23:47:14 +00001336 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Tim Peters07e99cb2001-01-14 23:47:14 +00001338 mo = Flags.match(resp)
1339 if not mo:
1340 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001341
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001342 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
1344
1345def Time2Internaldate(date_time):
1346
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001348
Tim Peters07e99cb2001-01-14 23:47:14 +00001349 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1350 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001351
Fred Drakedb519202002-01-05 17:17:09 +00001352 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001353 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001354 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001356 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001358 else:
1359 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1362 if dt[0] == '0':
1363 dt = ' ' + dt[1:]
1364 if time.daylight and tt[-1]:
1365 zone = -time.altzone
1366 else:
1367 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001368 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001369
1370
1371
Guido van Rossum8c062211999-12-13 23:27:45 +00001372if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001373
Piers Laudere0273de2002-11-22 05:53:04 +00001374 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1375 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1376 # to test the IMAP4_stream class
1377
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001378 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001379
Tim Peters07e99cb2001-01-14 23:47:14 +00001380 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001381 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001382 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001383 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001384
Piers Laudere0273de2002-11-22 05:53:04 +00001385 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001386 for opt,val in optlist:
1387 if opt == '-d':
1388 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001389 elif opt == '-s':
1390 stream_command = val
1391 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001392
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001394
Tim Peters07e99cb2001-01-14 23:47:14 +00001395 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001396
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001398 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001399
Piers Lauder47404ff2003-04-29 23:40:59 +00001400 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 +00001401 test_seq1 = (
1402 ('login', (USER, PASSWD)),
1403 ('create', ('/tmp/xxx 1',)),
1404 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1405 ('CREATE', ('/tmp/yyz 2',)),
1406 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1407 ('list', ('/tmp', 'yy*')),
1408 ('select', ('/tmp/yyz 2',)),
1409 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001410 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001411 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001412 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 ('expunge', ()),
1414 ('recent', ()),
1415 ('close', ()),
1416 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001417
Tim Peters07e99cb2001-01-14 23:47:14 +00001418 test_seq2 = (
1419 ('select', ()),
1420 ('response',('UIDVALIDITY',)),
1421 ('uid', ('SEARCH', 'ALL')),
1422 ('response', ('EXISTS',)),
1423 ('append', (None, None, None, test_mesg)),
1424 ('recent', ()),
1425 ('logout', ()),
1426 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001427
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001429 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001430 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001431 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001432 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001434
Tim Peters07e99cb2001-01-14 23:47:14 +00001435 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001436 if stream_command:
1437 M = IMAP4_stream(stream_command)
1438 else:
1439 M = IMAP4(host)
1440 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001441 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001442 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001443 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001444
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 for cmd,args in test_seq1:
1446 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001447
Tim Peters07e99cb2001-01-14 23:47:14 +00001448 for ml in run('list', ('/tmp/', 'yy%')):
1449 mo = re.match(r'.*"([^"]+)"$', ml)
1450 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001451 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001453
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 for cmd,args in test_seq2:
1455 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001456
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1458 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001459
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001460 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 if not uid: continue
1462 run('uid', ('FETCH', '%s' % uid[-1],
1463 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001464
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001465 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001466
Tim Peters07e99cb2001-01-14 23:47:14 +00001467 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001468 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001469
Tim Peters07e99cb2001-01-14 23:47:14 +00001470 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001471 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001472If you would like to see debugging output,
1473try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001474''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001475
Tim Peters07e99cb2001-01-14 23:47:14 +00001476 raise