blob: 42353bbd64830be0950260b03f37d901c3e231cd [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
R David Murrayfcb6d6a2013-03-19 13:52:33 -040027from io import DEFAULT_BUFFER_SIZE
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000028
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000029try:
30 import ssl
31 HAVE_SSL = True
Brett Cannoncd171c82013-07-04 17:43:24 -040032except ImportError:
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000033 HAVE_SSL = False
34
Thomas Wouters47b49bf2007-08-30 22:15:33 +000035__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000036 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000037
Tim Peters07e99cb2001-01-14 23:47:14 +000038# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000039
Christian Heimesfb5faf02008-11-05 19:39:50 +000040CRLF = b'\r\n'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000041Debug = 0
42IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000043IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000044AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000045
Tim Peters07e99cb2001-01-14 23:47:14 +000046# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000047
48Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000049 # name valid states
50 'APPEND': ('AUTH', 'SELECTED'),
51 'AUTHENTICATE': ('NONAUTH',),
52 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
53 'CHECK': ('SELECTED',),
54 'CLOSE': ('SELECTED',),
55 'COPY': ('SELECTED',),
56 'CREATE': ('AUTH', 'SELECTED'),
57 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000058 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000059 'EXAMINE': ('AUTH', 'SELECTED'),
60 'EXPUNGE': ('SELECTED',),
61 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000062 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000063 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000064 'GETQUOTA': ('AUTH', 'SELECTED'),
65 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000066 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000067 'LIST': ('AUTH', 'SELECTED'),
68 'LOGIN': ('NONAUTH',),
69 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
70 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000071 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000072 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000073 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000074 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000075 'RENAME': ('AUTH', 'SELECTED'),
76 'SEARCH': ('SELECTED',),
77 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000078 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000079 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000080 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000081 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000082 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000083 'STATUS': ('AUTH', 'SELECTED'),
84 'STORE': ('SELECTED',),
85 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000086 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000087 'UID': ('SELECTED',),
88 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
89 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000090
Tim Peters07e99cb2001-01-14 23:47:14 +000091# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000092
Christian Heimesfb5faf02008-11-05 19:39:50 +000093Continuation = re.compile(br'\+( (?P<data>.*))?')
94Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
95InternalDate = re.compile(br'.*INTERNALDATE "'
96 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
97 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
98 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
99 br'"')
100Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
101MapCRLF = re.compile(br'\r\n|\r|\n')
102Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
103Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +0000104Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000105 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000106
107
108
109class IMAP4:
110
Tim Peters07e99cb2001-01-14 23:47:14 +0000111 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000112
Tim Peters07e99cb2001-01-14 23:47:14 +0000113 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000114
Tim Peters07e99cb2001-01-14 23:47:14 +0000115 host - host's name (default: localhost);
116 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000117
Tim Peters07e99cb2001-01-14 23:47:14 +0000118 All IMAP4rev1 commands are supported by methods of the same
119 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000120
Tim Peters07e99cb2001-01-14 23:47:14 +0000121 All arguments to commands are converted to strings, except for
122 AUTHENTICATE, and the last argument to APPEND which is passed as
123 an IMAP4 literal. If necessary (the string contains any
124 non-printing characters or white-space and isn't enclosed with
125 either parentheses or double quotes) each string is quoted.
126 However, the 'password' argument to the LOGIN command is always
127 quoted. If you want to avoid having an argument string quoted
128 (eg: the 'flags' argument to STORE) then enclose the string in
129 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000130
Tim Peters07e99cb2001-01-14 23:47:14 +0000131 Each command returns a tuple: (type, [data, ...]) where 'type'
132 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000133 tagged response, or untagged results from command. Each 'data'
134 is either a string, or a tuple. If a tuple, then the first part
135 is the header of the response, and the second part contains
136 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000137
Tim Peters07e99cb2001-01-14 23:47:14 +0000138 Errors raise the exception class <instance>.error("<reason>").
139 IMAP4 server errors raise <instance>.abort("<reason>"),
140 which is a sub-class of 'error'. Mailbox status changes
141 from READ-WRITE to READ-ONLY raise the exception class
142 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000143
Tim Peters07e99cb2001-01-14 23:47:14 +0000144 "error" exceptions imply a program error.
145 "abort" exceptions imply the connection should be reset, and
146 the command re-tried.
147 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000148
Piers Lauderd80ef022005-06-01 23:50:52 +0000149 Note: to use this module, you must read the RFCs pertaining to the
150 IMAP4 protocol, as the semantics of the arguments to each IMAP4
151 command are left to the invoker, not to mention the results. Also,
152 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000154
Tim Peters07e99cb2001-01-14 23:47:14 +0000155 class error(Exception): pass # Logical errors - debug required
156 class abort(error): pass # Service errors - close and retry
157 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000158
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 self.debug = Debug
161 self.state = 'LOGOUT'
162 self.literal = None # A literal argument to a command
163 self.tagged_commands = {} # Tagged commands awaiting response
164 self.untagged_responses = {} # {typ: [data, ...], ...}
165 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000166 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000167 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000168 self._tls_established = False
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000169
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000173
Victor Stinner33e649c2011-01-05 23:01:37 +0000174 try:
175 self._connect()
176 except Exception:
177 try:
178 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200179 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000180 pass
181 raise
182
183
184 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 # Create unique tag for this session,
186 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000187
Piers Lauder2dfc1682005-07-05 04:20:07 +0000188 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000189 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000190 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000191 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000192
Tim Peters07e99cb2001-01-14 23:47:14 +0000193 # Get server welcome message,
194 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000195
Tim Peters07e99cb2001-01-14 23:47:14 +0000196 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000197 self._cmd_log_len = 10
198 self._cmd_log_idx = 0
199 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000200 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000201 self._mesg('imaplib version %s' % __version__)
202 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000203
Tim Peters07e99cb2001-01-14 23:47:14 +0000204 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000205 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000207 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 self.state = 'NONAUTH'
209 else:
210 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000211
Antoine Pitroudbe75192010-11-16 17:55:26 +0000212 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000213 if __debug__:
214 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000215 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000216
Tim Peters07e99cb2001-01-14 23:47:14 +0000217 for version in AllowedVersions:
218 if not version in self.capabilities:
219 continue
220 self.PROTOCOL_VERSION = version
221 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000222
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000224
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000225
Tim Peters07e99cb2001-01-14 23:47:14 +0000226 def __getattr__(self, attr):
227 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000228 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000229 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000230 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000231
232
233
Piers Lauder15e5d532001-07-20 10:52:06 +0000234 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000235
236
Christian Heimesfb5faf02008-11-05 19:39:50 +0000237 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000238 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000239
Piers Lauderf97b2d72002-06-05 22:31:57 +0000240 def open(self, host = '', port = IMAP4_PORT):
241 """Setup connection to remote server on "host:port"
242 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000243 This connection will be used by the routines:
244 read, readline, send, shutdown.
245 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000246 self.host = host
247 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000248 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000249 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000250
251
Piers Lauder15e5d532001-07-20 10:52:06 +0000252 def read(self, size):
253 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200254 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000255
256
257 def readline(self):
258 """Read line from remote."""
259 return self.file.readline()
260
261
262 def send(self, data):
263 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000264 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000265
Piers Lauderf2d7d152002-02-22 01:15:17 +0000266
Piers Lauder15e5d532001-07-20 10:52:06 +0000267 def shutdown(self):
268 """Close I/O established in "open"."""
269 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000270 try:
271 self.sock.shutdown(socket.SHUT_RDWR)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200272 except OSError as e:
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000273 # The server might already have closed the connection
274 if e.errno != errno.ENOTCONN:
275 raise
276 finally:
277 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000278
279
280 def socket(self):
281 """Return socket instance used to connect to IMAP4 server.
282
283 socket = <instance>.socket()
284 """
285 return self.sock
286
287
288
289 # Utility methods
290
291
Tim Peters07e99cb2001-01-14 23:47:14 +0000292 def recent(self):
293 """Return most recent 'RECENT' responses if any exist,
294 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000295
Tim Peters07e99cb2001-01-14 23:47:14 +0000296 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000297
Tim Peters07e99cb2001-01-14 23:47:14 +0000298 'data' is None if no new messages,
299 else list of RECENT responses, most recent last.
300 """
301 name = 'RECENT'
302 typ, dat = self._untagged_response('OK', [None], name)
303 if dat[-1]:
304 return typ, dat
305 typ, dat = self.noop() # Prod server for response
306 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000307
308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 def response(self, code):
310 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000311
Tim Peters07e99cb2001-01-14 23:47:14 +0000312 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000313
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 (code, [data]) = <instance>.response(code)
315 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000316 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000317
318
Guido van Rossum26367a01998-09-28 15:34:46 +0000319
Tim Peters07e99cb2001-01-14 23:47:14 +0000320 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000321
322
Tim Peters07e99cb2001-01-14 23:47:14 +0000323 def append(self, mailbox, flags, date_time, message):
324 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000325
Tim Peters07e99cb2001-01-14 23:47:14 +0000326 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000327
Tim Peters07e99cb2001-01-14 23:47:14 +0000328 All args except `message' can be None.
329 """
330 name = 'APPEND'
331 if not mailbox:
332 mailbox = 'INBOX'
333 if flags:
334 if (flags[0],flags[-1]) != ('(',')'):
335 flags = '(%s)' % flags
336 else:
337 flags = None
338 if date_time:
339 date_time = Time2Internaldate(date_time)
340 else:
341 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000342 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000344
345
Tim Peters07e99cb2001-01-14 23:47:14 +0000346 def authenticate(self, mechanism, authobject):
347 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000348
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 'mechanism' specifies which authentication mechanism is to
350 be used - it must appear in <instance>.capabilities in the
351 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000352
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000354
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000356
R David Murray774a39f2013-02-19 12:17:31 -0500357 It will be called to process server continuation responses; the
358 response argument it is passed will be a bytes. It should return bytes
359 data that will be base64 encoded and sent to the server. It should
360 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000362 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000363 # XXX: shouldn't this code be removed, not commented out?
364 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000365 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000366 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 self.literal = _Authenticator(authobject).process
368 typ, dat = self._simple_command('AUTHENTICATE', mech)
369 if typ != 'OK':
370 raise self.error(dat[-1])
371 self.state = 'AUTH'
372 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000373
374
Piers Lauderd80ef022005-06-01 23:50:52 +0000375 def capability(self):
376 """(typ, [data]) = <instance>.capability()
377 Fetch capabilities list from server."""
378
379 name = 'CAPABILITY'
380 typ, dat = self._simple_command(name)
381 return self._untagged_response(typ, dat, name)
382
383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 def check(self):
385 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000386
Tim Peters07e99cb2001-01-14 23:47:14 +0000387 (typ, [data]) = <instance>.check()
388 """
389 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000390
391
Tim Peters07e99cb2001-01-14 23:47:14 +0000392 def close(self):
393 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 Deleted messages are removed from writable mailbox.
396 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 (typ, [data]) = <instance>.close()
399 """
400 try:
401 typ, dat = self._simple_command('CLOSE')
402 finally:
403 self.state = 'AUTH'
404 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000405
406
Tim Peters07e99cb2001-01-14 23:47:14 +0000407 def copy(self, message_set, new_mailbox):
408 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000409
Tim Peters07e99cb2001-01-14 23:47:14 +0000410 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
411 """
412 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000413
414
Tim Peters07e99cb2001-01-14 23:47:14 +0000415 def create(self, mailbox):
416 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 (typ, [data]) = <instance>.create(mailbox)
419 """
420 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000421
422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 def delete(self, mailbox):
424 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000425
Tim Peters07e99cb2001-01-14 23:47:14 +0000426 (typ, [data]) = <instance>.delete(mailbox)
427 """
428 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000430 def deleteacl(self, mailbox, who):
431 """Delete the ACLs (remove any rights) set for who on mailbox.
432
433 (typ, [data]) = <instance>.deleteacl(mailbox, who)
434 """
435 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000436
Tim Peters07e99cb2001-01-14 23:47:14 +0000437 def expunge(self):
438 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000439
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Tim Peters07e99cb2001-01-14 23:47:14 +0000442 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 'data' is list of 'EXPUNGE'd message numbers in order received.
445 """
446 name = 'EXPUNGE'
447 typ, dat = self._simple_command(name)
448 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
450
Tim Peters07e99cb2001-01-14 23:47:14 +0000451 def fetch(self, message_set, message_parts):
452 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000453
Tim Peters07e99cb2001-01-14 23:47:14 +0000454 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000455
Tim Peters07e99cb2001-01-14 23:47:14 +0000456 'message_parts' should be a string of selected parts
457 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000458
Tim Peters07e99cb2001-01-14 23:47:14 +0000459 'data' are tuples of message part envelope and data.
460 """
461 name = 'FETCH'
462 typ, dat = self._simple_command(name, message_set, message_parts)
463 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000464
465
Piers Lauder15e5d532001-07-20 10:52:06 +0000466 def getacl(self, mailbox):
467 """Get the ACLs for a mailbox.
468
469 (typ, [data]) = <instance>.getacl(mailbox)
470 """
471 typ, dat = self._simple_command('GETACL', mailbox)
472 return self._untagged_response(typ, dat, 'ACL')
473
474
Piers Lauderd80ef022005-06-01 23:50:52 +0000475 def getannotation(self, mailbox, entry, attribute):
476 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
477 Retrieve ANNOTATIONs."""
478
479 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
480 return self._untagged_response(typ, dat, 'ANNOTATION')
481
482
Piers Lauder3fca2912002-06-17 07:07:20 +0000483 def getquota(self, root):
484 """Get the quota root's resource usage and limits.
485
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000486 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000487
488 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000489 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000490 typ, dat = self._simple_command('GETQUOTA', root)
491 return self._untagged_response(typ, dat, 'QUOTA')
492
493
494 def getquotaroot(self, mailbox):
495 """Get the list of quota roots for the named mailbox.
496
497 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000498 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000499 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000500 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
501 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000502 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000503
504
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 def list(self, directory='""', pattern='*'):
506 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 'data' is list of LIST responses.
511 """
512 name = 'LIST'
513 typ, dat = self._simple_command(name, directory, pattern)
514 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000515
516
Tim Peters07e99cb2001-01-14 23:47:14 +0000517 def login(self, user, password):
518 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000519
Tim Peters07e99cb2001-01-14 23:47:14 +0000520 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000521
Tim Peters07e99cb2001-01-14 23:47:14 +0000522 NB: 'password' will be quoted.
523 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000524 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
525 if typ != 'OK':
526 raise self.error(dat[-1])
527 self.state = 'AUTH'
528 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000529
530
Piers Laudere0273de2002-11-22 05:53:04 +0000531 def login_cram_md5(self, user, password):
532 """ Force use of CRAM-MD5 authentication.
533
534 (typ, [data]) = <instance>.login_cram_md5(user, password)
535 """
536 self.user, self.password = user, password
537 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
538
539
540 def _CRAM_MD5_AUTH(self, challenge):
541 """ Authobject to use with CRAM-MD5 authentication. """
542 import hmac
R David Murray774a39f2013-02-19 12:17:31 -0500543 pwd = (self.password.encode('ASCII') if isinstance(self.password, str)
544 else self.password)
545 return self.user + " " + hmac.HMAC(pwd, challenge).hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000546
547
Tim Peters07e99cb2001-01-14 23:47:14 +0000548 def logout(self):
549 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000550
Tim Peters07e99cb2001-01-14 23:47:14 +0000551 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000552
Tim Peters07e99cb2001-01-14 23:47:14 +0000553 Returns server 'BYE' response.
554 """
555 self.state = 'LOGOUT'
556 try: typ, dat = self._simple_command('LOGOUT')
557 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000558 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000559 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000560 return 'BYE', self.untagged_responses['BYE']
561 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000562
563
Tim Peters07e99cb2001-01-14 23:47:14 +0000564 def lsub(self, directory='""', pattern='*'):
565 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000566
Tim Peters07e99cb2001-01-14 23:47:14 +0000567 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000568
Tim Peters07e99cb2001-01-14 23:47:14 +0000569 'data' are tuples of message part envelope and data.
570 """
571 name = 'LSUB'
572 typ, dat = self._simple_command(name, directory, pattern)
573 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000574
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000575 def myrights(self, mailbox):
576 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
577
578 (typ, [data]) = <instance>.myrights(mailbox)
579 """
580 typ,dat = self._simple_command('MYRIGHTS', mailbox)
581 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000582
Piers Lauder15e5d532001-07-20 10:52:06 +0000583 def namespace(self):
584 """ Returns IMAP namespaces ala rfc2342
585
586 (typ, [data, ...]) = <instance>.namespace()
587 """
588 name = 'NAMESPACE'
589 typ, dat = self._simple_command(name)
590 return self._untagged_response(typ, dat, name)
591
592
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 def noop(self):
594 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000595
Piers Laudere0273de2002-11-22 05:53:04 +0000596 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000597 """
598 if __debug__:
599 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000600 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000601 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000602
603
Tim Peters07e99cb2001-01-14 23:47:14 +0000604 def partial(self, message_num, message_part, start, length):
605 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000606
Tim Peters07e99cb2001-01-14 23:47:14 +0000607 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000608
Tim Peters07e99cb2001-01-14 23:47:14 +0000609 'data' is tuple of message part envelope and data.
610 """
611 name = 'PARTIAL'
612 typ, dat = self._simple_command(name, message_num, message_part, start, length)
613 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000614
615
Piers Laudere0273de2002-11-22 05:53:04 +0000616 def proxyauth(self, user):
617 """Assume authentication as "user".
618
619 Allows an authorised administrator to proxy into any user's
620 mailbox.
621
622 (typ, [data]) = <instance>.proxyauth(user)
623 """
624
625 name = 'PROXYAUTH'
626 return self._simple_command('PROXYAUTH', user)
627
628
Tim Peters07e99cb2001-01-14 23:47:14 +0000629 def rename(self, oldmailbox, newmailbox):
630 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000631
Piers Laudere0273de2002-11-22 05:53:04 +0000632 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 """
634 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
636
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 def search(self, charset, *criteria):
638 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000639
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000640 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000641
Tim Peters07e99cb2001-01-14 23:47:14 +0000642 'data' is space separated list of matching message numbers.
643 """
644 name = 'SEARCH'
645 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000646 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000647 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000648 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000650
651
Piers Lauder14f39402005-08-31 10:46:29 +0000652 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000656
Piers Lauder14f39402005-08-31 10:46:29 +0000657 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000658
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000660
661 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
662 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000663 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 self.untagged_responses = {} # Flush old responses.
665 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000666 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 name = 'EXAMINE'
668 else:
669 name = 'SELECT'
670 typ, dat = self._simple_command(name, mailbox)
671 if typ != 'OK':
672 self.state = 'AUTH' # Might have been 'SELECTED'
673 return typ, dat
674 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000675 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 and not readonly:
677 if __debug__:
678 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000679 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 raise self.readonly('%s is not writable' % mailbox)
681 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000682
683
Piers Lauder15e5d532001-07-20 10:52:06 +0000684 def setacl(self, mailbox, who, what):
685 """Set a mailbox acl.
686
Piers Lauderf167dc32004-03-25 00:12:21 +0000687 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000688 """
689 return self._simple_command('SETACL', mailbox, who, what)
690
691
Piers Lauderd80ef022005-06-01 23:50:52 +0000692 def setannotation(self, *args):
693 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
694 Set ANNOTATIONs."""
695
696 typ, dat = self._simple_command('SETANNOTATION', *args)
697 return self._untagged_response(typ, dat, 'ANNOTATION')
698
699
Piers Lauder3fca2912002-06-17 07:07:20 +0000700 def setquota(self, root, limits):
701 """Set the quota root's resource limits.
702
703 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000704 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000705 typ, dat = self._simple_command('SETQUOTA', root, limits)
706 return self._untagged_response(typ, dat, 'QUOTA')
707
708
Piers Lauder15e5d532001-07-20 10:52:06 +0000709 def sort(self, sort_criteria, charset, *search_criteria):
710 """IMAP4rev1 extension SORT command.
711
712 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
713 """
714 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000715 #if not name in self.capabilities: # Let the server decide!
716 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000717 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000718 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000719 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000720 return self._untagged_response(typ, dat, name)
721
722
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000723 def starttls(self, ssl_context=None):
724 name = 'STARTTLS'
725 if not HAVE_SSL:
726 raise self.error('SSL support missing')
727 if self._tls_established:
728 raise self.abort('TLS session already established')
729 if name not in self.capabilities:
730 raise self.abort('TLS not supported by server')
731 # Generate a default SSL context if none was passed.
732 if ssl_context is None:
733 ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
734 # SSLv2 considered harmful.
735 ssl_context.options |= ssl.OP_NO_SSLv2
736 typ, dat = self._simple_command(name)
737 if typ == 'OK':
738 self.sock = ssl_context.wrap_socket(self.sock)
739 self.file = self.sock.makefile('rb')
740 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000741 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000742 else:
743 raise self.error("Couldn't establish TLS session")
744 return self._untagged_response(typ, dat, name)
745
746
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 def status(self, mailbox, names):
748 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000749
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 (typ, [data]) = <instance>.status(mailbox, names)
751 """
752 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000753 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000754 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 typ, dat = self._simple_command(name, mailbox, names)
756 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000757
758
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 def store(self, message_set, command, flags):
760 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000761
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 (typ, [data]) = <instance>.store(message_set, command, flags)
763 """
764 if (flags[0],flags[-1]) != ('(',')'):
765 flags = '(%s)' % flags # Avoid quoting the flags
766 typ, dat = self._simple_command('STORE', message_set, command, flags)
767 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000768
769
Tim Peters07e99cb2001-01-14 23:47:14 +0000770 def subscribe(self, mailbox):
771 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000772
Tim Peters07e99cb2001-01-14 23:47:14 +0000773 (typ, [data]) = <instance>.subscribe(mailbox)
774 """
775 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000776
777
Martin v. Löwisd8921372003-11-10 06:44:44 +0000778 def thread(self, threading_algorithm, charset, *search_criteria):
779 """IMAPrev1 extension THREAD command.
780
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000781 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000782 """
783 name = 'THREAD'
784 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
785 return self._untagged_response(typ, dat, name)
786
787
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 def uid(self, command, *args):
789 """Execute "command arg ..." with messages identified by UID,
790 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000791
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 Returns response appropriate to 'command'.
795 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000796 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000797 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 raise self.error("Unknown IMAP4 UID command: %s" % command)
799 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000800 raise self.error("command %s illegal in state %s, "
801 "only allowed in states %s" %
802 (command, self.state,
803 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000805 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000806 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000807 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 else:
809 name = 'FETCH'
810 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000811
812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 def unsubscribe(self, mailbox):
814 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 (typ, [data]) = <instance>.unsubscribe(mailbox)
817 """
818 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
820
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 def xatom(self, name, *args):
822 """Allow simple extension commands
823 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000824
Piers Lauder15e5d532001-07-20 10:52:06 +0000825 Assumes command is legal in current state.
826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000828
829 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000831 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000832 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000833 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000834 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000835 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000836 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000837
838
839
Tim Peters07e99cb2001-01-14 23:47:14 +0000840 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000844 if dat is None:
845 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 ur = self.untagged_responses
847 if __debug__:
848 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000849 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000851 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 ur[typ].append(dat)
853 else:
854 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000855
856
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 def _check_bye(self):
858 bye = self.untagged_responses.get('BYE')
859 if bye:
Antoine Pitroudac47912010-11-10 00:18:40 +0000860 raise self.abort(bye[-1].decode('ascii', 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000861
862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000864
Tim Peters07e99cb2001-01-14 23:47:14 +0000865 if self.state not in Commands[name]:
866 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000867 raise self.error("command %s illegal in state %s, "
868 "only allowed in states %s" %
869 (name, self.state,
870 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000873 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000875
Raymond Hettinger54f02222002-06-01 14:18:47 +0000876 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 and not self.is_readonly:
878 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000881 name = bytes(name, 'ASCII')
882 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 for arg in args:
884 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000885 if isinstance(arg, str):
886 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000887 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 literal = self.literal
890 if literal is not None:
891 self.literal = None
892 if type(literal) is type(self._command):
893 literator = literal
894 else:
895 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000896 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000897
Tim Peters07e99cb2001-01-14 23:47:14 +0000898 if __debug__:
899 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000900 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000902 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000903
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000905 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200906 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000907 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000908
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 if literal is None:
910 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000911
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 while 1:
913 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000914
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 while self._get_response():
916 if self.tagged_commands[tag]: # BAD/NO?
917 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 if literator:
922 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 if __debug__:
925 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000926 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000929 self.send(literal)
930 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200931 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 if not literator:
935 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000938
939
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000941 # BYE is expected after LOGOUT
942 if name != 'LOGOUT':
943 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 try:
945 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000946 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000948 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +0000950 if name != 'LOGOUT':
951 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 if typ == 'BAD':
953 raise self.error('%s command error: %s %s' % (name, typ, data))
954 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000955
956
Antoine Pitroudbe75192010-11-16 17:55:26 +0000957 def _get_capabilities(self):
958 typ, dat = self.capability()
959 if dat == [None]:
960 raise self.error('no CAPABILITY response from server')
961 dat = str(dat[-1], "ASCII")
962 dat = dat.upper()
963 self.capabilities = tuple(dat.split())
964
965
Tim Peters07e99cb2001-01-14 23:47:14 +0000966 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000967
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 # Read response and store.
969 #
970 # Returns None for continuation responses,
971 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 if self._match(self.tagre, resp):
978 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000979 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000983 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 dat = self.mo.group('data')
985 self.tagged_commands[tag] = (typ, [dat])
986 else:
987 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 if not self._match(Untagged_response, resp):
992 if self._match(Untagged_status, resp):
993 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 if self.mo is None:
996 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 if self._match(Continuation, resp):
999 self.continuation_response = self.mo.group('data')
1000 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001001
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001003
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001005 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001007 if dat is None: dat = b'' # Null untagged response
1008 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001011
Tim Peters07e99cb2001-01-14 23:47:14 +00001012 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001013
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001015
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001016 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001017 if __debug__:
1018 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001019 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001020 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001031
Tim Peters07e99cb2001-01-14 23:47:14 +00001032 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001035 typ = self.mo.group('type')
1036 typ = str(typ, "ASCII")
1037 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001038
Tim Peters07e99cb2001-01-14 23:47:14 +00001039 if __debug__:
1040 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001041 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
1045
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 while 1:
1049 result = self.tagged_commands[tag]
1050 if result is not None:
1051 del self.tagged_commands[tag]
1052 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001053
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 # Some have reported "unexpected response" exceptions.
1055 # Note that ignoring them here causes loops.
1056 # Instead, send me details of the unexpected response and
1057 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001058
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 try:
1060 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001061 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001062 if __debug__:
1063 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001064 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001066
1067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001069
Piers Lauder15e5d532001-07-20 10:52:06 +00001070 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001071 if not line:
1072 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001075 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001076 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001077
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 line = line[:-2]
1079 if __debug__:
1080 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001081 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001082 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001083 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001085
1086
Tim Peters07e99cb2001-01-14 23:47:14 +00001087 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001088
Tim Peters07e99cb2001-01-14 23:47:14 +00001089 # Run compiled regular expression match method on 's'.
1090 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 self.mo = cre.match(s)
1093 if __debug__:
1094 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001095 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001097
1098
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001100
Christian Heimesfb5faf02008-11-05 19:39:50 +00001101 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001102 self.tagnum = self.tagnum + 1
1103 self.tagged_commands[tag] = None
1104 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001105
1106
Tim Peters07e99cb2001-01-14 23:47:14 +00001107 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001108
Antoine Pitroub1436f12010-11-09 22:55:55 +00001109 arg = arg.replace('\\', '\\\\')
1110 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001111
Antoine Pitroub1436f12010-11-09 22:55:55 +00001112 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001113
1114
Tim Peters07e99cb2001-01-14 23:47:14 +00001115 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001116
Guido van Rossum68468eb2003-02-27 20:14:51 +00001117 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001118
1119
Tim Peters07e99cb2001-01-14 23:47:14 +00001120 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001121 if typ == 'NO':
1122 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001123 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001124 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001125 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001126 if __debug__:
1127 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001128 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001129 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001130
1131
Piers Lauderf2d7d152002-02-22 01:15:17 +00001132 if __debug__:
1133
1134 def _mesg(self, s, secs=None):
1135 if secs is None:
1136 secs = time.time()
1137 tm = time.strftime('%M:%S', time.localtime(secs))
1138 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1139 sys.stderr.flush()
1140
1141 def _dump_ur(self, dict):
1142 # Dump untagged responses (in `dict').
1143 l = dict.items()
1144 if not l: return
1145 t = '\n\t\t'
1146 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1147 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1148
1149 def _log(self, line):
1150 # Keep log of last `_cmd_log_len' interactions for debugging.
1151 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1152 self._cmd_log_idx += 1
1153 if self._cmd_log_idx >= self._cmd_log_len:
1154 self._cmd_log_idx = 0
1155
1156 def print_log(self):
1157 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1158 i, n = self._cmd_log_idx, self._cmd_log_len
1159 while n:
1160 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001161 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001162 except:
1163 pass
1164 i += 1
1165 if i >= self._cmd_log_len:
1166 i = 0
1167 n -= 1
1168
1169
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001170if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001171
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001172 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001173
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001174 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001175
Antoine Pitrou08728162011-05-06 18:49:52 +02001176 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001177
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001178 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001179 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001180 keyfile - PEM formatted file that contains your private key (default: None);
1181 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001182 ssl_context - a SSLContext object that contains your certificate chain
1183 and private key (default: None)
1184 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001185 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001186
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001187 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001188 """
Piers Laudera4f83132002-03-08 01:53:24 +00001189
1190
Antoine Pitrou08728162011-05-06 18:49:52 +02001191 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None):
1192 if ssl_context is not None and keyfile is not None:
1193 raise ValueError("ssl_context and keyfile arguments are mutually "
1194 "exclusive")
1195 if ssl_context is not None and certfile is not None:
1196 raise ValueError("ssl_context and certfile arguments are mutually "
1197 "exclusive")
1198
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001199 self.keyfile = keyfile
1200 self.certfile = certfile
Antoine Pitrou08728162011-05-06 18:49:52 +02001201 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001202 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001203
Christian Heimesfb5faf02008-11-05 19:39:50 +00001204 def _create_socket(self):
1205 sock = IMAP4._create_socket(self)
Antoine Pitrou08728162011-05-06 18:49:52 +02001206 if self.ssl_context:
1207 return self.ssl_context.wrap_socket(sock)
1208 else:
1209 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001210
Christian Heimesfb5faf02008-11-05 19:39:50 +00001211 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001212 """Setup connection to remote server on "host:port".
1213 (default: localhost:standard IMAP4 SSL port).
1214 This connection will be used by the routines:
1215 read, readline, send, shutdown.
1216 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001217 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001218
1219 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001220
1221
Piers Laudere0273de2002-11-22 05:53:04 +00001222class IMAP4_stream(IMAP4):
1223
1224 """IMAP4 client class over a stream
1225
1226 Instantiate with: IMAP4_stream(command)
1227
Christian Heimesfb5faf02008-11-05 19:39:50 +00001228 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001229
1230 for more documentation see the docstring of the parent class IMAP4.
1231 """
1232
1233
1234 def __init__(self, command):
1235 self.command = command
1236 IMAP4.__init__(self)
1237
1238
1239 def open(self, host = None, port = None):
1240 """Setup a stream connection.
1241 This connection will be used by the routines:
1242 read, readline, send, shutdown.
1243 """
1244 self.host = None # For compatibility with parent class
1245 self.port = None
1246 self.sock = None
1247 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001248 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001249 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001250 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1251 shell=True, close_fds=True)
1252 self.writefile = self.process.stdin
1253 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001254
1255 def read(self, size):
1256 """Read 'size' bytes from remote."""
1257 return self.readfile.read(size)
1258
1259
1260 def readline(self):
1261 """Read line from remote."""
1262 return self.readfile.readline()
1263
1264
1265 def send(self, data):
1266 """Send data to remote."""
1267 self.writefile.write(data)
1268 self.writefile.flush()
1269
1270
1271 def shutdown(self):
1272 """Close I/O established in "open"."""
1273 self.readfile.close()
1274 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001275 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001276
1277
1278
Guido van Rossumeda960a1998-06-18 14:24:28 +00001279class _Authenticator:
1280
Tim Peters07e99cb2001-01-14 23:47:14 +00001281 """Private class to provide en/decoding
1282 for base64-based authentication conversation.
1283 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001284
Tim Peters07e99cb2001-01-14 23:47:14 +00001285 def __init__(self, mechinst):
1286 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001287
Tim Peters07e99cb2001-01-14 23:47:14 +00001288 def process(self, data):
1289 ret = self.mech(self.decode(data))
1290 if ret is None:
1291 return '*' # Abort conversation
1292 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001293
Tim Peters07e99cb2001-01-14 23:47:14 +00001294 def encode(self, inp):
1295 #
1296 # Invoke binascii.b2a_base64 iteratively with
1297 # short even length buffers, strip the trailing
1298 # line feed from the result and append. "Even"
1299 # means a number that factors to both 6 and 8,
1300 # so when it gets to the end of the 8-bit input
1301 # there's no partial 6-bit output.
1302 #
R David Murray774a39f2013-02-19 12:17:31 -05001303 oup = b''
1304 if isinstance(inp, str):
1305 inp = inp.encode('ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001306 while inp:
1307 if len(inp) > 48:
1308 t = inp[:48]
1309 inp = inp[48:]
1310 else:
1311 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001312 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001313 e = binascii.b2a_base64(t)
1314 if e:
1315 oup = oup + e[:-1]
1316 return oup
1317
1318 def decode(self, inp):
1319 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001320 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001321 return binascii.a2b_base64(inp)
1322
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001323Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1324Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001325
1326def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001327 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001328
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001329 Return corresponding local time. The return value is a
1330 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001331 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001332
Tim Peters07e99cb2001-01-14 23:47:14 +00001333 mo = InternalDate.match(resp)
1334 if not mo:
1335 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001336
Tim Peters07e99cb2001-01-14 23:47:14 +00001337 mon = Mon2num[mo.group('mon')]
1338 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001339
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001340 day = int(mo.group('day'))
1341 year = int(mo.group('year'))
1342 hour = int(mo.group('hour'))
1343 min = int(mo.group('min'))
1344 sec = int(mo.group('sec'))
1345 zoneh = int(mo.group('zoneh'))
1346 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001347
Tim Peters07e99cb2001-01-14 23:47:14 +00001348 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001349
Tim Peters07e99cb2001-01-14 23:47:14 +00001350 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001351 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001352 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001353
Tim Peters07e99cb2001-01-14 23:47:14 +00001354 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001355 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001356
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001357 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
1359
1360
1361def Int2AP(num):
1362
Tim Peters07e99cb2001-01-14 23:47:14 +00001363 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001364
Christian Heimesfb5faf02008-11-05 19:39:50 +00001365 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001366 num = int(abs(num))
1367 while num:
1368 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001369 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001370 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001371
1372
1373
1374def ParseFlags(resp):
1375
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001377
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 mo = Flags.match(resp)
1379 if not mo:
1380 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001381
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001382 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001383
1384
1385def Time2Internaldate(date_time):
1386
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001387 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001388
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001389 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001390 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001391 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001392 representing local time, an instance of time.struct_time (as
1393 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001394 double-quoted string. In the last case, it is assumed to already
1395 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 """
Fred Drakedb519202002-01-05 17:17:09 +00001397 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001398 dt = datetime.fromtimestamp(date_time,
1399 timezone.utc).astimezone()
1400 elif isinstance(date_time, tuple):
1401 try:
1402 gmtoff = date_time.tm_gmtoff
1403 except AttributeError:
1404 if time.daylight:
1405 dst = date_time[8]
1406 if dst == -1:
1407 dst = time.localtime(time.mktime(date_time))[8]
1408 gmtoff = -(time.timezone, time.altzone)[dst]
1409 else:
1410 gmtoff = -time.timezone
1411 delta = timedelta(seconds=gmtoff)
1412 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1413 elif isinstance(date_time, datetime):
1414 if date_time.tzinfo is None:
1415 raise ValueError("date_time must be aware")
1416 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001417 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001418 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001419 else:
1420 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001421 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1422 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
1424
1425
Guido van Rossum8c062211999-12-13 23:27:45 +00001426if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001427
Piers Laudere0273de2002-11-22 05:53:04 +00001428 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1429 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1430 # to test the IMAP4_stream class
1431
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001432 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001433
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001435 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001436 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001437 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001438
Piers Laudere0273de2002-11-22 05:53:04 +00001439 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001440 for opt,val in optlist:
1441 if opt == '-d':
1442 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001443 elif opt == '-s':
1444 stream_command = val
1445 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001446
Tim Peters07e99cb2001-01-14 23:47:14 +00001447 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001448
Tim Peters07e99cb2001-01-14 23:47:14 +00001449 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001450
Tim Peters07e99cb2001-01-14 23:47:14 +00001451 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001452 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001453
Piers Lauder47404ff2003-04-29 23:40:59 +00001454 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 +00001455 test_seq1 = (
1456 ('login', (USER, PASSWD)),
1457 ('create', ('/tmp/xxx 1',)),
1458 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1459 ('CREATE', ('/tmp/yyz 2',)),
1460 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1461 ('list', ('/tmp', 'yy*')),
1462 ('select', ('/tmp/yyz 2',)),
1463 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001464 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001465 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001466 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001467 ('expunge', ()),
1468 ('recent', ()),
1469 ('close', ()),
1470 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001471
Tim Peters07e99cb2001-01-14 23:47:14 +00001472 test_seq2 = (
1473 ('select', ()),
1474 ('response',('UIDVALIDITY',)),
1475 ('uid', ('SEARCH', 'ALL')),
1476 ('response', ('EXISTS',)),
1477 ('append', (None, None, None, test_mesg)),
1478 ('recent', ()),
1479 ('logout', ()),
1480 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001481
Tim Peters07e99cb2001-01-14 23:47:14 +00001482 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001483 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001484 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001485 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001486 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001487 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001488
Tim Peters07e99cb2001-01-14 23:47:14 +00001489 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001490 if stream_command:
1491 M = IMAP4_stream(stream_command)
1492 else:
1493 M = IMAP4(host)
1494 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001495 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001496 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001497 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001498
Tim Peters07e99cb2001-01-14 23:47:14 +00001499 for cmd,args in test_seq1:
1500 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001501
Tim Peters07e99cb2001-01-14 23:47:14 +00001502 for ml in run('list', ('/tmp/', 'yy%')):
1503 mo = re.match(r'.*"([^"]+)"$', ml)
1504 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001505 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001506 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001507
Tim Peters07e99cb2001-01-14 23:47:14 +00001508 for cmd,args in test_seq2:
1509 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001510
Tim Peters07e99cb2001-01-14 23:47:14 +00001511 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1512 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001513
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001514 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001515 if not uid: continue
1516 run('uid', ('FETCH', '%s' % uid[-1],
1517 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001518
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001519 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001520
Tim Peters07e99cb2001-01-14 23:47:14 +00001521 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001522 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001523
Tim Peters07e99cb2001-01-14 23:47:14 +00001524 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001525 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001526If you would like to see debugging output,
1527try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001528''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001529
Tim Peters07e99cb2001-01-14 23:47:14 +00001530 raise