blob: 3f8c65a98b297266fc7bb27cd849f2b08ba81ad7 [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
Alexander Belopolsky8141cc72012-06-22 21:03:39 -040026from datetime import datetime, timezone, timedelta
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000027try:
28 import ssl
29 HAVE_SSL = True
30except ImportError:
31 HAVE_SSL = False
32
Thomas Wouters47b49bf2007-08-30 22:15:33 +000033__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000034 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000035
Tim Peters07e99cb2001-01-14 23:47:14 +000036# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000037
Christian Heimesfb5faf02008-11-05 19:39:50 +000038CRLF = b'\r\n'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000039Debug = 0
40IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000041IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000042AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000043
Tim Peters07e99cb2001-01-14 23:47:14 +000044# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000045
46Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000047 # name valid states
48 'APPEND': ('AUTH', 'SELECTED'),
49 'AUTHENTICATE': ('NONAUTH',),
50 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
51 'CHECK': ('SELECTED',),
52 'CLOSE': ('SELECTED',),
53 'COPY': ('SELECTED',),
54 'CREATE': ('AUTH', 'SELECTED'),
55 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000056 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000057 'EXAMINE': ('AUTH', 'SELECTED'),
58 'EXPUNGE': ('SELECTED',),
59 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000060 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000061 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000062 'GETQUOTA': ('AUTH', 'SELECTED'),
63 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000064 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000065 'LIST': ('AUTH', 'SELECTED'),
66 'LOGIN': ('NONAUTH',),
67 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
68 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000069 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000070 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000071 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000072 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000073 'RENAME': ('AUTH', 'SELECTED'),
74 'SEARCH': ('SELECTED',),
75 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000076 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000077 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000078 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000079 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000080 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000081 'STATUS': ('AUTH', 'SELECTED'),
82 'STORE': ('SELECTED',),
83 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000084 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000085 'UID': ('SELECTED',),
86 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
87 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000088
Tim Peters07e99cb2001-01-14 23:47:14 +000089# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000090
Christian Heimesfb5faf02008-11-05 19:39:50 +000091Continuation = re.compile(br'\+( (?P<data>.*))?')
92Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
93InternalDate = re.compile(br'.*INTERNALDATE "'
94 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
95 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
96 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
97 br'"')
98Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
99MapCRLF = re.compile(br'\r\n|\r|\n')
100Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
101Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +0000102Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000103 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104
105
106
107class IMAP4:
108
Tim Peters07e99cb2001-01-14 23:47:14 +0000109 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000110
Tim Peters07e99cb2001-01-14 23:47:14 +0000111 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000112
Tim Peters07e99cb2001-01-14 23:47:14 +0000113 host - host's name (default: localhost);
114 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000115
Tim Peters07e99cb2001-01-14 23:47:14 +0000116 All IMAP4rev1 commands are supported by methods of the same
117 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000118
Tim Peters07e99cb2001-01-14 23:47:14 +0000119 All arguments to commands are converted to strings, except for
120 AUTHENTICATE, and the last argument to APPEND which is passed as
121 an IMAP4 literal. If necessary (the string contains any
122 non-printing characters or white-space and isn't enclosed with
123 either parentheses or double quotes) each string is quoted.
124 However, the 'password' argument to the LOGIN command is always
125 quoted. If you want to avoid having an argument string quoted
126 (eg: the 'flags' argument to STORE) then enclose the string in
127 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 Each command returns a tuple: (type, [data, ...]) where 'type'
130 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000131 tagged response, or untagged results from command. Each 'data'
132 is either a string, or a tuple. If a tuple, then the first part
133 is the header of the response, and the second part contains
134 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000135
Tim Peters07e99cb2001-01-14 23:47:14 +0000136 Errors raise the exception class <instance>.error("<reason>").
137 IMAP4 server errors raise <instance>.abort("<reason>"),
138 which is a sub-class of 'error'. Mailbox status changes
139 from READ-WRITE to READ-ONLY raise the exception class
140 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000141
Tim Peters07e99cb2001-01-14 23:47:14 +0000142 "error" exceptions imply a program error.
143 "abort" exceptions imply the connection should be reset, and
144 the command re-tried.
145 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000146
Piers Lauderd80ef022005-06-01 23:50:52 +0000147 Note: to use this module, you must read the RFCs pertaining to the
148 IMAP4 protocol, as the semantics of the arguments to each IMAP4
149 command are left to the invoker, not to mention the results. Also,
150 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000152
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 class error(Exception): pass # Logical errors - debug required
154 class abort(error): pass # Service errors - close and retry
155 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000156
Tim Peters07e99cb2001-01-14 23:47:14 +0000157 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 self.debug = Debug
159 self.state = 'LOGOUT'
160 self.literal = None # A literal argument to a command
161 self.tagged_commands = {} # Tagged commands awaiting response
162 self.untagged_responses = {} # {typ: [data, ...], ...}
163 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000164 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000166 self._tls_established = False
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000167
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000169
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Victor Stinner33e649c2011-01-05 23:01:37 +0000172 try:
173 self._connect()
174 except Exception:
175 try:
176 self.shutdown()
177 except socket.error:
178 pass
179 raise
180
181
182 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 # Create unique tag for this session,
184 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000185
Piers Lauder2dfc1682005-07-05 04:20:07 +0000186 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000187 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000188 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000189 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000190
Tim Peters07e99cb2001-01-14 23:47:14 +0000191 # Get server welcome message,
192 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000193
Tim Peters07e99cb2001-01-14 23:47:14 +0000194 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000195 self._cmd_log_len = 10
196 self._cmd_log_idx = 0
197 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000199 self._mesg('imaplib version %s' % __version__)
200 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000201
Tim Peters07e99cb2001-01-14 23:47:14 +0000202 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000203 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000204 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000205 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 self.state = 'NONAUTH'
207 else:
208 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000209
Antoine Pitroudbe75192010-11-16 17:55:26 +0000210 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000211 if __debug__:
212 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000213 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000214
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 for version in AllowedVersions:
216 if not version in self.capabilities:
217 continue
218 self.PROTOCOL_VERSION = version
219 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000220
Tim Peters07e99cb2001-01-14 23:47:14 +0000221 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000222
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000223
Tim Peters07e99cb2001-01-14 23:47:14 +0000224 def __getattr__(self, attr):
225 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000226 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000227 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000228 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000229
230
231
Piers Lauder15e5d532001-07-20 10:52:06 +0000232 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000233
234
Christian Heimesfb5faf02008-11-05 19:39:50 +0000235 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000236 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000237
Piers Lauderf97b2d72002-06-05 22:31:57 +0000238 def open(self, host = '', port = IMAP4_PORT):
239 """Setup connection to remote server on "host:port"
240 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000241 This connection will be used by the routines:
242 read, readline, send, shutdown.
243 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000244 self.host = host
245 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000246 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000247 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000248
249
Piers Lauder15e5d532001-07-20 10:52:06 +0000250 def read(self, size):
251 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200252 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000253
254
255 def readline(self):
256 """Read line from remote."""
257 return self.file.readline()
258
259
260 def send(self, data):
261 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000262 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000263
Piers Lauderf2d7d152002-02-22 01:15:17 +0000264
Piers Lauder15e5d532001-07-20 10:52:06 +0000265 def shutdown(self):
266 """Close I/O established in "open"."""
267 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000268 try:
269 self.sock.shutdown(socket.SHUT_RDWR)
270 except socket.error as e:
271 # The server might already have closed the connection
272 if e.errno != errno.ENOTCONN:
273 raise
274 finally:
275 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000276
277
278 def socket(self):
279 """Return socket instance used to connect to IMAP4 server.
280
281 socket = <instance>.socket()
282 """
283 return self.sock
284
285
286
287 # Utility methods
288
289
Tim Peters07e99cb2001-01-14 23:47:14 +0000290 def recent(self):
291 """Return most recent 'RECENT' responses if any exist,
292 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000293
Tim Peters07e99cb2001-01-14 23:47:14 +0000294 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000295
Tim Peters07e99cb2001-01-14 23:47:14 +0000296 'data' is None if no new messages,
297 else list of RECENT responses, most recent last.
298 """
299 name = 'RECENT'
300 typ, dat = self._untagged_response('OK', [None], name)
301 if dat[-1]:
302 return typ, dat
303 typ, dat = self.noop() # Prod server for response
304 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000305
306
Tim Peters07e99cb2001-01-14 23:47:14 +0000307 def response(self, code):
308 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000309
Tim Peters07e99cb2001-01-14 23:47:14 +0000310 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000311
Tim Peters07e99cb2001-01-14 23:47:14 +0000312 (code, [data]) = <instance>.response(code)
313 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000314 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000315
316
Guido van Rossum26367a01998-09-28 15:34:46 +0000317
Tim Peters07e99cb2001-01-14 23:47:14 +0000318 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000319
320
Tim Peters07e99cb2001-01-14 23:47:14 +0000321 def append(self, mailbox, flags, date_time, message):
322 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000323
Tim Peters07e99cb2001-01-14 23:47:14 +0000324 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000325
Tim Peters07e99cb2001-01-14 23:47:14 +0000326 All args except `message' can be None.
327 """
328 name = 'APPEND'
329 if not mailbox:
330 mailbox = 'INBOX'
331 if flags:
332 if (flags[0],flags[-1]) != ('(',')'):
333 flags = '(%s)' % flags
334 else:
335 flags = None
336 if date_time:
337 date_time = Time2Internaldate(date_time)
338 else:
339 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000340 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000342
343
Tim Peters07e99cb2001-01-14 23:47:14 +0000344 def authenticate(self, mechanism, authobject):
345 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000346
Tim Peters07e99cb2001-01-14 23:47:14 +0000347 'mechanism' specifies which authentication mechanism is to
348 be used - it must appear in <instance>.capabilities in the
349 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000350
Tim Peters07e99cb2001-01-14 23:47:14 +0000351 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000352
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000354
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 It will be called to process server continuation responses.
356 It should return data that will be encoded and sent to server.
357 It should return None if the client abort response '*' should
358 be sent instead.
359 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000360 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000361 # XXX: shouldn't this code be removed, not commented out?
362 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000363 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000364 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000365 self.literal = _Authenticator(authobject).process
366 typ, dat = self._simple_command('AUTHENTICATE', mech)
367 if typ != 'OK':
368 raise self.error(dat[-1])
369 self.state = 'AUTH'
370 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000371
372
Piers Lauderd80ef022005-06-01 23:50:52 +0000373 def capability(self):
374 """(typ, [data]) = <instance>.capability()
375 Fetch capabilities list from server."""
376
377 name = 'CAPABILITY'
378 typ, dat = self._simple_command(name)
379 return self._untagged_response(typ, dat, name)
380
381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 def check(self):
383 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 (typ, [data]) = <instance>.check()
386 """
387 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000388
389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 def close(self):
391 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 Deleted messages are removed from writable mailbox.
394 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 (typ, [data]) = <instance>.close()
397 """
398 try:
399 typ, dat = self._simple_command('CLOSE')
400 finally:
401 self.state = 'AUTH'
402 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 def copy(self, message_set, new_mailbox):
406 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
409 """
410 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 def create(self, mailbox):
414 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 (typ, [data]) = <instance>.create(mailbox)
417 """
418 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000419
420
Tim Peters07e99cb2001-01-14 23:47:14 +0000421 def delete(self, mailbox):
422 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000423
Tim Peters07e99cb2001-01-14 23:47:14 +0000424 (typ, [data]) = <instance>.delete(mailbox)
425 """
426 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000427
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000428 def deleteacl(self, mailbox, who):
429 """Delete the ACLs (remove any rights) set for who on mailbox.
430
431 (typ, [data]) = <instance>.deleteacl(mailbox, who)
432 """
433 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000434
Tim Peters07e99cb2001-01-14 23:47:14 +0000435 def expunge(self):
436 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000437
Tim Peters07e99cb2001-01-14 23:47:14 +0000438 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000439
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Tim Peters07e99cb2001-01-14 23:47:14 +0000442 'data' is list of 'EXPUNGE'd message numbers in order received.
443 """
444 name = 'EXPUNGE'
445 typ, dat = self._simple_command(name)
446 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000447
448
Tim Peters07e99cb2001-01-14 23:47:14 +0000449 def fetch(self, message_set, message_parts):
450 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000453
Tim Peters07e99cb2001-01-14 23:47:14 +0000454 'message_parts' should be a string of selected parts
455 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000456
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 'data' are tuples of message part envelope and data.
458 """
459 name = 'FETCH'
460 typ, dat = self._simple_command(name, message_set, message_parts)
461 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000462
463
Piers Lauder15e5d532001-07-20 10:52:06 +0000464 def getacl(self, mailbox):
465 """Get the ACLs for a mailbox.
466
467 (typ, [data]) = <instance>.getacl(mailbox)
468 """
469 typ, dat = self._simple_command('GETACL', mailbox)
470 return self._untagged_response(typ, dat, 'ACL')
471
472
Piers Lauderd80ef022005-06-01 23:50:52 +0000473 def getannotation(self, mailbox, entry, attribute):
474 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
475 Retrieve ANNOTATIONs."""
476
477 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
478 return self._untagged_response(typ, dat, 'ANNOTATION')
479
480
Piers Lauder3fca2912002-06-17 07:07:20 +0000481 def getquota(self, root):
482 """Get the quota root's resource usage and limits.
483
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000484 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000485
486 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000487 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000488 typ, dat = self._simple_command('GETQUOTA', root)
489 return self._untagged_response(typ, dat, 'QUOTA')
490
491
492 def getquotaroot(self, mailbox):
493 """Get the list of quota roots for the named mailbox.
494
495 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000496 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000497 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000498 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
499 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000500 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000501
502
Tim Peters07e99cb2001-01-14 23:47:14 +0000503 def list(self, directory='""', pattern='*'):
504 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
Tim Peters07e99cb2001-01-14 23:47:14 +0000506 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 'data' is list of LIST responses.
509 """
510 name = 'LIST'
511 typ, dat = self._simple_command(name, directory, pattern)
512 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 def login(self, user, password):
516 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000517
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000519
Tim Peters07e99cb2001-01-14 23:47:14 +0000520 NB: 'password' will be quoted.
521 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000522 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
523 if typ != 'OK':
524 raise self.error(dat[-1])
525 self.state = 'AUTH'
526 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000527
528
Piers Laudere0273de2002-11-22 05:53:04 +0000529 def login_cram_md5(self, user, password):
530 """ Force use of CRAM-MD5 authentication.
531
532 (typ, [data]) = <instance>.login_cram_md5(user, password)
533 """
534 self.user, self.password = user, password
535 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
536
537
538 def _CRAM_MD5_AUTH(self, challenge):
539 """ Authobject to use with CRAM-MD5 authentication. """
540 import hmac
541 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
542
543
Tim Peters07e99cb2001-01-14 23:47:14 +0000544 def logout(self):
545 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000546
Tim Peters07e99cb2001-01-14 23:47:14 +0000547 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000548
Tim Peters07e99cb2001-01-14 23:47:14 +0000549 Returns server 'BYE' response.
550 """
551 self.state = 'LOGOUT'
552 try: typ, dat = self._simple_command('LOGOUT')
553 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000554 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000555 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000556 return 'BYE', self.untagged_responses['BYE']
557 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000558
559
Tim Peters07e99cb2001-01-14 23:47:14 +0000560 def lsub(self, directory='""', pattern='*'):
561 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000562
Tim Peters07e99cb2001-01-14 23:47:14 +0000563 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000564
Tim Peters07e99cb2001-01-14 23:47:14 +0000565 'data' are tuples of message part envelope and data.
566 """
567 name = 'LSUB'
568 typ, dat = self._simple_command(name, directory, pattern)
569 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000570
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000571 def myrights(self, mailbox):
572 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
573
574 (typ, [data]) = <instance>.myrights(mailbox)
575 """
576 typ,dat = self._simple_command('MYRIGHTS', mailbox)
577 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000578
Piers Lauder15e5d532001-07-20 10:52:06 +0000579 def namespace(self):
580 """ Returns IMAP namespaces ala rfc2342
581
582 (typ, [data, ...]) = <instance>.namespace()
583 """
584 name = 'NAMESPACE'
585 typ, dat = self._simple_command(name)
586 return self._untagged_response(typ, dat, name)
587
588
Tim Peters07e99cb2001-01-14 23:47:14 +0000589 def noop(self):
590 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000591
Piers Laudere0273de2002-11-22 05:53:04 +0000592 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 """
594 if __debug__:
595 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000596 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000597 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000598
599
Tim Peters07e99cb2001-01-14 23:47:14 +0000600 def partial(self, message_num, message_part, start, length):
601 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000602
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000604
Tim Peters07e99cb2001-01-14 23:47:14 +0000605 'data' is tuple of message part envelope and data.
606 """
607 name = 'PARTIAL'
608 typ, dat = self._simple_command(name, message_num, message_part, start, length)
609 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000610
611
Piers Laudere0273de2002-11-22 05:53:04 +0000612 def proxyauth(self, user):
613 """Assume authentication as "user".
614
615 Allows an authorised administrator to proxy into any user's
616 mailbox.
617
618 (typ, [data]) = <instance>.proxyauth(user)
619 """
620
621 name = 'PROXYAUTH'
622 return self._simple_command('PROXYAUTH', user)
623
624
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 def rename(self, oldmailbox, newmailbox):
626 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
Piers Laudere0273de2002-11-22 05:53:04 +0000628 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000629 """
630 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000631
632
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 def search(self, charset, *criteria):
634 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000636 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000637
Tim Peters07e99cb2001-01-14 23:47:14 +0000638 'data' is space separated list of matching message numbers.
639 """
640 name = 'SEARCH'
641 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000642 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000643 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000644 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000645 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000646
647
Piers Lauder14f39402005-08-31 10:46:29 +0000648 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000650
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000652
Piers Lauder14f39402005-08-31 10:46:29 +0000653 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000656
657 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
658 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 self.untagged_responses = {} # Flush old responses.
661 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000662 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000663 name = 'EXAMINE'
664 else:
665 name = 'SELECT'
666 typ, dat = self._simple_command(name, mailbox)
667 if typ != 'OK':
668 self.state = 'AUTH' # Might have been 'SELECTED'
669 return typ, dat
670 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000671 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000672 and not readonly:
673 if __debug__:
674 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000675 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 raise self.readonly('%s is not writable' % mailbox)
677 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000678
679
Piers Lauder15e5d532001-07-20 10:52:06 +0000680 def setacl(self, mailbox, who, what):
681 """Set a mailbox acl.
682
Piers Lauderf167dc32004-03-25 00:12:21 +0000683 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000684 """
685 return self._simple_command('SETACL', mailbox, who, what)
686
687
Piers Lauderd80ef022005-06-01 23:50:52 +0000688 def setannotation(self, *args):
689 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
690 Set ANNOTATIONs."""
691
692 typ, dat = self._simple_command('SETANNOTATION', *args)
693 return self._untagged_response(typ, dat, 'ANNOTATION')
694
695
Piers Lauder3fca2912002-06-17 07:07:20 +0000696 def setquota(self, root, limits):
697 """Set the quota root's resource limits.
698
699 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000700 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000701 typ, dat = self._simple_command('SETQUOTA', root, limits)
702 return self._untagged_response(typ, dat, 'QUOTA')
703
704
Piers Lauder15e5d532001-07-20 10:52:06 +0000705 def sort(self, sort_criteria, charset, *search_criteria):
706 """IMAP4rev1 extension SORT command.
707
708 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
709 """
710 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000711 #if not name in self.capabilities: # Let the server decide!
712 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000713 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000714 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000715 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000716 return self._untagged_response(typ, dat, name)
717
718
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000719 def starttls(self, ssl_context=None):
720 name = 'STARTTLS'
721 if not HAVE_SSL:
722 raise self.error('SSL support missing')
723 if self._tls_established:
724 raise self.abort('TLS session already established')
725 if name not in self.capabilities:
726 raise self.abort('TLS not supported by server')
727 # Generate a default SSL context if none was passed.
728 if ssl_context is None:
729 ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
730 # SSLv2 considered harmful.
731 ssl_context.options |= ssl.OP_NO_SSLv2
732 typ, dat = self._simple_command(name)
733 if typ == 'OK':
734 self.sock = ssl_context.wrap_socket(self.sock)
735 self.file = self.sock.makefile('rb')
736 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000737 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000738 else:
739 raise self.error("Couldn't establish TLS session")
740 return self._untagged_response(typ, dat, name)
741
742
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 def status(self, mailbox, names):
744 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000745
Tim Peters07e99cb2001-01-14 23:47:14 +0000746 (typ, [data]) = <instance>.status(mailbox, names)
747 """
748 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000749 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000750 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000751 typ, dat = self._simple_command(name, mailbox, names)
752 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000753
754
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 def store(self, message_set, command, flags):
756 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000757
Tim Peters07e99cb2001-01-14 23:47:14 +0000758 (typ, [data]) = <instance>.store(message_set, command, flags)
759 """
760 if (flags[0],flags[-1]) != ('(',')'):
761 flags = '(%s)' % flags # Avoid quoting the flags
762 typ, dat = self._simple_command('STORE', message_set, command, flags)
763 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000764
765
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 def subscribe(self, mailbox):
767 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000768
Tim Peters07e99cb2001-01-14 23:47:14 +0000769 (typ, [data]) = <instance>.subscribe(mailbox)
770 """
771 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000772
773
Martin v. Löwisd8921372003-11-10 06:44:44 +0000774 def thread(self, threading_algorithm, charset, *search_criteria):
775 """IMAPrev1 extension THREAD command.
776
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000777 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000778 """
779 name = 'THREAD'
780 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
781 return self._untagged_response(typ, dat, name)
782
783
Tim Peters07e99cb2001-01-14 23:47:14 +0000784 def uid(self, command, *args):
785 """Execute "command arg ..." with messages identified by UID,
786 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000787
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000789
Tim Peters07e99cb2001-01-14 23:47:14 +0000790 Returns response appropriate to 'command'.
791 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000792 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000793 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 raise self.error("Unknown IMAP4 UID command: %s" % command)
795 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000796 raise self.error("command %s illegal in state %s, "
797 "only allowed in states %s" %
798 (command, self.state,
799 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000801 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000802 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000803 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 else:
805 name = 'FETCH'
806 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000807
808
Tim Peters07e99cb2001-01-14 23:47:14 +0000809 def unsubscribe(self, mailbox):
810 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000811
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 (typ, [data]) = <instance>.unsubscribe(mailbox)
813 """
814 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 def xatom(self, name, *args):
818 """Allow simple extension commands
819 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000820
Piers Lauder15e5d532001-07-20 10:52:06 +0000821 Assumes command is legal in current state.
822
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000824
825 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000827 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000828 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000829 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000830 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000831 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000832 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000833
834
835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000837
838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000840 if dat is None:
841 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 ur = self.untagged_responses
843 if __debug__:
844 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000845 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000847 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000848 ur[typ].append(dat)
849 else:
850 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000851
852
Tim Peters07e99cb2001-01-14 23:47:14 +0000853 def _check_bye(self):
854 bye = self.untagged_responses.get('BYE')
855 if bye:
Antoine Pitroudac47912010-11-10 00:18:40 +0000856 raise self.abort(bye[-1].decode('ascii', 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000857
858
Tim Peters07e99cb2001-01-14 23:47:14 +0000859 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 if self.state not in Commands[name]:
862 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000863 raise self.error("command %s illegal in state %s, "
864 "only allowed in states %s" %
865 (name, self.state,
866 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000869 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000871
Raymond Hettinger54f02222002-06-01 14:18:47 +0000872 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 and not self.is_readonly:
874 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000877 name = bytes(name, 'ASCII')
878 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 for arg in args:
880 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000881 if isinstance(arg, str):
882 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000883 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 literal = self.literal
886 if literal is not None:
887 self.literal = None
888 if type(literal) is type(self._command):
889 literator = literal
890 else:
891 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000892 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000893
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 if __debug__:
895 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000896 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000898 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000901 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000902 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 if literal is None:
906 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 while 1:
909 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000910
Tim Peters07e99cb2001-01-14 23:47:14 +0000911 while self._get_response():
912 if self.tagged_commands[tag]: # BAD/NO?
913 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000914
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000916
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 if literator:
918 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000919
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 if __debug__:
921 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000922 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000925 self.send(literal)
926 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000927 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 if not literator:
931 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000934
935
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000937 # BYE is expected after LOGOUT
938 if name != 'LOGOUT':
939 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 try:
941 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000942 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000944 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +0000946 if name != 'LOGOUT':
947 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 if typ == 'BAD':
949 raise self.error('%s command error: %s %s' % (name, typ, data))
950 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000951
952
Antoine Pitroudbe75192010-11-16 17:55:26 +0000953 def _get_capabilities(self):
954 typ, dat = self.capability()
955 if dat == [None]:
956 raise self.error('no CAPABILITY response from server')
957 dat = str(dat[-1], "ASCII")
958 dat = dat.upper()
959 self.capabilities = tuple(dat.split())
960
961
Tim Peters07e99cb2001-01-14 23:47:14 +0000962 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000963
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 # Read response and store.
965 #
966 # Returns None for continuation responses,
967 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000970
Tim Peters07e99cb2001-01-14 23:47:14 +0000971 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 if self._match(self.tagre, resp):
974 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000975 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000979 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 dat = self.mo.group('data')
981 self.tagged_commands[tag] = (typ, [dat])
982 else:
983 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 if not self._match(Untagged_response, resp):
988 if self._match(Untagged_status, resp):
989 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 if self.mo is None:
992 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 if self._match(Continuation, resp):
995 self.continuation_response = self.mo.group('data')
996 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001001 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001003 if dat is None: dat = b'' # Null untagged response
1004 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001005
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001007
Tim Peters07e99cb2001-01-14 23:47:14 +00001008 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001011
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001012 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 if __debug__:
1014 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001015 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001016 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001019
Tim Peters07e99cb2001-01-14 23:47:14 +00001020 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001031 typ = self.mo.group('type')
1032 typ = str(typ, "ASCII")
1033 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001034
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 if __debug__:
1036 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001037 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001038
Tim Peters07e99cb2001-01-14 23:47:14 +00001039 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001040
1041
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001043
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 while 1:
1045 result = self.tagged_commands[tag]
1046 if result is not None:
1047 del self.tagged_commands[tag]
1048 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 # Some have reported "unexpected response" exceptions.
1051 # Note that ignoring them here causes loops.
1052 # Instead, send me details of the unexpected response and
1053 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001054
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 try:
1056 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001057 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 if __debug__:
1059 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001060 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001062
1063
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001065
Piers Lauder15e5d532001-07-20 10:52:06 +00001066 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 if not line:
1068 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001069
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001071 if not line.endswith(b'\r\n'):
1072 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 line = line[:-2]
1075 if __debug__:
1076 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001077 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001079 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001081
1082
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001084
Tim Peters07e99cb2001-01-14 23:47:14 +00001085 # Run compiled regular expression match method on 's'.
1086 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001087
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 self.mo = cre.match(s)
1089 if __debug__:
1090 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001091 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001093
1094
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001096
Christian Heimesfb5faf02008-11-05 19:39:50 +00001097 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001098 self.tagnum = self.tagnum + 1
1099 self.tagged_commands[tag] = None
1100 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001101
1102
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001104
Antoine Pitroub1436f12010-11-09 22:55:55 +00001105 arg = arg.replace('\\', '\\\\')
1106 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001107
Antoine Pitroub1436f12010-11-09 22:55:55 +00001108 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001109
1110
Tim Peters07e99cb2001-01-14 23:47:14 +00001111 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001112
Guido van Rossum68468eb2003-02-27 20:14:51 +00001113 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001114
1115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001117 if typ == 'NO':
1118 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001119 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001120 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001121 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001122 if __debug__:
1123 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001124 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001125 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001126
1127
Piers Lauderf2d7d152002-02-22 01:15:17 +00001128 if __debug__:
1129
1130 def _mesg(self, s, secs=None):
1131 if secs is None:
1132 secs = time.time()
1133 tm = time.strftime('%M:%S', time.localtime(secs))
1134 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1135 sys.stderr.flush()
1136
1137 def _dump_ur(self, dict):
1138 # Dump untagged responses (in `dict').
1139 l = dict.items()
1140 if not l: return
1141 t = '\n\t\t'
1142 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1143 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1144
1145 def _log(self, line):
1146 # Keep log of last `_cmd_log_len' interactions for debugging.
1147 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1148 self._cmd_log_idx += 1
1149 if self._cmd_log_idx >= self._cmd_log_len:
1150 self._cmd_log_idx = 0
1151
1152 def print_log(self):
1153 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1154 i, n = self._cmd_log_idx, self._cmd_log_len
1155 while n:
1156 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001157 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001158 except:
1159 pass
1160 i += 1
1161 if i >= self._cmd_log_len:
1162 i = 0
1163 n -= 1
1164
1165
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001166if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001167
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001168 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001169
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001170 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001171
Antoine Pitrou08728162011-05-06 18:49:52 +02001172 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001173
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001174 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001175 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001176 keyfile - PEM formatted file that contains your private key (default: None);
1177 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001178 ssl_context - a SSLContext object that contains your certificate chain
1179 and private key (default: None)
1180 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001181 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001182
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001183 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001184 """
Piers Laudera4f83132002-03-08 01:53:24 +00001185
1186
Antoine Pitrou08728162011-05-06 18:49:52 +02001187 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None):
1188 if ssl_context is not None and keyfile is not None:
1189 raise ValueError("ssl_context and keyfile arguments are mutually "
1190 "exclusive")
1191 if ssl_context is not None and certfile is not None:
1192 raise ValueError("ssl_context and certfile arguments are mutually "
1193 "exclusive")
1194
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001195 self.keyfile = keyfile
1196 self.certfile = certfile
Antoine Pitrou08728162011-05-06 18:49:52 +02001197 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001198 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001199
Christian Heimesfb5faf02008-11-05 19:39:50 +00001200 def _create_socket(self):
1201 sock = IMAP4._create_socket(self)
Antoine Pitrou08728162011-05-06 18:49:52 +02001202 if self.ssl_context:
1203 return self.ssl_context.wrap_socket(sock)
1204 else:
1205 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001206
Christian Heimesfb5faf02008-11-05 19:39:50 +00001207 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001208 """Setup connection to remote server on "host:port".
1209 (default: localhost:standard IMAP4 SSL port).
1210 This connection will be used by the routines:
1211 read, readline, send, shutdown.
1212 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001213 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001214
1215 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001216
1217
Piers Laudere0273de2002-11-22 05:53:04 +00001218class IMAP4_stream(IMAP4):
1219
1220 """IMAP4 client class over a stream
1221
1222 Instantiate with: IMAP4_stream(command)
1223
Christian Heimesfb5faf02008-11-05 19:39:50 +00001224 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001225
1226 for more documentation see the docstring of the parent class IMAP4.
1227 """
1228
1229
1230 def __init__(self, command):
1231 self.command = command
1232 IMAP4.__init__(self)
1233
1234
1235 def open(self, host = None, port = None):
1236 """Setup a stream connection.
1237 This connection will be used by the routines:
1238 read, readline, send, shutdown.
1239 """
1240 self.host = None # For compatibility with parent class
1241 self.port = None
1242 self.sock = None
1243 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001244 self.process = subprocess.Popen(self.command,
1245 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1246 shell=True, close_fds=True)
1247 self.writefile = self.process.stdin
1248 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001249
1250 def read(self, size):
1251 """Read 'size' bytes from remote."""
1252 return self.readfile.read(size)
1253
1254
1255 def readline(self):
1256 """Read line from remote."""
1257 return self.readfile.readline()
1258
1259
1260 def send(self, data):
1261 """Send data to remote."""
1262 self.writefile.write(data)
1263 self.writefile.flush()
1264
1265
1266 def shutdown(self):
1267 """Close I/O established in "open"."""
1268 self.readfile.close()
1269 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001270 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001271
1272
1273
Guido van Rossumeda960a1998-06-18 14:24:28 +00001274class _Authenticator:
1275
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 """Private class to provide en/decoding
1277 for base64-based authentication conversation.
1278 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001279
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 def __init__(self, mechinst):
1281 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001282
Tim Peters07e99cb2001-01-14 23:47:14 +00001283 def process(self, data):
1284 ret = self.mech(self.decode(data))
1285 if ret is None:
1286 return '*' # Abort conversation
1287 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001288
Tim Peters07e99cb2001-01-14 23:47:14 +00001289 def encode(self, inp):
1290 #
1291 # Invoke binascii.b2a_base64 iteratively with
1292 # short even length buffers, strip the trailing
1293 # line feed from the result and append. "Even"
1294 # means a number that factors to both 6 and 8,
1295 # so when it gets to the end of the 8-bit input
1296 # there's no partial 6-bit output.
1297 #
1298 oup = ''
1299 while inp:
1300 if len(inp) > 48:
1301 t = inp[:48]
1302 inp = inp[48:]
1303 else:
1304 t = inp
1305 inp = ''
1306 e = binascii.b2a_base64(t)
1307 if e:
1308 oup = oup + e[:-1]
1309 return oup
1310
1311 def decode(self, inp):
1312 if not inp:
1313 return ''
1314 return binascii.a2b_base64(inp)
1315
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001316Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1317Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001318
1319def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001320 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001321
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001322 Return corresponding local time. The return value is a
1323 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001324 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001325
Tim Peters07e99cb2001-01-14 23:47:14 +00001326 mo = InternalDate.match(resp)
1327 if not mo:
1328 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001329
Tim Peters07e99cb2001-01-14 23:47:14 +00001330 mon = Mon2num[mo.group('mon')]
1331 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001332
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001333 day = int(mo.group('day'))
1334 year = int(mo.group('year'))
1335 hour = int(mo.group('hour'))
1336 min = int(mo.group('min'))
1337 sec = int(mo.group('sec'))
1338 zoneh = int(mo.group('zoneh'))
1339 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001340
Tim Peters07e99cb2001-01-14 23:47:14 +00001341 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001342
Tim Peters07e99cb2001-01-14 23:47:14 +00001343 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001344 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001345 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001346
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001348 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001349
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001350 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001351
1352
1353
1354def Int2AP(num):
1355
Tim Peters07e99cb2001-01-14 23:47:14 +00001356 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001357
Christian Heimesfb5faf02008-11-05 19:39:50 +00001358 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001359 num = int(abs(num))
1360 while num:
1361 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001362 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001363 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001364
1365
1366
1367def ParseFlags(resp):
1368
Tim Peters07e99cb2001-01-14 23:47:14 +00001369 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001370
Tim Peters07e99cb2001-01-14 23:47:14 +00001371 mo = Flags.match(resp)
1372 if not mo:
1373 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001374
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001375 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001376
1377
1378def Time2Internaldate(date_time):
1379
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001380 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001381
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001382 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001383 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001384 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001385 representing local time, an instance of time.struct_time (as
1386 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001387 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 """
Fred Drakedb519202002-01-05 17:17:09 +00001390 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001391 dt = datetime.fromtimestamp(date_time,
1392 timezone.utc).astimezone()
1393 elif isinstance(date_time, tuple):
1394 try:
1395 gmtoff = date_time.tm_gmtoff
1396 except AttributeError:
1397 if time.daylight:
1398 dst = date_time[8]
1399 if dst == -1:
1400 dst = time.localtime(time.mktime(date_time))[8]
1401 gmtoff = -(time.timezone, time.altzone)[dst]
1402 else:
1403 gmtoff = -time.timezone
1404 delta = timedelta(seconds=gmtoff)
1405 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1406 elif isinstance(date_time, datetime):
1407 if date_time.tzinfo is None:
1408 raise ValueError("date_time must be aware")
1409 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001410 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001411 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001412 else:
1413 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001414 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1415 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001416
1417
1418
Guido van Rossum8c062211999-12-13 23:27:45 +00001419if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001420
Piers Laudere0273de2002-11-22 05:53:04 +00001421 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1422 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1423 # to test the IMAP4_stream class
1424
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001425 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001426
Tim Peters07e99cb2001-01-14 23:47:14 +00001427 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001428 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001429 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001430 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001431
Piers Laudere0273de2002-11-22 05:53:04 +00001432 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 for opt,val in optlist:
1434 if opt == '-d':
1435 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001436 elif opt == '-s':
1437 stream_command = val
1438 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001439
Tim Peters07e99cb2001-01-14 23:47:14 +00001440 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001441
Tim Peters07e99cb2001-01-14 23:47:14 +00001442 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001443
Tim Peters07e99cb2001-01-14 23:47:14 +00001444 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001445 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001446
Piers Lauder47404ff2003-04-29 23:40:59 +00001447 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 +00001448 test_seq1 = (
1449 ('login', (USER, PASSWD)),
1450 ('create', ('/tmp/xxx 1',)),
1451 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1452 ('CREATE', ('/tmp/yyz 2',)),
1453 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1454 ('list', ('/tmp', 'yy*')),
1455 ('select', ('/tmp/yyz 2',)),
1456 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001457 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001458 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001459 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001460 ('expunge', ()),
1461 ('recent', ()),
1462 ('close', ()),
1463 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001464
Tim Peters07e99cb2001-01-14 23:47:14 +00001465 test_seq2 = (
1466 ('select', ()),
1467 ('response',('UIDVALIDITY',)),
1468 ('uid', ('SEARCH', 'ALL')),
1469 ('response', ('EXISTS',)),
1470 ('append', (None, None, None, test_mesg)),
1471 ('recent', ()),
1472 ('logout', ()),
1473 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001474
Tim Peters07e99cb2001-01-14 23:47:14 +00001475 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001476 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001477 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001478 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001479 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001480 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001481
Tim Peters07e99cb2001-01-14 23:47:14 +00001482 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001483 if stream_command:
1484 M = IMAP4_stream(stream_command)
1485 else:
1486 M = IMAP4(host)
1487 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001488 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001489 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001490 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001491
Tim Peters07e99cb2001-01-14 23:47:14 +00001492 for cmd,args in test_seq1:
1493 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001494
Tim Peters07e99cb2001-01-14 23:47:14 +00001495 for ml in run('list', ('/tmp/', 'yy%')):
1496 mo = re.match(r'.*"([^"]+)"$', ml)
1497 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001498 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001499 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001500
Tim Peters07e99cb2001-01-14 23:47:14 +00001501 for cmd,args in test_seq2:
1502 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001503
Tim Peters07e99cb2001-01-14 23:47:14 +00001504 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1505 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001506
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001507 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001508 if not uid: continue
1509 run('uid', ('FETCH', '%s' % uid[-1],
1510 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001511
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001512 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001513
Tim Peters07e99cb2001-01-14 23:47:14 +00001514 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001515 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001516
Tim Peters07e99cb2001-01-14 23:47:14 +00001517 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001518 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001519If you would like to see debugging output,
1520try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001521''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001522
Tim Peters07e99cb2001-01-14 23:47:14 +00001523 raise