blob: e2a05818fa617053c5f5c513aff4b49907135f4f [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
Alexander Belopolsky2420d832012-04-29 15:56:49 -040025import binascii, errno, random, re, socket, subprocess, sys, time, calendar
R David Murrayfcb6d6a2013-03-19 13:52:33 -040026from io import DEFAULT_BUFFER_SIZE
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000027
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000028try:
29 import ssl
30 HAVE_SSL = True
31except ImportError:
32 HAVE_SSL = False
33
Thomas Wouters47b49bf2007-08-30 22:15:33 +000034__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000035 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000036
Tim Peters07e99cb2001-01-14 23:47:14 +000037# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000038
Christian Heimesfb5faf02008-11-05 19:39:50 +000039CRLF = b'\r\n'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000040Debug = 0
41IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000042IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000043AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000044
Tim Peters07e99cb2001-01-14 23:47:14 +000045# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000046
47Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000048 # name valid states
49 'APPEND': ('AUTH', 'SELECTED'),
50 'AUTHENTICATE': ('NONAUTH',),
51 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
52 'CHECK': ('SELECTED',),
53 'CLOSE': ('SELECTED',),
54 'COPY': ('SELECTED',),
55 'CREATE': ('AUTH', 'SELECTED'),
56 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000057 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000058 'EXAMINE': ('AUTH', 'SELECTED'),
59 'EXPUNGE': ('SELECTED',),
60 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000061 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000062 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000063 'GETQUOTA': ('AUTH', 'SELECTED'),
64 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000065 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000066 'LIST': ('AUTH', 'SELECTED'),
67 'LOGIN': ('NONAUTH',),
68 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
69 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000070 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000071 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000072 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000073 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000074 'RENAME': ('AUTH', 'SELECTED'),
75 'SEARCH': ('SELECTED',),
76 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000077 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000078 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000079 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000080 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000081 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000082 'STATUS': ('AUTH', 'SELECTED'),
83 'STORE': ('SELECTED',),
84 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000085 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000086 'UID': ('SELECTED',),
87 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
88 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000089
Tim Peters07e99cb2001-01-14 23:47:14 +000090# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000091
Christian Heimesfb5faf02008-11-05 19:39:50 +000092Continuation = re.compile(br'\+( (?P<data>.*))?')
93Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
94InternalDate = re.compile(br'.*INTERNALDATE "'
95 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
96 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
97 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
98 br'"')
99Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
100MapCRLF = re.compile(br'\r\n|\r|\n')
101Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
102Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +0000103Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000104 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000105
106
107
108class IMAP4:
109
Tim Peters07e99cb2001-01-14 23:47:14 +0000110 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000111
Tim Peters07e99cb2001-01-14 23:47:14 +0000112 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000113
Tim Peters07e99cb2001-01-14 23:47:14 +0000114 host - host's name (default: localhost);
115 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000116
Tim Peters07e99cb2001-01-14 23:47:14 +0000117 All IMAP4rev1 commands are supported by methods of the same
118 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000119
Tim Peters07e99cb2001-01-14 23:47:14 +0000120 All arguments to commands are converted to strings, except for
121 AUTHENTICATE, and the last argument to APPEND which is passed as
122 an IMAP4 literal. If necessary (the string contains any
123 non-printing characters or white-space and isn't enclosed with
124 either parentheses or double quotes) each string is quoted.
125 However, the 'password' argument to the LOGIN command is always
126 quoted. If you want to avoid having an argument string quoted
127 (eg: the 'flags' argument to STORE) then enclose the string in
128 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000129
Tim Peters07e99cb2001-01-14 23:47:14 +0000130 Each command returns a tuple: (type, [data, ...]) where 'type'
131 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000132 tagged response, or untagged results from command. Each 'data'
133 is either a string, or a tuple. If a tuple, then the first part
134 is the header of the response, and the second part contains
135 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000136
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 Errors raise the exception class <instance>.error("<reason>").
138 IMAP4 server errors raise <instance>.abort("<reason>"),
139 which is a sub-class of 'error'. Mailbox status changes
140 from READ-WRITE to READ-ONLY raise the exception class
141 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000142
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 "error" exceptions imply a program error.
144 "abort" exceptions imply the connection should be reset, and
145 the command re-tried.
146 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000147
Piers Lauderd80ef022005-06-01 23:50:52 +0000148 Note: to use this module, you must read the RFCs pertaining to the
149 IMAP4 protocol, as the semantics of the arguments to each IMAP4
150 command are left to the invoker, not to mention the results. Also,
151 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 class error(Exception): pass # Logical errors - debug required
155 class abort(error): pass # Service errors - close and retry
156 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000157
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 self.debug = Debug
160 self.state = 'LOGOUT'
161 self.literal = None # A literal argument to a command
162 self.tagged_commands = {} # Tagged commands awaiting response
163 self.untagged_responses = {} # {typ: [data, ...], ...}
164 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000165 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000166 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000167 self._tls_established = False
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000168
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000170
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000172
Victor Stinner33e649c2011-01-05 23:01:37 +0000173 try:
174 self._connect()
175 except Exception:
176 try:
177 self.shutdown()
178 except socket.error:
179 pass
180 raise
181
182
183 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000184 # Create unique tag for this session,
185 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000186
Piers Lauder2dfc1682005-07-05 04:20:07 +0000187 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000188 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000190 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000191
Tim Peters07e99cb2001-01-14 23:47:14 +0000192 # Get server welcome message,
193 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000194
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000196 self._cmd_log_len = 10
197 self._cmd_log_idx = 0
198 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000199 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000200 self._mesg('imaplib version %s' % __version__)
201 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000202
Tim Peters07e99cb2001-01-14 23:47:14 +0000203 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000204 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000205 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000206 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000207 self.state = 'NONAUTH'
208 else:
209 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000210
Antoine Pitroudbe75192010-11-16 17:55:26 +0000211 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000212 if __debug__:
213 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000214 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000215
Tim Peters07e99cb2001-01-14 23:47:14 +0000216 for version in AllowedVersions:
217 if not version in self.capabilities:
218 continue
219 self.PROTOCOL_VERSION = version
220 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000221
Tim Peters07e99cb2001-01-14 23:47:14 +0000222 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000223
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000224
Tim Peters07e99cb2001-01-14 23:47:14 +0000225 def __getattr__(self, attr):
226 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000227 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000228 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000229 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000230
231
232
Piers Lauder15e5d532001-07-20 10:52:06 +0000233 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000234
235
Christian Heimesfb5faf02008-11-05 19:39:50 +0000236 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000237 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000238
Piers Lauderf97b2d72002-06-05 22:31:57 +0000239 def open(self, host = '', port = IMAP4_PORT):
240 """Setup connection to remote server on "host:port"
241 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000242 This connection will be used by the routines:
243 read, readline, send, shutdown.
244 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000245 self.host = host
246 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000247 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000248 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000249
250
Piers Lauder15e5d532001-07-20 10:52:06 +0000251 def read(self, size):
252 """Read 'size' bytes from remote."""
Christian Heimesfb5faf02008-11-05 19:39:50 +0000253 chunks = []
254 read = 0
255 while read < size:
256 data = self.file.read(min(size-read, 4096))
257 if not data:
258 break
259 read += len(data)
260 chunks.append(data)
261 return b''.join(chunks)
Piers Lauder15e5d532001-07-20 10:52:06 +0000262
263
264 def readline(self):
265 """Read line from remote."""
266 return self.file.readline()
267
268
269 def send(self, data):
270 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000271 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000272
Piers Lauderf2d7d152002-02-22 01:15:17 +0000273
Piers Lauder15e5d532001-07-20 10:52:06 +0000274 def shutdown(self):
275 """Close I/O established in "open"."""
276 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000277 try:
278 self.sock.shutdown(socket.SHUT_RDWR)
279 except socket.error as e:
280 # The server might already have closed the connection
281 if e.errno != errno.ENOTCONN:
282 raise
283 finally:
284 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000285
286
287 def socket(self):
288 """Return socket instance used to connect to IMAP4 server.
289
290 socket = <instance>.socket()
291 """
292 return self.sock
293
294
295
296 # Utility methods
297
298
Tim Peters07e99cb2001-01-14 23:47:14 +0000299 def recent(self):
300 """Return most recent 'RECENT' responses if any exist,
301 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000302
Tim Peters07e99cb2001-01-14 23:47:14 +0000303 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000304
Tim Peters07e99cb2001-01-14 23:47:14 +0000305 'data' is None if no new messages,
306 else list of RECENT responses, most recent last.
307 """
308 name = 'RECENT'
309 typ, dat = self._untagged_response('OK', [None], name)
310 if dat[-1]:
311 return typ, dat
312 typ, dat = self.noop() # Prod server for response
313 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000314
315
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 def response(self, code):
317 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000318
Tim Peters07e99cb2001-01-14 23:47:14 +0000319 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000320
Tim Peters07e99cb2001-01-14 23:47:14 +0000321 (code, [data]) = <instance>.response(code)
322 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000323 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000324
325
Guido van Rossum26367a01998-09-28 15:34:46 +0000326
Tim Peters07e99cb2001-01-14 23:47:14 +0000327 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000328
329
Tim Peters07e99cb2001-01-14 23:47:14 +0000330 def append(self, mailbox, flags, date_time, message):
331 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000332
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000334
Tim Peters07e99cb2001-01-14 23:47:14 +0000335 All args except `message' can be None.
336 """
337 name = 'APPEND'
338 if not mailbox:
339 mailbox = 'INBOX'
340 if flags:
341 if (flags[0],flags[-1]) != ('(',')'):
342 flags = '(%s)' % flags
343 else:
344 flags = None
345 if date_time:
346 date_time = Time2Internaldate(date_time)
347 else:
348 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000349 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000350 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000351
352
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 def authenticate(self, mechanism, authobject):
354 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000355
Tim Peters07e99cb2001-01-14 23:47:14 +0000356 'mechanism' specifies which authentication mechanism is to
357 be used - it must appear in <instance>.capabilities in the
358 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000359
Tim Peters07e99cb2001-01-14 23:47:14 +0000360 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000361
Tim Peters07e99cb2001-01-14 23:47:14 +0000362 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000363
R David Murray774a39f2013-02-19 12:17:31 -0500364 It will be called to process server continuation responses; the
365 response argument it is passed will be a bytes. It should return bytes
366 data that will be base64 encoded and sent to the server. It should
367 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000368 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000369 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000370 # XXX: shouldn't this code be removed, not commented out?
371 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000372 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000373 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 self.literal = _Authenticator(authobject).process
375 typ, dat = self._simple_command('AUTHENTICATE', mech)
376 if typ != 'OK':
377 raise self.error(dat[-1])
378 self.state = 'AUTH'
379 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000380
381
Piers Lauderd80ef022005-06-01 23:50:52 +0000382 def capability(self):
383 """(typ, [data]) = <instance>.capability()
384 Fetch capabilities list from server."""
385
386 name = 'CAPABILITY'
387 typ, dat = self._simple_command(name)
388 return self._untagged_response(typ, dat, name)
389
390
Tim Peters07e99cb2001-01-14 23:47:14 +0000391 def check(self):
392 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
Tim Peters07e99cb2001-01-14 23:47:14 +0000394 (typ, [data]) = <instance>.check()
395 """
396 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 def close(self):
400 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000401
Tim Peters07e99cb2001-01-14 23:47:14 +0000402 Deleted messages are removed from writable mailbox.
403 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 (typ, [data]) = <instance>.close()
406 """
407 try:
408 typ, dat = self._simple_command('CLOSE')
409 finally:
410 self.state = 'AUTH'
411 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000412
413
Tim Peters07e99cb2001-01-14 23:47:14 +0000414 def copy(self, message_set, new_mailbox):
415 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000416
Tim Peters07e99cb2001-01-14 23:47:14 +0000417 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
418 """
419 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000420
421
Tim Peters07e99cb2001-01-14 23:47:14 +0000422 def create(self, mailbox):
423 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000424
Tim Peters07e99cb2001-01-14 23:47:14 +0000425 (typ, [data]) = <instance>.create(mailbox)
426 """
427 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000428
429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 def delete(self, mailbox):
431 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000432
Tim Peters07e99cb2001-01-14 23:47:14 +0000433 (typ, [data]) = <instance>.delete(mailbox)
434 """
435 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000436
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000437 def deleteacl(self, mailbox, who):
438 """Delete the ACLs (remove any rights) set for who on mailbox.
439
440 (typ, [data]) = <instance>.deleteacl(mailbox, who)
441 """
442 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 def expunge(self):
445 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000448
Tim Peters07e99cb2001-01-14 23:47:14 +0000449 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000450
Tim Peters07e99cb2001-01-14 23:47:14 +0000451 'data' is list of 'EXPUNGE'd message numbers in order received.
452 """
453 name = 'EXPUNGE'
454 typ, dat = self._simple_command(name)
455 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000456
457
Tim Peters07e99cb2001-01-14 23:47:14 +0000458 def fetch(self, message_set, message_parts):
459 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000460
Tim Peters07e99cb2001-01-14 23:47:14 +0000461 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 'message_parts' should be a string of selected parts
464 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000465
Tim Peters07e99cb2001-01-14 23:47:14 +0000466 'data' are tuples of message part envelope and data.
467 """
468 name = 'FETCH'
469 typ, dat = self._simple_command(name, message_set, message_parts)
470 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000471
472
Piers Lauder15e5d532001-07-20 10:52:06 +0000473 def getacl(self, mailbox):
474 """Get the ACLs for a mailbox.
475
476 (typ, [data]) = <instance>.getacl(mailbox)
477 """
478 typ, dat = self._simple_command('GETACL', mailbox)
479 return self._untagged_response(typ, dat, 'ACL')
480
481
Piers Lauderd80ef022005-06-01 23:50:52 +0000482 def getannotation(self, mailbox, entry, attribute):
483 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
484 Retrieve ANNOTATIONs."""
485
486 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
487 return self._untagged_response(typ, dat, 'ANNOTATION')
488
489
Piers Lauder3fca2912002-06-17 07:07:20 +0000490 def getquota(self, root):
491 """Get the quota root's resource usage and limits.
492
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000493 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000494
495 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000496 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000497 typ, dat = self._simple_command('GETQUOTA', root)
498 return self._untagged_response(typ, dat, 'QUOTA')
499
500
501 def getquotaroot(self, mailbox):
502 """Get the list of quota roots for the named mailbox.
503
504 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000505 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000506 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000507 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
508 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000509 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000510
511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 def list(self, directory='""', pattern='*'):
513 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000516
Tim Peters07e99cb2001-01-14 23:47:14 +0000517 'data' is list of LIST responses.
518 """
519 name = 'LIST'
520 typ, dat = self._simple_command(name, directory, pattern)
521 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000522
523
Tim Peters07e99cb2001-01-14 23:47:14 +0000524 def login(self, user, password):
525 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000526
Tim Peters07e99cb2001-01-14 23:47:14 +0000527 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000528
Tim Peters07e99cb2001-01-14 23:47:14 +0000529 NB: 'password' will be quoted.
530 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
532 if typ != 'OK':
533 raise self.error(dat[-1])
534 self.state = 'AUTH'
535 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
537
Piers Laudere0273de2002-11-22 05:53:04 +0000538 def login_cram_md5(self, user, password):
539 """ Force use of CRAM-MD5 authentication.
540
541 (typ, [data]) = <instance>.login_cram_md5(user, password)
542 """
543 self.user, self.password = user, password
544 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
545
546
547 def _CRAM_MD5_AUTH(self, challenge):
548 """ Authobject to use with CRAM-MD5 authentication. """
549 import hmac
R David Murray774a39f2013-02-19 12:17:31 -0500550 pwd = (self.password.encode('ASCII') if isinstance(self.password, str)
551 else self.password)
552 return self.user + " " + hmac.HMAC(pwd, challenge).hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000553
554
Tim Peters07e99cb2001-01-14 23:47:14 +0000555 def logout(self):
556 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000557
Tim Peters07e99cb2001-01-14 23:47:14 +0000558 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000559
Tim Peters07e99cb2001-01-14 23:47:14 +0000560 Returns server 'BYE' response.
561 """
562 self.state = 'LOGOUT'
563 try: typ, dat = self._simple_command('LOGOUT')
564 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000565 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000566 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000567 return 'BYE', self.untagged_responses['BYE']
568 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000569
570
Tim Peters07e99cb2001-01-14 23:47:14 +0000571 def lsub(self, directory='""', pattern='*'):
572 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000573
Tim Peters07e99cb2001-01-14 23:47:14 +0000574 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000575
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 'data' are tuples of message part envelope and data.
577 """
578 name = 'LSUB'
579 typ, dat = self._simple_command(name, directory, pattern)
580 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000581
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000582 def myrights(self, mailbox):
583 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
584
585 (typ, [data]) = <instance>.myrights(mailbox)
586 """
587 typ,dat = self._simple_command('MYRIGHTS', mailbox)
588 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000589
Piers Lauder15e5d532001-07-20 10:52:06 +0000590 def namespace(self):
591 """ Returns IMAP namespaces ala rfc2342
592
593 (typ, [data, ...]) = <instance>.namespace()
594 """
595 name = 'NAMESPACE'
596 typ, dat = self._simple_command(name)
597 return self._untagged_response(typ, dat, name)
598
599
Tim Peters07e99cb2001-01-14 23:47:14 +0000600 def noop(self):
601 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000602
Piers Laudere0273de2002-11-22 05:53:04 +0000603 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000604 """
605 if __debug__:
606 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000607 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000608 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000609
610
Tim Peters07e99cb2001-01-14 23:47:14 +0000611 def partial(self, message_num, message_part, start, length):
612 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000613
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000615
Tim Peters07e99cb2001-01-14 23:47:14 +0000616 'data' is tuple of message part envelope and data.
617 """
618 name = 'PARTIAL'
619 typ, dat = self._simple_command(name, message_num, message_part, start, length)
620 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000621
622
Piers Laudere0273de2002-11-22 05:53:04 +0000623 def proxyauth(self, user):
624 """Assume authentication as "user".
625
626 Allows an authorised administrator to proxy into any user's
627 mailbox.
628
629 (typ, [data]) = <instance>.proxyauth(user)
630 """
631
632 name = 'PROXYAUTH'
633 return self._simple_command('PROXYAUTH', user)
634
635
Tim Peters07e99cb2001-01-14 23:47:14 +0000636 def rename(self, oldmailbox, newmailbox):
637 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
Piers Laudere0273de2002-11-22 05:53:04 +0000639 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000640 """
641 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
643
Tim Peters07e99cb2001-01-14 23:47:14 +0000644 def search(self, charset, *criteria):
645 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000646
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000647 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000648
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 'data' is space separated list of matching message numbers.
650 """
651 name = 'SEARCH'
652 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000653 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000654 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000655 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000656 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000657
658
Piers Lauder14f39402005-08-31 10:46:29 +0000659 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000661
Tim Peters07e99cb2001-01-14 23:47:14 +0000662 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000663
Piers Lauder14f39402005-08-31 10:46:29 +0000664 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000665
Tim Peters07e99cb2001-01-14 23:47:14 +0000666 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000667
668 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
669 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000670 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 self.untagged_responses = {} # Flush old responses.
672 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000673 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000674 name = 'EXAMINE'
675 else:
676 name = 'SELECT'
677 typ, dat = self._simple_command(name, mailbox)
678 if typ != 'OK':
679 self.state = 'AUTH' # Might have been 'SELECTED'
680 return typ, dat
681 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000682 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000683 and not readonly:
684 if __debug__:
685 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000686 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000687 raise self.readonly('%s is not writable' % mailbox)
688 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000689
690
Piers Lauder15e5d532001-07-20 10:52:06 +0000691 def setacl(self, mailbox, who, what):
692 """Set a mailbox acl.
693
Piers Lauderf167dc32004-03-25 00:12:21 +0000694 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000695 """
696 return self._simple_command('SETACL', mailbox, who, what)
697
698
Piers Lauderd80ef022005-06-01 23:50:52 +0000699 def setannotation(self, *args):
700 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
701 Set ANNOTATIONs."""
702
703 typ, dat = self._simple_command('SETANNOTATION', *args)
704 return self._untagged_response(typ, dat, 'ANNOTATION')
705
706
Piers Lauder3fca2912002-06-17 07:07:20 +0000707 def setquota(self, root, limits):
708 """Set the quota root's resource limits.
709
710 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000711 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000712 typ, dat = self._simple_command('SETQUOTA', root, limits)
713 return self._untagged_response(typ, dat, 'QUOTA')
714
715
Piers Lauder15e5d532001-07-20 10:52:06 +0000716 def sort(self, sort_criteria, charset, *search_criteria):
717 """IMAP4rev1 extension SORT command.
718
719 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
720 """
721 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000722 #if not name in self.capabilities: # Let the server decide!
723 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000724 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000725 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000726 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000727 return self._untagged_response(typ, dat, name)
728
729
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000730 def starttls(self, ssl_context=None):
731 name = 'STARTTLS'
732 if not HAVE_SSL:
733 raise self.error('SSL support missing')
734 if self._tls_established:
735 raise self.abort('TLS session already established')
736 if name not in self.capabilities:
737 raise self.abort('TLS not supported by server')
738 # Generate a default SSL context if none was passed.
739 if ssl_context is None:
740 ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
741 # SSLv2 considered harmful.
742 ssl_context.options |= ssl.OP_NO_SSLv2
743 typ, dat = self._simple_command(name)
744 if typ == 'OK':
745 self.sock = ssl_context.wrap_socket(self.sock)
746 self.file = self.sock.makefile('rb')
747 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000748 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000749 else:
750 raise self.error("Couldn't establish TLS session")
751 return self._untagged_response(typ, dat, name)
752
753
Tim Peters07e99cb2001-01-14 23:47:14 +0000754 def status(self, mailbox, names):
755 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000756
Tim Peters07e99cb2001-01-14 23:47:14 +0000757 (typ, [data]) = <instance>.status(mailbox, names)
758 """
759 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000760 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000761 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 typ, dat = self._simple_command(name, mailbox, names)
763 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000764
765
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 def store(self, message_set, command, flags):
767 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000768
Tim Peters07e99cb2001-01-14 23:47:14 +0000769 (typ, [data]) = <instance>.store(message_set, command, flags)
770 """
771 if (flags[0],flags[-1]) != ('(',')'):
772 flags = '(%s)' % flags # Avoid quoting the flags
773 typ, dat = self._simple_command('STORE', message_set, command, flags)
774 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000775
776
Tim Peters07e99cb2001-01-14 23:47:14 +0000777 def subscribe(self, mailbox):
778 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000779
Tim Peters07e99cb2001-01-14 23:47:14 +0000780 (typ, [data]) = <instance>.subscribe(mailbox)
781 """
782 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000783
784
Martin v. Löwisd8921372003-11-10 06:44:44 +0000785 def thread(self, threading_algorithm, charset, *search_criteria):
786 """IMAPrev1 extension THREAD command.
787
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000788 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000789 """
790 name = 'THREAD'
791 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
792 return self._untagged_response(typ, dat, name)
793
794
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 def uid(self, command, *args):
796 """Execute "command arg ..." with messages identified by UID,
797 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000798
Tim Peters07e99cb2001-01-14 23:47:14 +0000799 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000800
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 Returns response appropriate to 'command'.
802 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000803 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000804 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 raise self.error("Unknown IMAP4 UID command: %s" % command)
806 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000807 raise self.error("command %s illegal in state %s, "
808 "only allowed in states %s" %
809 (command, self.state,
810 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000811 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000812 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000813 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000814 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000815 else:
816 name = 'FETCH'
817 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000818
819
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 def unsubscribe(self, mailbox):
821 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000822
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 (typ, [data]) = <instance>.unsubscribe(mailbox)
824 """
825 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 def xatom(self, name, *args):
829 """Allow simple extension commands
830 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Piers Lauder15e5d532001-07-20 10:52:06 +0000832 Assumes command is legal in current state.
833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000835
836 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000837 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000838 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000839 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000840 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000841 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000842 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000843 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000844
845
846
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000848
849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000851 if dat is None:
852 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000853 ur = self.untagged_responses
854 if __debug__:
855 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000856 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000858 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000859 ur[typ].append(dat)
860 else:
861 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000862
863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 def _check_bye(self):
865 bye = self.untagged_responses.get('BYE')
866 if bye:
Antoine Pitroudac47912010-11-10 00:18:40 +0000867 raise self.abort(bye[-1].decode('ascii', 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000868
869
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 if self.state not in Commands[name]:
873 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000874 raise self.error("command %s illegal in state %s, "
875 "only allowed in states %s" %
876 (name, self.state,
877 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000880 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000881 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000882
Raymond Hettinger54f02222002-06-01 14:18:47 +0000883 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 and not self.is_readonly:
885 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000888 name = bytes(name, 'ASCII')
889 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000890 for arg in args:
891 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000892 if isinstance(arg, str):
893 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000894 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000895
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 literal = self.literal
897 if literal is not None:
898 self.literal = None
899 if type(literal) is type(self._command):
900 literator = literal
901 else:
902 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000903 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 if __debug__:
906 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000907 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000909 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000910
Tim Peters07e99cb2001-01-14 23:47:14 +0000911 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000912 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000913 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000914 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 if literal is None:
917 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 while 1:
920 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000921
Tim Peters07e99cb2001-01-14 23:47:14 +0000922 while self._get_response():
923 if self.tagged_commands[tag]: # BAD/NO?
924 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 if literator:
929 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 if __debug__:
932 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000933 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000936 self.send(literal)
937 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000938 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000939 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 if not literator:
942 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000943
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000948 # BYE is expected after LOGOUT
949 if name != 'LOGOUT':
950 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000951 try:
952 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000953 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000954 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000955 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +0000957 if name != 'LOGOUT':
958 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 if typ == 'BAD':
960 raise self.error('%s command error: %s %s' % (name, typ, data))
961 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
963
Antoine Pitroudbe75192010-11-16 17:55:26 +0000964 def _get_capabilities(self):
965 typ, dat = self.capability()
966 if dat == [None]:
967 raise self.error('no CAPABILITY response from server')
968 dat = str(dat[-1], "ASCII")
969 dat = dat.upper()
970 self.capabilities = tuple(dat.split())
971
972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 # Read response and store.
976 #
977 # Returns None for continuation responses,
978 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000983
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 if self._match(self.tagre, resp):
985 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000986 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000990 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 dat = self.mo.group('data')
992 self.tagged_commands[tag] = (typ, [dat])
993 else:
994 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000995
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 if not self._match(Untagged_response, resp):
999 if self._match(Untagged_status, resp):
1000 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001001
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 if self.mo is None:
1003 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001004
Tim Peters07e99cb2001-01-14 23:47:14 +00001005 if self._match(Continuation, resp):
1006 self.continuation_response = self.mo.group('data')
1007 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001008
Tim Peters07e99cb2001-01-14 23:47:14 +00001009 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001010
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001012 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001014 if dat is None: dat = b'' # Null untagged response
1015 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001016
Tim Peters07e99cb2001-01-14 23:47:14 +00001017 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001018
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001020
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001022
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001023 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 if __debug__:
1025 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001026 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001027 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001028
Tim Peters07e99cb2001-01-14 23:47:14 +00001029 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001030
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001032
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001034
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001036
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001038
Tim Peters07e99cb2001-01-14 23:47:14 +00001039 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001040
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001042 typ = self.mo.group('type')
1043 typ = str(typ, "ASCII")
1044 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001045
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 if __debug__:
1047 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001048 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
1052
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001054
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 while 1:
1056 result = self.tagged_commands[tag]
1057 if result is not None:
1058 del self.tagged_commands[tag]
1059 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001060
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 # Some have reported "unexpected response" exceptions.
1062 # Note that ignoring them here causes loops.
1063 # Instead, send me details of the unexpected response and
1064 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 try:
1067 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001068 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 if __debug__:
1070 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001071 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
1074
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001076
Piers Lauder15e5d532001-07-20 10:52:06 +00001077 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 if not line:
1079 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001080
Tim Peters07e99cb2001-01-14 23:47:14 +00001081 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001082 if not line.endswith(b'\r\n'):
1083 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001084
Tim Peters07e99cb2001-01-14 23:47:14 +00001085 line = line[:-2]
1086 if __debug__:
1087 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001088 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001089 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001090 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001092
1093
Tim Peters07e99cb2001-01-14 23:47:14 +00001094 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001095
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 # Run compiled regular expression match method on 's'.
1097 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001098
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 self.mo = cre.match(s)
1100 if __debug__:
1101 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001102 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001104
1105
Tim Peters07e99cb2001-01-14 23:47:14 +00001106 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001107
Christian Heimesfb5faf02008-11-05 19:39:50 +00001108 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 self.tagnum = self.tagnum + 1
1110 self.tagged_commands[tag] = None
1111 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001112
1113
Tim Peters07e99cb2001-01-14 23:47:14 +00001114 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001115
Antoine Pitroub1436f12010-11-09 22:55:55 +00001116 arg = arg.replace('\\', '\\\\')
1117 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001118
Antoine Pitroub1436f12010-11-09 22:55:55 +00001119 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001120
1121
Tim Peters07e99cb2001-01-14 23:47:14 +00001122 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001123
Guido van Rossum68468eb2003-02-27 20:14:51 +00001124 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001125
1126
Tim Peters07e99cb2001-01-14 23:47:14 +00001127 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001128 if typ == 'NO':
1129 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001130 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001131 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001132 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 if __debug__:
1134 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001135 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001136 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001137
1138
Piers Lauderf2d7d152002-02-22 01:15:17 +00001139 if __debug__:
1140
1141 def _mesg(self, s, secs=None):
1142 if secs is None:
1143 secs = time.time()
1144 tm = time.strftime('%M:%S', time.localtime(secs))
1145 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1146 sys.stderr.flush()
1147
1148 def _dump_ur(self, dict):
1149 # Dump untagged responses (in `dict').
1150 l = dict.items()
1151 if not l: return
1152 t = '\n\t\t'
1153 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1154 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1155
1156 def _log(self, line):
1157 # Keep log of last `_cmd_log_len' interactions for debugging.
1158 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1159 self._cmd_log_idx += 1
1160 if self._cmd_log_idx >= self._cmd_log_len:
1161 self._cmd_log_idx = 0
1162
1163 def print_log(self):
1164 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1165 i, n = self._cmd_log_idx, self._cmd_log_len
1166 while n:
1167 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001168 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001169 except:
1170 pass
1171 i += 1
1172 if i >= self._cmd_log_len:
1173 i = 0
1174 n -= 1
1175
1176
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001177if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001178
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001179 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001180
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001181 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001182
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001183 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001184
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001185 host - host's name (default: localhost);
1186 port - port number (default: standard IMAP4 SSL port).
1187 keyfile - PEM formatted file that contains your private key (default: None);
1188 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001189
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001190 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001191 """
Piers Laudera4f83132002-03-08 01:53:24 +00001192
1193
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001194 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1195 self.keyfile = keyfile
1196 self.certfile = certfile
1197 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001198
Christian Heimesfb5faf02008-11-05 19:39:50 +00001199 def _create_socket(self):
1200 sock = IMAP4._create_socket(self)
1201 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001202
Christian Heimesfb5faf02008-11-05 19:39:50 +00001203 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001204 """Setup connection to remote server on "host:port".
1205 (default: localhost:standard IMAP4 SSL port).
1206 This connection will be used by the routines:
1207 read, readline, send, shutdown.
1208 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001209 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001210
1211 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001212
1213
Piers Laudere0273de2002-11-22 05:53:04 +00001214class IMAP4_stream(IMAP4):
1215
1216 """IMAP4 client class over a stream
1217
1218 Instantiate with: IMAP4_stream(command)
1219
Christian Heimesfb5faf02008-11-05 19:39:50 +00001220 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001221
1222 for more documentation see the docstring of the parent class IMAP4.
1223 """
1224
1225
1226 def __init__(self, command):
1227 self.command = command
1228 IMAP4.__init__(self)
1229
1230
1231 def open(self, host = None, port = None):
1232 """Setup a stream connection.
1233 This connection will be used by the routines:
1234 read, readline, send, shutdown.
1235 """
1236 self.host = None # For compatibility with parent class
1237 self.port = None
1238 self.sock = None
1239 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001240 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001241 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001242 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1243 shell=True, close_fds=True)
1244 self.writefile = self.process.stdin
1245 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001246
1247 def read(self, size):
1248 """Read 'size' bytes from remote."""
1249 return self.readfile.read(size)
1250
1251
1252 def readline(self):
1253 """Read line from remote."""
1254 return self.readfile.readline()
1255
1256
1257 def send(self, data):
1258 """Send data to remote."""
1259 self.writefile.write(data)
1260 self.writefile.flush()
1261
1262
1263 def shutdown(self):
1264 """Close I/O established in "open"."""
1265 self.readfile.close()
1266 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001267 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001268
1269
1270
Guido van Rossumeda960a1998-06-18 14:24:28 +00001271class _Authenticator:
1272
Tim Peters07e99cb2001-01-14 23:47:14 +00001273 """Private class to provide en/decoding
1274 for base64-based authentication conversation.
1275 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001276
Tim Peters07e99cb2001-01-14 23:47:14 +00001277 def __init__(self, mechinst):
1278 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001279
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 def process(self, data):
1281 ret = self.mech(self.decode(data))
1282 if ret is None:
1283 return '*' # Abort conversation
1284 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001285
Tim Peters07e99cb2001-01-14 23:47:14 +00001286 def encode(self, inp):
1287 #
1288 # Invoke binascii.b2a_base64 iteratively with
1289 # short even length buffers, strip the trailing
1290 # line feed from the result and append. "Even"
1291 # means a number that factors to both 6 and 8,
1292 # so when it gets to the end of the 8-bit input
1293 # there's no partial 6-bit output.
1294 #
R David Murray774a39f2013-02-19 12:17:31 -05001295 oup = b''
1296 if isinstance(inp, str):
1297 inp = inp.encode('ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001298 while inp:
1299 if len(inp) > 48:
1300 t = inp[:48]
1301 inp = inp[48:]
1302 else:
1303 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001304 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001305 e = binascii.b2a_base64(t)
1306 if e:
1307 oup = oup + e[:-1]
1308 return oup
1309
1310 def decode(self, inp):
1311 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001312 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001313 return binascii.a2b_base64(inp)
1314
Guido van Rossumeda960a1998-06-18 14:24:28 +00001315
1316
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001317Mon2num = {b'Jan': 1, b'Feb': 2, b'Mar': 3, b'Apr': 4, b'May': 5, b'Jun': 6,
1318 b'Jul': 7, b'Aug': 8, b'Sep': 9, b'Oct': 10, b'Nov': 11, b'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001319
1320def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001321 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001323 Return corresponding local time. The return value is a
1324 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001325 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001326
Tim Peters07e99cb2001-01-14 23:47:14 +00001327 mo = InternalDate.match(resp)
1328 if not mo:
1329 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001330
Tim Peters07e99cb2001-01-14 23:47:14 +00001331 mon = Mon2num[mo.group('mon')]
1332 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001333
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001334 day = int(mo.group('day'))
1335 year = int(mo.group('year'))
1336 hour = int(mo.group('hour'))
1337 min = int(mo.group('min'))
1338 sec = int(mo.group('sec'))
1339 zoneh = int(mo.group('zoneh'))
1340 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001341
Tim Peters07e99cb2001-01-14 23:47:14 +00001342 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001345 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001347
Tim Peters07e99cb2001-01-14 23:47:14 +00001348 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001349 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001350
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001351 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001352
1353
1354
1355def Int2AP(num):
1356
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
Christian Heimesfb5faf02008-11-05 19:39:50 +00001359 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001360 num = int(abs(num))
1361 while num:
1362 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001363 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001364 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001365
1366
1367
1368def ParseFlags(resp):
1369
Tim Peters07e99cb2001-01-14 23:47:14 +00001370 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001371
Tim Peters07e99cb2001-01-14 23:47:14 +00001372 mo = Flags.match(resp)
1373 if not mo:
1374 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001375
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001376 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001377
1378
1379def Time2Internaldate(date_time):
1380
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001381 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001382
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001383 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001384 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001385 seconds since epoch (as returned by time.time()), a 9-tuple
1386 representing local time (as returned by time.localtime()), or a
1387 double-quoted string. In the last case, it is assumed to already
1388 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001389 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001390
Fred Drakedb519202002-01-05 17:17:09 +00001391 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001392 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001393 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001394 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001395 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001397 else:
1398 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001399
Tim Peters07e99cb2001-01-14 23:47:14 +00001400 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1401 if dt[0] == '0':
1402 dt = ' ' + dt[1:]
1403 if time.daylight and tt[-1]:
1404 zone = -time.altzone
1405 else:
1406 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001407 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001408
1409
1410
Guido van Rossum8c062211999-12-13 23:27:45 +00001411if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
Piers Laudere0273de2002-11-22 05:53:04 +00001413 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1414 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1415 # to test the IMAP4_stream class
1416
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001417 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001418
Tim Peters07e99cb2001-01-14 23:47:14 +00001419 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001420 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001421 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001422 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001423
Piers Laudere0273de2002-11-22 05:53:04 +00001424 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001425 for opt,val in optlist:
1426 if opt == '-d':
1427 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001428 elif opt == '-s':
1429 stream_command = val
1430 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001431
Tim Peters07e99cb2001-01-14 23:47:14 +00001432 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001433
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001435
Tim Peters07e99cb2001-01-14 23:47:14 +00001436 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001437 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001438
Piers Lauder47404ff2003-04-29 23:40:59 +00001439 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 +00001440 test_seq1 = (
1441 ('login', (USER, PASSWD)),
1442 ('create', ('/tmp/xxx 1',)),
1443 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1444 ('CREATE', ('/tmp/yyz 2',)),
1445 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1446 ('list', ('/tmp', 'yy*')),
1447 ('select', ('/tmp/yyz 2',)),
1448 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001449 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001450 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001451 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 ('expunge', ()),
1453 ('recent', ()),
1454 ('close', ()),
1455 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001456
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 test_seq2 = (
1458 ('select', ()),
1459 ('response',('UIDVALIDITY',)),
1460 ('uid', ('SEARCH', 'ALL')),
1461 ('response', ('EXISTS',)),
1462 ('append', (None, None, None, test_mesg)),
1463 ('recent', ()),
1464 ('logout', ()),
1465 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001466
Tim Peters07e99cb2001-01-14 23:47:14 +00001467 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001468 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001469 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001470 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001471 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001472 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001473
Tim Peters07e99cb2001-01-14 23:47:14 +00001474 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001475 if stream_command:
1476 M = IMAP4_stream(stream_command)
1477 else:
1478 M = IMAP4(host)
1479 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001480 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001481 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001482 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001483
Tim Peters07e99cb2001-01-14 23:47:14 +00001484 for cmd,args in test_seq1:
1485 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001486
Tim Peters07e99cb2001-01-14 23:47:14 +00001487 for ml in run('list', ('/tmp/', 'yy%')):
1488 mo = re.match(r'.*"([^"]+)"$', ml)
1489 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001490 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001491 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001492
Tim Peters07e99cb2001-01-14 23:47:14 +00001493 for cmd,args in test_seq2:
1494 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001495
Tim Peters07e99cb2001-01-14 23:47:14 +00001496 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1497 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001498
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001499 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001500 if not uid: continue
1501 run('uid', ('FETCH', '%s' % uid[-1],
1502 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001503
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001504 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001505
Tim Peters07e99cb2001-01-14 23:47:14 +00001506 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001507 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001508
Tim Peters07e99cb2001-01-14 23:47:14 +00001509 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001510 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001511If you would like to see debugging output,
1512try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001513''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001514
Tim Peters07e99cb2001-01-14 23:47:14 +00001515 raise