blob: ade2f9c2aa0573e0de09993d65d838e9d5210790 [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
Georg Brandlca580f42013-10-27 06:52:14 +010046# Maximal line length when calling readline(). This is to prevent
47# reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1)
48# don't specify a line length. RFC 2683 however suggests limiting client
49# command lines to 1000 octets and server command lines to 8000 octets.
50# We have selected 10000 for some extra margin and since that is supposedly
51# also what UW and Panda IMAP does.
52_MAXLINE = 10000
53
54
Tim Peters07e99cb2001-01-14 23:47:14 +000055# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000056
57Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000058 # name valid states
59 'APPEND': ('AUTH', 'SELECTED'),
60 'AUTHENTICATE': ('NONAUTH',),
61 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
62 'CHECK': ('SELECTED',),
63 'CLOSE': ('SELECTED',),
64 'COPY': ('SELECTED',),
65 'CREATE': ('AUTH', 'SELECTED'),
66 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000067 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000068 'EXAMINE': ('AUTH', 'SELECTED'),
69 'EXPUNGE': ('SELECTED',),
70 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000071 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000072 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000073 'GETQUOTA': ('AUTH', 'SELECTED'),
74 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000075 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000076 'LIST': ('AUTH', 'SELECTED'),
77 'LOGIN': ('NONAUTH',),
78 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
79 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000080 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000081 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000082 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000083 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000084 'RENAME': ('AUTH', 'SELECTED'),
85 'SEARCH': ('SELECTED',),
86 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000087 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000088 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000089 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000090 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000091 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000092 'STATUS': ('AUTH', 'SELECTED'),
93 'STORE': ('SELECTED',),
94 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000095 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000096 'UID': ('SELECTED',),
97 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
98 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000099
Tim Peters07e99cb2001-01-14 23:47:14 +0000100# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000101
Christian Heimesfb5faf02008-11-05 19:39:50 +0000102Continuation = re.compile(br'\+( (?P<data>.*))?')
103Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
104InternalDate = re.compile(br'.*INTERNALDATE "'
105 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
106 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
107 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
108 br'"')
109Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
110MapCRLF = re.compile(br'\r\n|\r|\n')
111Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
112Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +0000113Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000114 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000115
116
117
118class IMAP4:
119
Tim Peters07e99cb2001-01-14 23:47:14 +0000120 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000121
Tim Peters07e99cb2001-01-14 23:47:14 +0000122 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000123
Tim Peters07e99cb2001-01-14 23:47:14 +0000124 host - host's name (default: localhost);
125 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000126
Tim Peters07e99cb2001-01-14 23:47:14 +0000127 All IMAP4rev1 commands are supported by methods of the same
128 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000129
Tim Peters07e99cb2001-01-14 23:47:14 +0000130 All arguments to commands are converted to strings, except for
131 AUTHENTICATE, and the last argument to APPEND which is passed as
132 an IMAP4 literal. If necessary (the string contains any
133 non-printing characters or white-space and isn't enclosed with
134 either parentheses or double quotes) each string is quoted.
135 However, the 'password' argument to the LOGIN command is always
136 quoted. If you want to avoid having an argument string quoted
137 (eg: the 'flags' argument to STORE) then enclose the string in
138 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000139
Tim Peters07e99cb2001-01-14 23:47:14 +0000140 Each command returns a tuple: (type, [data, ...]) where 'type'
141 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000142 tagged response, or untagged results from command. Each 'data'
143 is either a string, or a tuple. If a tuple, then the first part
144 is the header of the response, and the second part contains
145 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000146
Tim Peters07e99cb2001-01-14 23:47:14 +0000147 Errors raise the exception class <instance>.error("<reason>").
148 IMAP4 server errors raise <instance>.abort("<reason>"),
149 which is a sub-class of 'error'. Mailbox status changes
150 from READ-WRITE to READ-ONLY raise the exception class
151 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000152
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 "error" exceptions imply a program error.
154 "abort" exceptions imply the connection should be reset, and
155 the command re-tried.
156 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000157
Piers Lauderd80ef022005-06-01 23:50:52 +0000158 Note: to use this module, you must read the RFCs pertaining to the
159 IMAP4 protocol, as the semantics of the arguments to each IMAP4
160 command are left to the invoker, not to mention the results. Also,
161 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000163
Tim Peters07e99cb2001-01-14 23:47:14 +0000164 class error(Exception): pass # Logical errors - debug required
165 class abort(error): pass # Service errors - close and retry
166 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000167
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 self.debug = Debug
170 self.state = 'LOGOUT'
171 self.literal = None # A literal argument to a command
172 self.tagged_commands = {} # Tagged commands awaiting response
173 self.untagged_responses = {} # {typ: [data, ...], ...}
174 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000175 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000176 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000177 self._tls_established = False
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000178
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000180
Tim Peters07e99cb2001-01-14 23:47:14 +0000181 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000182
Victor Stinner33e649c2011-01-05 23:01:37 +0000183 try:
184 self._connect()
185 except Exception:
186 try:
187 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200188 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000189 pass
190 raise
191
192
193 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000194 # Create unique tag for this session,
195 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000196
Piers Lauder2dfc1682005-07-05 04:20:07 +0000197 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000198 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000199 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000200 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000201
Tim Peters07e99cb2001-01-14 23:47:14 +0000202 # Get server welcome message,
203 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000204
Tim Peters07e99cb2001-01-14 23:47:14 +0000205 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000206 self._cmd_log_len = 10
207 self._cmd_log_idx = 0
208 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000209 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000210 self._mesg('imaplib version %s' % __version__)
211 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000212
Tim Peters07e99cb2001-01-14 23:47:14 +0000213 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000214 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000216 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000217 self.state = 'NONAUTH'
218 else:
219 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000220
Antoine Pitroudbe75192010-11-16 17:55:26 +0000221 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000222 if __debug__:
223 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000224 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000225
Tim Peters07e99cb2001-01-14 23:47:14 +0000226 for version in AllowedVersions:
227 if not version in self.capabilities:
228 continue
229 self.PROTOCOL_VERSION = version
230 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000231
Tim Peters07e99cb2001-01-14 23:47:14 +0000232 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000233
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000234
Tim Peters07e99cb2001-01-14 23:47:14 +0000235 def __getattr__(self, attr):
236 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000237 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000238 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000239 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000240
241
242
Piers Lauder15e5d532001-07-20 10:52:06 +0000243 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000244
245
Christian Heimesfb5faf02008-11-05 19:39:50 +0000246 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000247 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000248
Piers Lauderf97b2d72002-06-05 22:31:57 +0000249 def open(self, host = '', port = IMAP4_PORT):
250 """Setup connection to remote server on "host:port"
251 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000252 This connection will be used by the routines:
253 read, readline, send, shutdown.
254 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000255 self.host = host
256 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000257 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000258 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000259
260
Piers Lauder15e5d532001-07-20 10:52:06 +0000261 def read(self, size):
262 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200263 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000264
265
266 def readline(self):
267 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100268 line = self.file.readline(_MAXLINE + 1)
269 if len(line) > _MAXLINE:
270 raise self.error("got more than %d bytes" % _MAXLINE)
271 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000272
273
274 def send(self, data):
275 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000276 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000277
Piers Lauderf2d7d152002-02-22 01:15:17 +0000278
Piers Lauder15e5d532001-07-20 10:52:06 +0000279 def shutdown(self):
280 """Close I/O established in "open"."""
281 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000282 try:
283 self.sock.shutdown(socket.SHUT_RDWR)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200284 except OSError as e:
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000285 # The server might already have closed the connection
286 if e.errno != errno.ENOTCONN:
287 raise
288 finally:
289 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000290
291
292 def socket(self):
293 """Return socket instance used to connect to IMAP4 server.
294
295 socket = <instance>.socket()
296 """
297 return self.sock
298
299
300
301 # Utility methods
302
303
Tim Peters07e99cb2001-01-14 23:47:14 +0000304 def recent(self):
305 """Return most recent 'RECENT' responses if any exist,
306 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000307
Tim Peters07e99cb2001-01-14 23:47:14 +0000308 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000309
Tim Peters07e99cb2001-01-14 23:47:14 +0000310 'data' is None if no new messages,
311 else list of RECENT responses, most recent last.
312 """
313 name = 'RECENT'
314 typ, dat = self._untagged_response('OK', [None], name)
315 if dat[-1]:
316 return typ, dat
317 typ, dat = self.noop() # Prod server for response
318 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000319
320
Tim Peters07e99cb2001-01-14 23:47:14 +0000321 def response(self, code):
322 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000323
Tim Peters07e99cb2001-01-14 23:47:14 +0000324 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000325
Tim Peters07e99cb2001-01-14 23:47:14 +0000326 (code, [data]) = <instance>.response(code)
327 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000328 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000329
330
Guido van Rossum26367a01998-09-28 15:34:46 +0000331
Tim Peters07e99cb2001-01-14 23:47:14 +0000332 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000333
334
Tim Peters07e99cb2001-01-14 23:47:14 +0000335 def append(self, mailbox, flags, date_time, message):
336 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000337
Tim Peters07e99cb2001-01-14 23:47:14 +0000338 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000339
Tim Peters07e99cb2001-01-14 23:47:14 +0000340 All args except `message' can be None.
341 """
342 name = 'APPEND'
343 if not mailbox:
344 mailbox = 'INBOX'
345 if flags:
346 if (flags[0],flags[-1]) != ('(',')'):
347 flags = '(%s)' % flags
348 else:
349 flags = None
350 if date_time:
351 date_time = Time2Internaldate(date_time)
352 else:
353 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000354 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000356
357
Tim Peters07e99cb2001-01-14 23:47:14 +0000358 def authenticate(self, mechanism, authobject):
359 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000360
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 'mechanism' specifies which authentication mechanism is to
362 be used - it must appear in <instance>.capabilities in the
363 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000364
Tim Peters07e99cb2001-01-14 23:47:14 +0000365 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000368
R David Murray774a39f2013-02-19 12:17:31 -0500369 It will be called to process server continuation responses; the
370 response argument it is passed will be a bytes. It should return bytes
371 data that will be base64 encoded and sent to the server. It should
372 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000374 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000375 # XXX: shouldn't this code be removed, not commented out?
376 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000377 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000378 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000379 self.literal = _Authenticator(authobject).process
380 typ, dat = self._simple_command('AUTHENTICATE', mech)
381 if typ != 'OK':
382 raise self.error(dat[-1])
383 self.state = 'AUTH'
384 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
386
Piers Lauderd80ef022005-06-01 23:50:52 +0000387 def capability(self):
388 """(typ, [data]) = <instance>.capability()
389 Fetch capabilities list from server."""
390
391 name = 'CAPABILITY'
392 typ, dat = self._simple_command(name)
393 return self._untagged_response(typ, dat, name)
394
395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 def check(self):
397 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 (typ, [data]) = <instance>.check()
400 """
401 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000402
403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 def close(self):
405 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000406
Tim Peters07e99cb2001-01-14 23:47:14 +0000407 Deleted messages are removed from writable mailbox.
408 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000409
Tim Peters07e99cb2001-01-14 23:47:14 +0000410 (typ, [data]) = <instance>.close()
411 """
412 try:
413 typ, dat = self._simple_command('CLOSE')
414 finally:
415 self.state = 'AUTH'
416 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 def copy(self, message_set, new_mailbox):
420 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000421
Tim Peters07e99cb2001-01-14 23:47:14 +0000422 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
423 """
424 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000425
426
Tim Peters07e99cb2001-01-14 23:47:14 +0000427 def create(self, mailbox):
428 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 (typ, [data]) = <instance>.create(mailbox)
431 """
432 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000433
434
Tim Peters07e99cb2001-01-14 23:47:14 +0000435 def delete(self, mailbox):
436 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000437
Tim Peters07e99cb2001-01-14 23:47:14 +0000438 (typ, [data]) = <instance>.delete(mailbox)
439 """
440 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000442 def deleteacl(self, mailbox, who):
443 """Delete the ACLs (remove any rights) set for who on mailbox.
444
445 (typ, [data]) = <instance>.deleteacl(mailbox, who)
446 """
447 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000448
Tim Peters07e99cb2001-01-14 23:47:14 +0000449 def expunge(self):
450 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000453
Tim Peters07e99cb2001-01-14 23:47:14 +0000454 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000455
Tim Peters07e99cb2001-01-14 23:47:14 +0000456 'data' is list of 'EXPUNGE'd message numbers in order received.
457 """
458 name = 'EXPUNGE'
459 typ, dat = self._simple_command(name)
460 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 def fetch(self, message_set, message_parts):
464 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
Tim Peters07e99cb2001-01-14 23:47:14 +0000466 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000467
Tim Peters07e99cb2001-01-14 23:47:14 +0000468 'message_parts' should be a string of selected parts
469 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000470
Tim Peters07e99cb2001-01-14 23:47:14 +0000471 'data' are tuples of message part envelope and data.
472 """
473 name = 'FETCH'
474 typ, dat = self._simple_command(name, message_set, message_parts)
475 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000476
477
Piers Lauder15e5d532001-07-20 10:52:06 +0000478 def getacl(self, mailbox):
479 """Get the ACLs for a mailbox.
480
481 (typ, [data]) = <instance>.getacl(mailbox)
482 """
483 typ, dat = self._simple_command('GETACL', mailbox)
484 return self._untagged_response(typ, dat, 'ACL')
485
486
Piers Lauderd80ef022005-06-01 23:50:52 +0000487 def getannotation(self, mailbox, entry, attribute):
488 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
489 Retrieve ANNOTATIONs."""
490
491 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
492 return self._untagged_response(typ, dat, 'ANNOTATION')
493
494
Piers Lauder3fca2912002-06-17 07:07:20 +0000495 def getquota(self, root):
496 """Get the quota root's resource usage and limits.
497
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000498 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000499
500 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000501 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000502 typ, dat = self._simple_command('GETQUOTA', root)
503 return self._untagged_response(typ, dat, 'QUOTA')
504
505
506 def getquotaroot(self, mailbox):
507 """Get the list of quota roots for the named mailbox.
508
509 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000510 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000511 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000512 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
513 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000514 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000515
516
Tim Peters07e99cb2001-01-14 23:47:14 +0000517 def list(self, directory='""', pattern='*'):
518 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000519
Tim Peters07e99cb2001-01-14 23:47:14 +0000520 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000521
Tim Peters07e99cb2001-01-14 23:47:14 +0000522 'data' is list of LIST responses.
523 """
524 name = 'LIST'
525 typ, dat = self._simple_command(name, directory, pattern)
526 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000527
528
Tim Peters07e99cb2001-01-14 23:47:14 +0000529 def login(self, user, password):
530 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000531
Tim Peters07e99cb2001-01-14 23:47:14 +0000532 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000533
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 NB: 'password' will be quoted.
535 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
537 if typ != 'OK':
538 raise self.error(dat[-1])
539 self.state = 'AUTH'
540 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000541
542
Piers Laudere0273de2002-11-22 05:53:04 +0000543 def login_cram_md5(self, user, password):
544 """ Force use of CRAM-MD5 authentication.
545
546 (typ, [data]) = <instance>.login_cram_md5(user, password)
547 """
548 self.user, self.password = user, password
549 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
550
551
552 def _CRAM_MD5_AUTH(self, challenge):
553 """ Authobject to use with CRAM-MD5 authentication. """
554 import hmac
R David Murray774a39f2013-02-19 12:17:31 -0500555 pwd = (self.password.encode('ASCII') if isinstance(self.password, str)
556 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100557 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000558
559
Tim Peters07e99cb2001-01-14 23:47:14 +0000560 def logout(self):
561 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000562
Tim Peters07e99cb2001-01-14 23:47:14 +0000563 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000564
Tim Peters07e99cb2001-01-14 23:47:14 +0000565 Returns server 'BYE' response.
566 """
567 self.state = 'LOGOUT'
568 try: typ, dat = self._simple_command('LOGOUT')
569 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000570 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000571 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000572 return 'BYE', self.untagged_responses['BYE']
573 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000574
575
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 def lsub(self, directory='""', pattern='*'):
577 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000578
Tim Peters07e99cb2001-01-14 23:47:14 +0000579 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000580
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 'data' are tuples of message part envelope and data.
582 """
583 name = 'LSUB'
584 typ, dat = self._simple_command(name, directory, pattern)
585 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000586
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000587 def myrights(self, mailbox):
588 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
589
590 (typ, [data]) = <instance>.myrights(mailbox)
591 """
592 typ,dat = self._simple_command('MYRIGHTS', mailbox)
593 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000594
Piers Lauder15e5d532001-07-20 10:52:06 +0000595 def namespace(self):
596 """ Returns IMAP namespaces ala rfc2342
597
598 (typ, [data, ...]) = <instance>.namespace()
599 """
600 name = 'NAMESPACE'
601 typ, dat = self._simple_command(name)
602 return self._untagged_response(typ, dat, name)
603
604
Tim Peters07e99cb2001-01-14 23:47:14 +0000605 def noop(self):
606 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000607
Piers Laudere0273de2002-11-22 05:53:04 +0000608 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000609 """
610 if __debug__:
611 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000612 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000614
615
Tim Peters07e99cb2001-01-14 23:47:14 +0000616 def partial(self, message_num, message_part, start, length):
617 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000618
Tim Peters07e99cb2001-01-14 23:47:14 +0000619 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000620
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 'data' is tuple of message part envelope and data.
622 """
623 name = 'PARTIAL'
624 typ, dat = self._simple_command(name, message_num, message_part, start, length)
625 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000626
627
Piers Laudere0273de2002-11-22 05:53:04 +0000628 def proxyauth(self, user):
629 """Assume authentication as "user".
630
631 Allows an authorised administrator to proxy into any user's
632 mailbox.
633
634 (typ, [data]) = <instance>.proxyauth(user)
635 """
636
637 name = 'PROXYAUTH'
638 return self._simple_command('PROXYAUTH', user)
639
640
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 def rename(self, oldmailbox, newmailbox):
642 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000643
Piers Laudere0273de2002-11-22 05:53:04 +0000644 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000645 """
646 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000647
648
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 def search(self, charset, *criteria):
650 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000651
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000652 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000653
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 'data' is space separated list of matching message numbers.
655 """
656 name = 'SEARCH'
657 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000658 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000659 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000660 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000662
663
Piers Lauder14f39402005-08-31 10:46:29 +0000664 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000665 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000666
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000668
Piers Lauder14f39402005-08-31 10:46:29 +0000669 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000670
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000672
673 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
674 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 self.untagged_responses = {} # Flush old responses.
677 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000678 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000679 name = 'EXAMINE'
680 else:
681 name = 'SELECT'
682 typ, dat = self._simple_command(name, mailbox)
683 if typ != 'OK':
684 self.state = 'AUTH' # Might have been 'SELECTED'
685 return typ, dat
686 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000687 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000688 and not readonly:
689 if __debug__:
690 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000691 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000692 raise self.readonly('%s is not writable' % mailbox)
693 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000694
695
Piers Lauder15e5d532001-07-20 10:52:06 +0000696 def setacl(self, mailbox, who, what):
697 """Set a mailbox acl.
698
Piers Lauderf167dc32004-03-25 00:12:21 +0000699 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000700 """
701 return self._simple_command('SETACL', mailbox, who, what)
702
703
Piers Lauderd80ef022005-06-01 23:50:52 +0000704 def setannotation(self, *args):
705 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
706 Set ANNOTATIONs."""
707
708 typ, dat = self._simple_command('SETANNOTATION', *args)
709 return self._untagged_response(typ, dat, 'ANNOTATION')
710
711
Piers Lauder3fca2912002-06-17 07:07:20 +0000712 def setquota(self, root, limits):
713 """Set the quota root's resource limits.
714
715 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000716 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000717 typ, dat = self._simple_command('SETQUOTA', root, limits)
718 return self._untagged_response(typ, dat, 'QUOTA')
719
720
Piers Lauder15e5d532001-07-20 10:52:06 +0000721 def sort(self, sort_criteria, charset, *search_criteria):
722 """IMAP4rev1 extension SORT command.
723
724 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
725 """
726 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000727 #if not name in self.capabilities: # Let the server decide!
728 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000729 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000730 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000731 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000732 return self._untagged_response(typ, dat, name)
733
734
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000735 def starttls(self, ssl_context=None):
736 name = 'STARTTLS'
737 if not HAVE_SSL:
738 raise self.error('SSL support missing')
739 if self._tls_established:
740 raise self.abort('TLS session already established')
741 if name not in self.capabilities:
742 raise self.abort('TLS not supported by server')
743 # Generate a default SSL context if none was passed.
744 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100745 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000746 typ, dat = self._simple_command(name)
747 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100748 server_hostname = self.host if ssl.HAS_SNI else None
749 self.sock = ssl_context.wrap_socket(self.sock,
750 server_hostname=server_hostname)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000751 self.file = self.sock.makefile('rb')
752 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000753 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000754 else:
755 raise self.error("Couldn't establish TLS session")
756 return self._untagged_response(typ, dat, name)
757
758
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 def status(self, mailbox, names):
760 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000761
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 (typ, [data]) = <instance>.status(mailbox, names)
763 """
764 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000765 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000766 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000767 typ, dat = self._simple_command(name, mailbox, names)
768 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000769
770
Tim Peters07e99cb2001-01-14 23:47:14 +0000771 def store(self, message_set, command, flags):
772 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000773
Tim Peters07e99cb2001-01-14 23:47:14 +0000774 (typ, [data]) = <instance>.store(message_set, command, flags)
775 """
776 if (flags[0],flags[-1]) != ('(',')'):
777 flags = '(%s)' % flags # Avoid quoting the flags
778 typ, dat = self._simple_command('STORE', message_set, command, flags)
779 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000780
781
Tim Peters07e99cb2001-01-14 23:47:14 +0000782 def subscribe(self, mailbox):
783 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000784
Tim Peters07e99cb2001-01-14 23:47:14 +0000785 (typ, [data]) = <instance>.subscribe(mailbox)
786 """
787 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000788
789
Martin v. Löwisd8921372003-11-10 06:44:44 +0000790 def thread(self, threading_algorithm, charset, *search_criteria):
791 """IMAPrev1 extension THREAD command.
792
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000793 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000794 """
795 name = 'THREAD'
796 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
797 return self._untagged_response(typ, dat, name)
798
799
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 def uid(self, command, *args):
801 """Execute "command arg ..." with messages identified by UID,
802 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000803
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000805
Tim Peters07e99cb2001-01-14 23:47:14 +0000806 Returns response appropriate to 'command'.
807 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000808 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000809 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 raise self.error("Unknown IMAP4 UID command: %s" % command)
811 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000812 raise self.error("command %s illegal in state %s, "
813 "only allowed in states %s" %
814 (command, self.state,
815 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000817 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000818 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000819 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 else:
821 name = 'FETCH'
822 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 def unsubscribe(self, mailbox):
826 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 (typ, [data]) = <instance>.unsubscribe(mailbox)
829 """
830 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 def xatom(self, name, *args):
834 """Allow simple extension commands
835 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000836
Piers Lauder15e5d532001-07-20 10:52:06 +0000837 Assumes command is legal in current state.
838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000840
841 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000843 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000844 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000845 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000846 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000847 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000848 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000849
850
851
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000853
854
Tim Peters07e99cb2001-01-14 23:47:14 +0000855 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000856 if dat is None:
857 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000858 ur = self.untagged_responses
859 if __debug__:
860 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000861 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000863 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 ur[typ].append(dat)
865 else:
866 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 def _check_bye(self):
870 bye = self.untagged_responses.get('BYE')
871 if bye:
Antoine Pitroudac47912010-11-10 00:18:40 +0000872 raise self.abort(bye[-1].decode('ascii', 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000873
874
Tim Peters07e99cb2001-01-14 23:47:14 +0000875 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 if self.state not in Commands[name]:
878 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000879 raise self.error("command %s illegal in state %s, "
880 "only allowed in states %s" %
881 (name, self.state,
882 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000885 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000887
Raymond Hettinger54f02222002-06-01 14:18:47 +0000888 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 and not self.is_readonly:
890 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000891
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000893 name = bytes(name, 'ASCII')
894 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 for arg in args:
896 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000897 if isinstance(arg, str):
898 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000899 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 literal = self.literal
902 if literal is not None:
903 self.literal = None
904 if type(literal) is type(self._command):
905 literator = literal
906 else:
907 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000908 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 if __debug__:
911 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000912 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000914 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000917 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200918 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 if literal is None:
922 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 while 1:
925 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 while self._get_response():
928 if self.tagged_commands[tag]: # BAD/NO?
929 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 if literator:
934 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000935
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 if __debug__:
937 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000938 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000939
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000941 self.send(literal)
942 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200943 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 if not literator:
947 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000950
951
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000953 # BYE is expected after LOGOUT
954 if name != 'LOGOUT':
955 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 try:
957 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000958 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000960 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +0000962 if name != 'LOGOUT':
963 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 if typ == 'BAD':
965 raise self.error('%s command error: %s %s' % (name, typ, data))
966 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000967
968
Antoine Pitroudbe75192010-11-16 17:55:26 +0000969 def _get_capabilities(self):
970 typ, dat = self.capability()
971 if dat == [None]:
972 raise self.error('no CAPABILITY response from server')
973 dat = str(dat[-1], "ASCII")
974 dat = dat.upper()
975 self.capabilities = tuple(dat.split())
976
977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 # Read response and store.
981 #
982 # Returns None for continuation responses,
983 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 if self._match(self.tagre, resp):
990 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000991 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000992 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000995 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 dat = self.mo.group('data')
997 self.tagged_commands[tag] = (typ, [dat])
998 else:
999 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001000
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 if not self._match(Untagged_response, resp):
1004 if self._match(Untagged_status, resp):
1005 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001006
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 if self.mo is None:
1008 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 if self._match(Continuation, resp):
1011 self.continuation_response = self.mo.group('data')
1012 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001013
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001017 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001019 if dat is None: dat = b'' # Null untagged response
1020 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001027
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001028 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001029 if __debug__:
1030 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001031 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001032 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001035
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001041
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001043
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001045
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001047 typ = self.mo.group('type')
1048 typ = str(typ, "ASCII")
1049 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001050
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 if __debug__:
1052 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001053 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001054
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001056
1057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 while 1:
1061 result = self.tagged_commands[tag]
1062 if result is not None:
1063 del self.tagged_commands[tag]
1064 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 # Some have reported "unexpected response" exceptions.
1067 # Note that ignoring them here causes loops.
1068 # Instead, send me details of the unexpected response and
1069 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001070
Tim Peters07e99cb2001-01-14 23:47:14 +00001071 try:
1072 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001073 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 if __debug__:
1075 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001076 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001077 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001078
1079
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001081
Piers Lauder15e5d532001-07-20 10:52:06 +00001082 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 if not line:
1084 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001085
Tim Peters07e99cb2001-01-14 23:47:14 +00001086 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001087 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001088 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001089
Tim Peters07e99cb2001-01-14 23:47:14 +00001090 line = line[:-2]
1091 if __debug__:
1092 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001093 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001094 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001095 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001097
1098
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001100
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 # Run compiled regular expression match method on 's'.
1102 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001103
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 self.mo = cre.match(s)
1105 if __debug__:
1106 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001107 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001108 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001109
1110
Tim Peters07e99cb2001-01-14 23:47:14 +00001111 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001112
Christian Heimesfb5faf02008-11-05 19:39:50 +00001113 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001114 self.tagnum = self.tagnum + 1
1115 self.tagged_commands[tag] = None
1116 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001117
1118
Tim Peters07e99cb2001-01-14 23:47:14 +00001119 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001120
Antoine Pitroub1436f12010-11-09 22:55:55 +00001121 arg = arg.replace('\\', '\\\\')
1122 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001123
Antoine Pitroub1436f12010-11-09 22:55:55 +00001124 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001125
1126
Tim Peters07e99cb2001-01-14 23:47:14 +00001127 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001128
Guido van Rossum68468eb2003-02-27 20:14:51 +00001129 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001130
1131
Tim Peters07e99cb2001-01-14 23:47:14 +00001132 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 if typ == 'NO':
1134 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001135 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001136 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001137 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001138 if __debug__:
1139 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001140 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001141 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001142
1143
Piers Lauderf2d7d152002-02-22 01:15:17 +00001144 if __debug__:
1145
1146 def _mesg(self, s, secs=None):
1147 if secs is None:
1148 secs = time.time()
1149 tm = time.strftime('%M:%S', time.localtime(secs))
1150 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1151 sys.stderr.flush()
1152
1153 def _dump_ur(self, dict):
1154 # Dump untagged responses (in `dict').
1155 l = dict.items()
1156 if not l: return
1157 t = '\n\t\t'
1158 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1159 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1160
1161 def _log(self, line):
1162 # Keep log of last `_cmd_log_len' interactions for debugging.
1163 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1164 self._cmd_log_idx += 1
1165 if self._cmd_log_idx >= self._cmd_log_len:
1166 self._cmd_log_idx = 0
1167
1168 def print_log(self):
1169 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1170 i, n = self._cmd_log_idx, self._cmd_log_len
1171 while n:
1172 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001173 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001174 except:
1175 pass
1176 i += 1
1177 if i >= self._cmd_log_len:
1178 i = 0
1179 n -= 1
1180
1181
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001182if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001183
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001184 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001185
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001186 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001187
Antoine Pitrou08728162011-05-06 18:49:52 +02001188 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001189
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001190 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001191 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001192 keyfile - PEM formatted file that contains your private key (default: None);
1193 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001194 ssl_context - a SSLContext object that contains your certificate chain
1195 and private key (default: None)
1196 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001197 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001198
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001199 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001200 """
Piers Laudera4f83132002-03-08 01:53:24 +00001201
1202
Antoine Pitrou08728162011-05-06 18:49:52 +02001203 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None):
1204 if ssl_context is not None and keyfile is not None:
1205 raise ValueError("ssl_context and keyfile arguments are mutually "
1206 "exclusive")
1207 if ssl_context is not None and certfile is not None:
1208 raise ValueError("ssl_context and certfile arguments are mutually "
1209 "exclusive")
1210
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001211 self.keyfile = keyfile
1212 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001213 if ssl_context is None:
1214 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1215 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001216 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001217 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001218
Christian Heimesfb5faf02008-11-05 19:39:50 +00001219 def _create_socket(self):
1220 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001221 server_hostname = self.host if ssl.HAS_SNI else None
1222 return self.ssl_context.wrap_socket(sock,
1223 server_hostname=server_hostname)
Piers Laudera4f83132002-03-08 01:53:24 +00001224
Christian Heimesfb5faf02008-11-05 19:39:50 +00001225 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001226 """Setup connection to remote server on "host:port".
1227 (default: localhost:standard IMAP4 SSL port).
1228 This connection will be used by the routines:
1229 read, readline, send, shutdown.
1230 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001231 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001232
1233 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001234
1235
Piers Laudere0273de2002-11-22 05:53:04 +00001236class IMAP4_stream(IMAP4):
1237
1238 """IMAP4 client class over a stream
1239
1240 Instantiate with: IMAP4_stream(command)
1241
Christian Heimesfb5faf02008-11-05 19:39:50 +00001242 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001243
1244 for more documentation see the docstring of the parent class IMAP4.
1245 """
1246
1247
1248 def __init__(self, command):
1249 self.command = command
1250 IMAP4.__init__(self)
1251
1252
1253 def open(self, host = None, port = None):
1254 """Setup a stream connection.
1255 This connection will be used by the routines:
1256 read, readline, send, shutdown.
1257 """
1258 self.host = None # For compatibility with parent class
1259 self.port = None
1260 self.sock = None
1261 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001262 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001263 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001264 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1265 shell=True, close_fds=True)
1266 self.writefile = self.process.stdin
1267 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001268
1269 def read(self, size):
1270 """Read 'size' bytes from remote."""
1271 return self.readfile.read(size)
1272
1273
1274 def readline(self):
1275 """Read line from remote."""
1276 return self.readfile.readline()
1277
1278
1279 def send(self, data):
1280 """Send data to remote."""
1281 self.writefile.write(data)
1282 self.writefile.flush()
1283
1284
1285 def shutdown(self):
1286 """Close I/O established in "open"."""
1287 self.readfile.close()
1288 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001289 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001290
1291
1292
Guido van Rossumeda960a1998-06-18 14:24:28 +00001293class _Authenticator:
1294
Tim Peters07e99cb2001-01-14 23:47:14 +00001295 """Private class to provide en/decoding
1296 for base64-based authentication conversation.
1297 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001298
Tim Peters07e99cb2001-01-14 23:47:14 +00001299 def __init__(self, mechinst):
1300 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001301
Tim Peters07e99cb2001-01-14 23:47:14 +00001302 def process(self, data):
1303 ret = self.mech(self.decode(data))
1304 if ret is None:
1305 return '*' # Abort conversation
1306 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001307
Tim Peters07e99cb2001-01-14 23:47:14 +00001308 def encode(self, inp):
1309 #
1310 # Invoke binascii.b2a_base64 iteratively with
1311 # short even length buffers, strip the trailing
1312 # line feed from the result and append. "Even"
1313 # means a number that factors to both 6 and 8,
1314 # so when it gets to the end of the 8-bit input
1315 # there's no partial 6-bit output.
1316 #
R David Murray774a39f2013-02-19 12:17:31 -05001317 oup = b''
1318 if isinstance(inp, str):
1319 inp = inp.encode('ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001320 while inp:
1321 if len(inp) > 48:
1322 t = inp[:48]
1323 inp = inp[48:]
1324 else:
1325 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001326 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001327 e = binascii.b2a_base64(t)
1328 if e:
1329 oup = oup + e[:-1]
1330 return oup
1331
1332 def decode(self, inp):
1333 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001334 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001335 return binascii.a2b_base64(inp)
1336
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001337Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1338Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001339
1340def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001341 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001342
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001343 Return corresponding local time. The return value is a
1344 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001345 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001346
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 mo = InternalDate.match(resp)
1348 if not mo:
1349 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001350
Tim Peters07e99cb2001-01-14 23:47:14 +00001351 mon = Mon2num[mo.group('mon')]
1352 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001353
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001354 day = int(mo.group('day'))
1355 year = int(mo.group('year'))
1356 hour = int(mo.group('hour'))
1357 min = int(mo.group('min'))
1358 sec = int(mo.group('sec'))
1359 zoneh = int(mo.group('zoneh'))
1360 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001361
Tim Peters07e99cb2001-01-14 23:47:14 +00001362 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001363
Tim Peters07e99cb2001-01-14 23:47:14 +00001364 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001365 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001366 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001367
Tim Peters07e99cb2001-01-14 23:47:14 +00001368 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001369 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001370
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001371 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001372
1373
1374
1375def Int2AP(num):
1376
Tim Peters07e99cb2001-01-14 23:47:14 +00001377 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001378
Christian Heimesfb5faf02008-11-05 19:39:50 +00001379 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001380 num = int(abs(num))
1381 while num:
1382 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001383 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001384 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001385
1386
1387
1388def ParseFlags(resp):
1389
Tim Peters07e99cb2001-01-14 23:47:14 +00001390 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001391
Tim Peters07e99cb2001-01-14 23:47:14 +00001392 mo = Flags.match(resp)
1393 if not mo:
1394 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001395
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001396 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001397
1398
1399def Time2Internaldate(date_time):
1400
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001401 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001402
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001403 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001404 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001405 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001406 representing local time, an instance of time.struct_time (as
1407 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001408 double-quoted string. In the last case, it is assumed to already
1409 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001410 """
Fred Drakedb519202002-01-05 17:17:09 +00001411 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001412 dt = datetime.fromtimestamp(date_time,
1413 timezone.utc).astimezone()
1414 elif isinstance(date_time, tuple):
1415 try:
1416 gmtoff = date_time.tm_gmtoff
1417 except AttributeError:
1418 if time.daylight:
1419 dst = date_time[8]
1420 if dst == -1:
1421 dst = time.localtime(time.mktime(date_time))[8]
1422 gmtoff = -(time.timezone, time.altzone)[dst]
1423 else:
1424 gmtoff = -time.timezone
1425 delta = timedelta(seconds=gmtoff)
1426 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1427 elif isinstance(date_time, datetime):
1428 if date_time.tzinfo is None:
1429 raise ValueError("date_time must be aware")
1430 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001431 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001432 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001433 else:
1434 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001435 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1436 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001437
1438
1439
Guido van Rossum8c062211999-12-13 23:27:45 +00001440if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001441
Piers Laudere0273de2002-11-22 05:53:04 +00001442 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1443 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1444 # to test the IMAP4_stream class
1445
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001446 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001447
Tim Peters07e99cb2001-01-14 23:47:14 +00001448 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001449 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001450 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001451 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001452
Piers Laudere0273de2002-11-22 05:53:04 +00001453 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 for opt,val in optlist:
1455 if opt == '-d':
1456 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001457 elif opt == '-s':
1458 stream_command = val
1459 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001460
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001462
Tim Peters07e99cb2001-01-14 23:47:14 +00001463 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001464
Tim Peters07e99cb2001-01-14 23:47:14 +00001465 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001466 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001467
Piers Lauder47404ff2003-04-29 23:40:59 +00001468 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 +00001469 test_seq1 = (
1470 ('login', (USER, PASSWD)),
1471 ('create', ('/tmp/xxx 1',)),
1472 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1473 ('CREATE', ('/tmp/yyz 2',)),
1474 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1475 ('list', ('/tmp', 'yy*')),
1476 ('select', ('/tmp/yyz 2',)),
1477 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001478 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001479 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001480 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001481 ('expunge', ()),
1482 ('recent', ()),
1483 ('close', ()),
1484 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001485
Tim Peters07e99cb2001-01-14 23:47:14 +00001486 test_seq2 = (
1487 ('select', ()),
1488 ('response',('UIDVALIDITY',)),
1489 ('uid', ('SEARCH', 'ALL')),
1490 ('response', ('EXISTS',)),
1491 ('append', (None, None, None, test_mesg)),
1492 ('recent', ()),
1493 ('logout', ()),
1494 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001495
Tim Peters07e99cb2001-01-14 23:47:14 +00001496 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001497 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001498 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001499 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001500 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001501 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001502
Tim Peters07e99cb2001-01-14 23:47:14 +00001503 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001504 if stream_command:
1505 M = IMAP4_stream(stream_command)
1506 else:
1507 M = IMAP4(host)
1508 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001509 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001510 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001511 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001512
Tim Peters07e99cb2001-01-14 23:47:14 +00001513 for cmd,args in test_seq1:
1514 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001515
Tim Peters07e99cb2001-01-14 23:47:14 +00001516 for ml in run('list', ('/tmp/', 'yy%')):
1517 mo = re.match(r'.*"([^"]+)"$', ml)
1518 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001519 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001520 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001521
Tim Peters07e99cb2001-01-14 23:47:14 +00001522 for cmd,args in test_seq2:
1523 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001524
Tim Peters07e99cb2001-01-14 23:47:14 +00001525 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1526 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001527
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001528 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001529 if not uid: continue
1530 run('uid', ('FETCH', '%s' % uid[-1],
1531 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001532
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001533 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001534
Tim Peters07e99cb2001-01-14 23:47:14 +00001535 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001536 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001537
Tim Peters07e99cb2001-01-14 23:47:14 +00001538 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001539 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001540If you would like to see debugging output,
1541try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001542''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001543
Tim Peters07e99cb2001-01-14 23:47:14 +00001544 raise