blob: eb05dcb4f1909fbc639ea22a286f3c3f7a860cb9 [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)
R David Murray936da2a2015-03-22 16:17:46 -040048# don't specify a line length. RFC 2683 suggests limiting client
49# command lines to 1000 octets and that servers should be prepared
50# to accept command lines up to 8000 octets, so we used to use 10K here.
51# In the modern world (eg: gmail) the response to, for example, a
52# search command can be quite large, so we now use 1M.
53_MAXLINE = 1000000
Georg Brandlca580f42013-10-27 06:52:14 +010054
55
Tim Peters07e99cb2001-01-14 23:47:14 +000056# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000057
58Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000059 # name valid states
60 'APPEND': ('AUTH', 'SELECTED'),
61 'AUTHENTICATE': ('NONAUTH',),
62 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
63 'CHECK': ('SELECTED',),
64 'CLOSE': ('SELECTED',),
65 'COPY': ('SELECTED',),
66 'CREATE': ('AUTH', 'SELECTED'),
67 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000068 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000069 'EXAMINE': ('AUTH', 'SELECTED'),
70 'EXPUNGE': ('SELECTED',),
71 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000072 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000073 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000074 'GETQUOTA': ('AUTH', 'SELECTED'),
75 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000076 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000077 'LIST': ('AUTH', 'SELECTED'),
78 'LOGIN': ('NONAUTH',),
79 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
80 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000081 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000082 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000083 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000084 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000085 'RENAME': ('AUTH', 'SELECTED'),
86 'SEARCH': ('SELECTED',),
87 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000088 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000089 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000090 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000091 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000092 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000093 'STATUS': ('AUTH', 'SELECTED'),
94 'STORE': ('SELECTED',),
95 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000096 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000097 'UID': ('SELECTED',),
98 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
99 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000100
Tim Peters07e99cb2001-01-14 23:47:14 +0000101# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102
Christian Heimesfb5faf02008-11-05 19:39:50 +0000103Continuation = re.compile(br'\+( (?P<data>.*))?')
104Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
105InternalDate = re.compile(br'.*INTERNALDATE "'
106 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
107 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
108 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
109 br'"')
110Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
111MapCRLF = re.compile(br'\r\n|\r|\n')
112Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
113Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +0000114Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000115 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000116
117
118
119class IMAP4:
120
Tim Peters07e99cb2001-01-14 23:47:14 +0000121 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000122
Tim Peters07e99cb2001-01-14 23:47:14 +0000123 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000124
Tim Peters07e99cb2001-01-14 23:47:14 +0000125 host - host's name (default: localhost);
126 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000127
Tim Peters07e99cb2001-01-14 23:47:14 +0000128 All IMAP4rev1 commands are supported by methods of the same
129 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000130
Tim Peters07e99cb2001-01-14 23:47:14 +0000131 All arguments to commands are converted to strings, except for
132 AUTHENTICATE, and the last argument to APPEND which is passed as
133 an IMAP4 literal. If necessary (the string contains any
134 non-printing characters or white-space and isn't enclosed with
135 either parentheses or double quotes) each string is quoted.
136 However, the 'password' argument to the LOGIN command is always
137 quoted. If you want to avoid having an argument string quoted
138 (eg: the 'flags' argument to STORE) then enclose the string in
139 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000140
Tim Peters07e99cb2001-01-14 23:47:14 +0000141 Each command returns a tuple: (type, [data, ...]) where 'type'
142 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000143 tagged response, or untagged results from command. Each 'data'
144 is either a string, or a tuple. If a tuple, then the first part
145 is the header of the response, and the second part contains
146 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000147
Tim Peters07e99cb2001-01-14 23:47:14 +0000148 Errors raise the exception class <instance>.error("<reason>").
149 IMAP4 server errors raise <instance>.abort("<reason>"),
150 which is a sub-class of 'error'. Mailbox status changes
151 from READ-WRITE to READ-ONLY raise the exception class
152 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 "error" exceptions imply a program error.
155 "abort" exceptions imply the connection should be reset, and
156 the command re-tried.
157 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000158
Piers Lauderd80ef022005-06-01 23:50:52 +0000159 Note: to use this module, you must read the RFCs pertaining to the
160 IMAP4 protocol, as the semantics of the arguments to each IMAP4
161 command are left to the invoker, not to mention the results. Also,
162 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000164
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 class error(Exception): pass # Logical errors - debug required
166 class abort(error): pass # Service errors - close and retry
167 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000168
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 self.debug = Debug
171 self.state = 'LOGOUT'
172 self.literal = None # A literal argument to a command
173 self.tagged_commands = {} # Tagged commands awaiting response
174 self.untagged_responses = {} # {typ: [data, ...], ...}
175 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000176 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000177 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000178 self._tls_established = False
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000179
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000181
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
Victor Stinner33e649c2011-01-05 23:01:37 +0000184 try:
185 self._connect()
186 except Exception:
187 try:
188 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200189 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000190 pass
191 raise
192
193
194 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 # Create unique tag for this session,
196 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000197
Piers Lauder2dfc1682005-07-05 04:20:07 +0000198 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000199 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000200 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000201 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000202
Tim Peters07e99cb2001-01-14 23:47:14 +0000203 # Get server welcome message,
204 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000205
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000207 self._cmd_log_len = 10
208 self._cmd_log_idx = 0
209 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000211 self._mesg('imaplib version %s' % __version__)
212 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000213
Tim Peters07e99cb2001-01-14 23:47:14 +0000214 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000215 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000216 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000217 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000218 self.state = 'NONAUTH'
219 else:
220 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000221
Antoine Pitroudbe75192010-11-16 17:55:26 +0000222 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 if __debug__:
224 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000225 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000226
Tim Peters07e99cb2001-01-14 23:47:14 +0000227 for version in AllowedVersions:
228 if not version in self.capabilities:
229 continue
230 self.PROTOCOL_VERSION = version
231 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000232
Tim Peters07e99cb2001-01-14 23:47:14 +0000233 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000234
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000235
Tim Peters07e99cb2001-01-14 23:47:14 +0000236 def __getattr__(self, attr):
237 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000238 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000239 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000240 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000241
242
243
Piers Lauder15e5d532001-07-20 10:52:06 +0000244 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000245
246
Christian Heimesfb5faf02008-11-05 19:39:50 +0000247 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000248 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000249
Piers Lauderf97b2d72002-06-05 22:31:57 +0000250 def open(self, host = '', port = IMAP4_PORT):
251 """Setup connection to remote server on "host:port"
252 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000253 This connection will be used by the routines:
254 read, readline, send, shutdown.
255 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000256 self.host = host
257 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000258 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000259 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000260
261
Piers Lauder15e5d532001-07-20 10:52:06 +0000262 def read(self, size):
263 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200264 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000265
266
267 def readline(self):
268 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100269 line = self.file.readline(_MAXLINE + 1)
270 if len(line) > _MAXLINE:
271 raise self.error("got more than %d bytes" % _MAXLINE)
272 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000273
274
275 def send(self, data):
276 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000277 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000278
Piers Lauderf2d7d152002-02-22 01:15:17 +0000279
Piers Lauder15e5d532001-07-20 10:52:06 +0000280 def shutdown(self):
281 """Close I/O established in "open"."""
282 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000283 try:
284 self.sock.shutdown(socket.SHUT_RDWR)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200285 except OSError as e:
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000286 # The server might already have closed the connection
287 if e.errno != errno.ENOTCONN:
288 raise
289 finally:
290 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000291
292
293 def socket(self):
294 """Return socket instance used to connect to IMAP4 server.
295
296 socket = <instance>.socket()
297 """
298 return self.sock
299
300
301
302 # Utility methods
303
304
Tim Peters07e99cb2001-01-14 23:47:14 +0000305 def recent(self):
306 """Return most recent 'RECENT' responses if any exist,
307 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000310
Tim Peters07e99cb2001-01-14 23:47:14 +0000311 'data' is None if no new messages,
312 else list of RECENT responses, most recent last.
313 """
314 name = 'RECENT'
315 typ, dat = self._untagged_response('OK', [None], name)
316 if dat[-1]:
317 return typ, dat
318 typ, dat = self.noop() # Prod server for response
319 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000320
321
Tim Peters07e99cb2001-01-14 23:47:14 +0000322 def response(self, code):
323 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000324
Tim Peters07e99cb2001-01-14 23:47:14 +0000325 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000326
Tim Peters07e99cb2001-01-14 23:47:14 +0000327 (code, [data]) = <instance>.response(code)
328 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000329 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000330
331
Guido van Rossum26367a01998-09-28 15:34:46 +0000332
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000334
335
Tim Peters07e99cb2001-01-14 23:47:14 +0000336 def append(self, mailbox, flags, date_time, message):
337 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000338
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 All args except `message' can be None.
342 """
343 name = 'APPEND'
344 if not mailbox:
345 mailbox = 'INBOX'
346 if flags:
347 if (flags[0],flags[-1]) != ('(',')'):
348 flags = '(%s)' % flags
349 else:
350 flags = None
351 if date_time:
352 date_time = Time2Internaldate(date_time)
353 else:
354 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000355 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000356 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000357
358
Tim Peters07e99cb2001-01-14 23:47:14 +0000359 def authenticate(self, mechanism, authobject):
360 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000361
Tim Peters07e99cb2001-01-14 23:47:14 +0000362 'mechanism' specifies which authentication mechanism is to
363 be used - it must appear in <instance>.capabilities in the
364 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000365
Tim Peters07e99cb2001-01-14 23:47:14 +0000366 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000367
Tim Peters07e99cb2001-01-14 23:47:14 +0000368 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000369
R David Murray774a39f2013-02-19 12:17:31 -0500370 It will be called to process server continuation responses; the
371 response argument it is passed will be a bytes. It should return bytes
372 data that will be base64 encoded and sent to the server. It should
373 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000375 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000376 # XXX: shouldn't this code be removed, not commented out?
377 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000378 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000379 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 self.literal = _Authenticator(authobject).process
381 typ, dat = self._simple_command('AUTHENTICATE', mech)
382 if typ != 'OK':
383 raise self.error(dat[-1])
384 self.state = 'AUTH'
385 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000386
387
Piers Lauderd80ef022005-06-01 23:50:52 +0000388 def capability(self):
389 """(typ, [data]) = <instance>.capability()
390 Fetch capabilities list from server."""
391
392 name = 'CAPABILITY'
393 typ, dat = self._simple_command(name)
394 return self._untagged_response(typ, dat, name)
395
396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 def check(self):
398 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
Tim Peters07e99cb2001-01-14 23:47:14 +0000400 (typ, [data]) = <instance>.check()
401 """
402 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 def close(self):
406 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000407
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 Deleted messages are removed from writable mailbox.
409 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 (typ, [data]) = <instance>.close()
412 """
413 try:
414 typ, dat = self._simple_command('CLOSE')
415 finally:
416 self.state = 'AUTH'
417 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000418
419
Tim Peters07e99cb2001-01-14 23:47:14 +0000420 def copy(self, message_set, new_mailbox):
421 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
424 """
425 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000426
427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 def create(self, mailbox):
429 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 (typ, [data]) = <instance>.create(mailbox)
432 """
433 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000434
435
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 def delete(self, mailbox):
437 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000438
Tim Peters07e99cb2001-01-14 23:47:14 +0000439 (typ, [data]) = <instance>.delete(mailbox)
440 """
441 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000443 def deleteacl(self, mailbox, who):
444 """Delete the ACLs (remove any rights) set for who on mailbox.
445
446 (typ, [data]) = <instance>.deleteacl(mailbox, who)
447 """
448 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 def expunge(self):
451 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000452
Tim Peters07e99cb2001-01-14 23:47:14 +0000453 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000454
Tim Peters07e99cb2001-01-14 23:47:14 +0000455 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000456
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 'data' is list of 'EXPUNGE'd message numbers in order received.
458 """
459 name = 'EXPUNGE'
460 typ, dat = self._simple_command(name)
461 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000462
463
Tim Peters07e99cb2001-01-14 23:47:14 +0000464 def fetch(self, message_set, message_parts):
465 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000466
Tim Peters07e99cb2001-01-14 23:47:14 +0000467 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000468
Tim Peters07e99cb2001-01-14 23:47:14 +0000469 'message_parts' should be a string of selected parts
470 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000471
Tim Peters07e99cb2001-01-14 23:47:14 +0000472 'data' are tuples of message part envelope and data.
473 """
474 name = 'FETCH'
475 typ, dat = self._simple_command(name, message_set, message_parts)
476 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000477
478
Piers Lauder15e5d532001-07-20 10:52:06 +0000479 def getacl(self, mailbox):
480 """Get the ACLs for a mailbox.
481
482 (typ, [data]) = <instance>.getacl(mailbox)
483 """
484 typ, dat = self._simple_command('GETACL', mailbox)
485 return self._untagged_response(typ, dat, 'ACL')
486
487
Piers Lauderd80ef022005-06-01 23:50:52 +0000488 def getannotation(self, mailbox, entry, attribute):
489 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
490 Retrieve ANNOTATIONs."""
491
492 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
493 return self._untagged_response(typ, dat, 'ANNOTATION')
494
495
Piers Lauder3fca2912002-06-17 07:07:20 +0000496 def getquota(self, root):
497 """Get the quota root's resource usage and limits.
498
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000499 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000500
501 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000502 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000503 typ, dat = self._simple_command('GETQUOTA', root)
504 return self._untagged_response(typ, dat, 'QUOTA')
505
506
507 def getquotaroot(self, mailbox):
508 """Get the list of quota roots for the named mailbox.
509
510 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000511 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000512 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000513 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
514 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000515 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000516
517
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 def list(self, directory='""', pattern='*'):
519 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000520
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000522
Tim Peters07e99cb2001-01-14 23:47:14 +0000523 'data' is list of LIST responses.
524 """
525 name = 'LIST'
526 typ, dat = self._simple_command(name, directory, pattern)
527 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000528
529
Tim Peters07e99cb2001-01-14 23:47:14 +0000530 def login(self, user, password):
531 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000532
Tim Peters07e99cb2001-01-14 23:47:14 +0000533 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000534
Tim Peters07e99cb2001-01-14 23:47:14 +0000535 NB: 'password' will be quoted.
536 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
538 if typ != 'OK':
539 raise self.error(dat[-1])
540 self.state = 'AUTH'
541 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000542
543
Piers Laudere0273de2002-11-22 05:53:04 +0000544 def login_cram_md5(self, user, password):
545 """ Force use of CRAM-MD5 authentication.
546
547 (typ, [data]) = <instance>.login_cram_md5(user, password)
548 """
549 self.user, self.password = user, password
550 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
551
552
553 def _CRAM_MD5_AUTH(self, challenge):
554 """ Authobject to use with CRAM-MD5 authentication. """
555 import hmac
R David Murray774a39f2013-02-19 12:17:31 -0500556 pwd = (self.password.encode('ASCII') if isinstance(self.password, str)
557 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100558 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000559
560
Tim Peters07e99cb2001-01-14 23:47:14 +0000561 def logout(self):
562 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000563
Tim Peters07e99cb2001-01-14 23:47:14 +0000564 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000565
Tim Peters07e99cb2001-01-14 23:47:14 +0000566 Returns server 'BYE' response.
567 """
568 self.state = 'LOGOUT'
569 try: typ, dat = self._simple_command('LOGOUT')
570 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000571 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000572 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000573 return 'BYE', self.untagged_responses['BYE']
574 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000575
576
Tim Peters07e99cb2001-01-14 23:47:14 +0000577 def lsub(self, directory='""', pattern='*'):
578 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000579
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000581
Tim Peters07e99cb2001-01-14 23:47:14 +0000582 'data' are tuples of message part envelope and data.
583 """
584 name = 'LSUB'
585 typ, dat = self._simple_command(name, directory, pattern)
586 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000587
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000588 def myrights(self, mailbox):
589 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
590
591 (typ, [data]) = <instance>.myrights(mailbox)
592 """
593 typ,dat = self._simple_command('MYRIGHTS', mailbox)
594 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000595
Piers Lauder15e5d532001-07-20 10:52:06 +0000596 def namespace(self):
597 """ Returns IMAP namespaces ala rfc2342
598
599 (typ, [data, ...]) = <instance>.namespace()
600 """
601 name = 'NAMESPACE'
602 typ, dat = self._simple_command(name)
603 return self._untagged_response(typ, dat, name)
604
605
Tim Peters07e99cb2001-01-14 23:47:14 +0000606 def noop(self):
607 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000608
Piers Laudere0273de2002-11-22 05:53:04 +0000609 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000610 """
611 if __debug__:
612 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000613 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000615
616
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 def partial(self, message_num, message_part, start, length):
618 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000619
Tim Peters07e99cb2001-01-14 23:47:14 +0000620 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000621
Tim Peters07e99cb2001-01-14 23:47:14 +0000622 'data' is tuple of message part envelope and data.
623 """
624 name = 'PARTIAL'
625 typ, dat = self._simple_command(name, message_num, message_part, start, length)
626 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
628
Piers Laudere0273de2002-11-22 05:53:04 +0000629 def proxyauth(self, user):
630 """Assume authentication as "user".
631
632 Allows an authorised administrator to proxy into any user's
633 mailbox.
634
635 (typ, [data]) = <instance>.proxyauth(user)
636 """
637
638 name = 'PROXYAUTH'
639 return self._simple_command('PROXYAUTH', user)
640
641
Tim Peters07e99cb2001-01-14 23:47:14 +0000642 def rename(self, oldmailbox, newmailbox):
643 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000644
Piers Laudere0273de2002-11-22 05:53:04 +0000645 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000646 """
647 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000648
649
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 def search(self, charset, *criteria):
651 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000652
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000653 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 'data' is space separated list of matching message numbers.
656 """
657 name = 'SEARCH'
658 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000659 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000660 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000661 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000662 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000663
664
Piers Lauder14f39402005-08-31 10:46:29 +0000665 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000666 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000667
Tim Peters07e99cb2001-01-14 23:47:14 +0000668 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000669
Piers Lauder14f39402005-08-31 10:46:29 +0000670 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000671
Tim Peters07e99cb2001-01-14 23:47:14 +0000672 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000673
674 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
675 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000677 self.untagged_responses = {} # Flush old responses.
678 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000679 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 name = 'EXAMINE'
681 else:
682 name = 'SELECT'
683 typ, dat = self._simple_command(name, mailbox)
684 if typ != 'OK':
685 self.state = 'AUTH' # Might have been 'SELECTED'
686 return typ, dat
687 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000688 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000689 and not readonly:
690 if __debug__:
691 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000692 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000693 raise self.readonly('%s is not writable' % mailbox)
694 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000695
696
Piers Lauder15e5d532001-07-20 10:52:06 +0000697 def setacl(self, mailbox, who, what):
698 """Set a mailbox acl.
699
Piers Lauderf167dc32004-03-25 00:12:21 +0000700 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000701 """
702 return self._simple_command('SETACL', mailbox, who, what)
703
704
Piers Lauderd80ef022005-06-01 23:50:52 +0000705 def setannotation(self, *args):
706 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
707 Set ANNOTATIONs."""
708
709 typ, dat = self._simple_command('SETANNOTATION', *args)
710 return self._untagged_response(typ, dat, 'ANNOTATION')
711
712
Piers Lauder3fca2912002-06-17 07:07:20 +0000713 def setquota(self, root, limits):
714 """Set the quota root's resource limits.
715
716 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000717 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000718 typ, dat = self._simple_command('SETQUOTA', root, limits)
719 return self._untagged_response(typ, dat, 'QUOTA')
720
721
Piers Lauder15e5d532001-07-20 10:52:06 +0000722 def sort(self, sort_criteria, charset, *search_criteria):
723 """IMAP4rev1 extension SORT command.
724
725 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
726 """
727 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000728 #if not name in self.capabilities: # Let the server decide!
729 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000730 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000731 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000732 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000733 return self._untagged_response(typ, dat, name)
734
735
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000736 def starttls(self, ssl_context=None):
737 name = 'STARTTLS'
738 if not HAVE_SSL:
739 raise self.error('SSL support missing')
740 if self._tls_established:
741 raise self.abort('TLS session already established')
742 if name not in self.capabilities:
743 raise self.abort('TLS not supported by server')
744 # Generate a default SSL context if none was passed.
745 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100746 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000747 typ, dat = self._simple_command(name)
748 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100749 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600750 server_hostname=self.host)
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
R David Murray95ff7232014-02-07 13:44:57 -05001066 # If we've seen a BYE at this point, the socket will be
1067 # closed, so report the BYE now.
1068
1069 self._check_bye()
1070
Tim Peters07e99cb2001-01-14 23:47:14 +00001071 # Some have reported "unexpected response" exceptions.
1072 # Note that ignoring them here causes loops.
1073 # Instead, send me details of the unexpected response and
1074 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001075
Tim Peters07e99cb2001-01-14 23:47:14 +00001076 try:
1077 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001078 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001079 if __debug__:
1080 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001081 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001082 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001083
1084
Tim Peters07e99cb2001-01-14 23:47:14 +00001085 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001086
Piers Lauder15e5d532001-07-20 10:52:06 +00001087 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 if not line:
1089 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001090
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001092 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001093 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001094
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 line = line[:-2]
1096 if __debug__:
1097 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001098 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001100 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001102
1103
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001105
Tim Peters07e99cb2001-01-14 23:47:14 +00001106 # Run compiled regular expression match method on 's'.
1107 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001108
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 self.mo = cre.match(s)
1110 if __debug__:
1111 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001112 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001113 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001114
1115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001117
Christian Heimesfb5faf02008-11-05 19:39:50 +00001118 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001119 self.tagnum = self.tagnum + 1
1120 self.tagged_commands[tag] = None
1121 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001122
1123
Tim Peters07e99cb2001-01-14 23:47:14 +00001124 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001125
Antoine Pitroub1436f12010-11-09 22:55:55 +00001126 arg = arg.replace('\\', '\\\\')
1127 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001128
Antoine Pitroub1436f12010-11-09 22:55:55 +00001129 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001130
1131
Tim Peters07e99cb2001-01-14 23:47:14 +00001132 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001133
Guido van Rossum68468eb2003-02-27 20:14:51 +00001134 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001135
1136
Tim Peters07e99cb2001-01-14 23:47:14 +00001137 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001138 if typ == 'NO':
1139 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001140 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001141 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001142 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001143 if __debug__:
1144 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001145 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001146 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001147
1148
Piers Lauderf2d7d152002-02-22 01:15:17 +00001149 if __debug__:
1150
1151 def _mesg(self, s, secs=None):
1152 if secs is None:
1153 secs = time.time()
1154 tm = time.strftime('%M:%S', time.localtime(secs))
1155 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1156 sys.stderr.flush()
1157
1158 def _dump_ur(self, dict):
1159 # Dump untagged responses (in `dict').
1160 l = dict.items()
1161 if not l: return
1162 t = '\n\t\t'
1163 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1164 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1165
1166 def _log(self, line):
1167 # Keep log of last `_cmd_log_len' interactions for debugging.
1168 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1169 self._cmd_log_idx += 1
1170 if self._cmd_log_idx >= self._cmd_log_len:
1171 self._cmd_log_idx = 0
1172
1173 def print_log(self):
1174 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1175 i, n = self._cmd_log_idx, self._cmd_log_len
1176 while n:
1177 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001178 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001179 except:
1180 pass
1181 i += 1
1182 if i >= self._cmd_log_len:
1183 i = 0
1184 n -= 1
1185
1186
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001187if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001188
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001189 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001190
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001191 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001192
Antoine Pitrou08728162011-05-06 18:49:52 +02001193 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001194
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001195 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001196 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001197 keyfile - PEM formatted file that contains your private key (default: None);
1198 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001199 ssl_context - a SSLContext object that contains your certificate chain
1200 and private key (default: None)
1201 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001202 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001203
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001204 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001205 """
Piers Laudera4f83132002-03-08 01:53:24 +00001206
1207
Antoine Pitrou08728162011-05-06 18:49:52 +02001208 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None):
1209 if ssl_context is not None and keyfile is not None:
1210 raise ValueError("ssl_context and keyfile arguments are mutually "
1211 "exclusive")
1212 if ssl_context is not None and certfile is not None:
1213 raise ValueError("ssl_context and certfile arguments are mutually "
1214 "exclusive")
1215
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001216 self.keyfile = keyfile
1217 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001218 if ssl_context is None:
1219 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1220 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001221 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001222 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001223
Christian Heimesfb5faf02008-11-05 19:39:50 +00001224 def _create_socket(self):
1225 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001226 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001227 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001228
Christian Heimesfb5faf02008-11-05 19:39:50 +00001229 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001230 """Setup connection to remote server on "host:port".
1231 (default: localhost:standard IMAP4 SSL port).
1232 This connection will be used by the routines:
1233 read, readline, send, shutdown.
1234 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001235 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001236
1237 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001238
1239
Piers Laudere0273de2002-11-22 05:53:04 +00001240class IMAP4_stream(IMAP4):
1241
1242 """IMAP4 client class over a stream
1243
1244 Instantiate with: IMAP4_stream(command)
1245
Christian Heimesfb5faf02008-11-05 19:39:50 +00001246 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001247
1248 for more documentation see the docstring of the parent class IMAP4.
1249 """
1250
1251
1252 def __init__(self, command):
1253 self.command = command
1254 IMAP4.__init__(self)
1255
1256
1257 def open(self, host = None, port = None):
1258 """Setup a stream connection.
1259 This connection will be used by the routines:
1260 read, readline, send, shutdown.
1261 """
1262 self.host = None # For compatibility with parent class
1263 self.port = None
1264 self.sock = None
1265 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001266 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001267 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001268 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1269 shell=True, close_fds=True)
1270 self.writefile = self.process.stdin
1271 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001272
1273 def read(self, size):
1274 """Read 'size' bytes from remote."""
1275 return self.readfile.read(size)
1276
1277
1278 def readline(self):
1279 """Read line from remote."""
1280 return self.readfile.readline()
1281
1282
1283 def send(self, data):
1284 """Send data to remote."""
1285 self.writefile.write(data)
1286 self.writefile.flush()
1287
1288
1289 def shutdown(self):
1290 """Close I/O established in "open"."""
1291 self.readfile.close()
1292 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001293 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001294
1295
1296
Guido van Rossumeda960a1998-06-18 14:24:28 +00001297class _Authenticator:
1298
Tim Peters07e99cb2001-01-14 23:47:14 +00001299 """Private class to provide en/decoding
1300 for base64-based authentication conversation.
1301 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001302
Tim Peters07e99cb2001-01-14 23:47:14 +00001303 def __init__(self, mechinst):
1304 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001305
Tim Peters07e99cb2001-01-14 23:47:14 +00001306 def process(self, data):
1307 ret = self.mech(self.decode(data))
1308 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001309 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001310 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001311
Tim Peters07e99cb2001-01-14 23:47:14 +00001312 def encode(self, inp):
1313 #
1314 # Invoke binascii.b2a_base64 iteratively with
1315 # short even length buffers, strip the trailing
1316 # line feed from the result and append. "Even"
1317 # means a number that factors to both 6 and 8,
1318 # so when it gets to the end of the 8-bit input
1319 # there's no partial 6-bit output.
1320 #
R David Murray774a39f2013-02-19 12:17:31 -05001321 oup = b''
1322 if isinstance(inp, str):
1323 inp = inp.encode('ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001324 while inp:
1325 if len(inp) > 48:
1326 t = inp[:48]
1327 inp = inp[48:]
1328 else:
1329 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001330 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001331 e = binascii.b2a_base64(t)
1332 if e:
1333 oup = oup + e[:-1]
1334 return oup
1335
1336 def decode(self, inp):
1337 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001338 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001339 return binascii.a2b_base64(inp)
1340
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001341Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1342Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
1344def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001345 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001346
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001347 Return corresponding local time. The return value is a
1348 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001349 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001350
Tim Peters07e99cb2001-01-14 23:47:14 +00001351 mo = InternalDate.match(resp)
1352 if not mo:
1353 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001354
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 mon = Mon2num[mo.group('mon')]
1356 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001357
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001358 day = int(mo.group('day'))
1359 year = int(mo.group('year'))
1360 hour = int(mo.group('hour'))
1361 min = int(mo.group('min'))
1362 sec = int(mo.group('sec'))
1363 zoneh = int(mo.group('zoneh'))
1364 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001365
Tim Peters07e99cb2001-01-14 23:47:14 +00001366 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001367
Tim Peters07e99cb2001-01-14 23:47:14 +00001368 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001369 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001370 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001371
Tim Peters07e99cb2001-01-14 23:47:14 +00001372 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001373 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001374
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001375 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001376
1377
1378
1379def Int2AP(num):
1380
Tim Peters07e99cb2001-01-14 23:47:14 +00001381 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001382
Christian Heimesfb5faf02008-11-05 19:39:50 +00001383 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001384 num = int(abs(num))
1385 while num:
1386 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001387 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001388 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001389
1390
1391
1392def ParseFlags(resp):
1393
Tim Peters07e99cb2001-01-14 23:47:14 +00001394 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001395
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 mo = Flags.match(resp)
1397 if not mo:
1398 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001399
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001400 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001401
1402
1403def Time2Internaldate(date_time):
1404
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001405 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001406
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001407 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001408 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001409 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001410 representing local time, an instance of time.struct_time (as
1411 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001412 double-quoted string. In the last case, it is assumed to already
1413 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001414 """
Fred Drakedb519202002-01-05 17:17:09 +00001415 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001416 dt = datetime.fromtimestamp(date_time,
1417 timezone.utc).astimezone()
1418 elif isinstance(date_time, tuple):
1419 try:
1420 gmtoff = date_time.tm_gmtoff
1421 except AttributeError:
1422 if time.daylight:
1423 dst = date_time[8]
1424 if dst == -1:
1425 dst = time.localtime(time.mktime(date_time))[8]
1426 gmtoff = -(time.timezone, time.altzone)[dst]
1427 else:
1428 gmtoff = -time.timezone
1429 delta = timedelta(seconds=gmtoff)
1430 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1431 elif isinstance(date_time, datetime):
1432 if date_time.tzinfo is None:
1433 raise ValueError("date_time must be aware")
1434 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001435 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001436 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001437 else:
1438 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001439 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1440 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001441
1442
1443
Guido van Rossum8c062211999-12-13 23:27:45 +00001444if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001445
Piers Laudere0273de2002-11-22 05:53:04 +00001446 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1447 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1448 # to test the IMAP4_stream class
1449
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001450 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001451
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001453 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001454 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001455 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001456
Piers Laudere0273de2002-11-22 05:53:04 +00001457 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001458 for opt,val in optlist:
1459 if opt == '-d':
1460 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001461 elif opt == '-s':
1462 stream_command = val
1463 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001464
Tim Peters07e99cb2001-01-14 23:47:14 +00001465 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001466
Tim Peters07e99cb2001-01-14 23:47:14 +00001467 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001468
Tim Peters07e99cb2001-01-14 23:47:14 +00001469 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001470 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001471
Piers Lauder47404ff2003-04-29 23:40:59 +00001472 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 +00001473 test_seq1 = (
1474 ('login', (USER, PASSWD)),
1475 ('create', ('/tmp/xxx 1',)),
1476 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1477 ('CREATE', ('/tmp/yyz 2',)),
1478 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1479 ('list', ('/tmp', 'yy*')),
1480 ('select', ('/tmp/yyz 2',)),
1481 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001482 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001483 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001484 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001485 ('expunge', ()),
1486 ('recent', ()),
1487 ('close', ()),
1488 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001489
Tim Peters07e99cb2001-01-14 23:47:14 +00001490 test_seq2 = (
1491 ('select', ()),
1492 ('response',('UIDVALIDITY',)),
1493 ('uid', ('SEARCH', 'ALL')),
1494 ('response', ('EXISTS',)),
1495 ('append', (None, None, None, test_mesg)),
1496 ('recent', ()),
1497 ('logout', ()),
1498 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001499
Tim Peters07e99cb2001-01-14 23:47:14 +00001500 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001501 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001502 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001503 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001504 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001505 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001506
Tim Peters07e99cb2001-01-14 23:47:14 +00001507 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001508 if stream_command:
1509 M = IMAP4_stream(stream_command)
1510 else:
1511 M = IMAP4(host)
1512 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001513 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001514 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001515 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001516
Tim Peters07e99cb2001-01-14 23:47:14 +00001517 for cmd,args in test_seq1:
1518 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001519
Tim Peters07e99cb2001-01-14 23:47:14 +00001520 for ml in run('list', ('/tmp/', 'yy%')):
1521 mo = re.match(r'.*"([^"]+)"$', ml)
1522 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001523 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001524 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001525
Tim Peters07e99cb2001-01-14 23:47:14 +00001526 for cmd,args in test_seq2:
1527 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001528
Tim Peters07e99cb2001-01-14 23:47:14 +00001529 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1530 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001531
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001532 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001533 if not uid: continue
1534 run('uid', ('FETCH', '%s' % uid[-1],
1535 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001536
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001537 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001538
Tim Peters07e99cb2001-01-14 23:47:14 +00001539 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001540 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001541
Tim Peters07e99cb2001-01-14 23:47:14 +00001542 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001543 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001544If you would like to see debugging output,
1545try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001546''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001547
Tim Peters07e99cb2001-01-14 23:47:14 +00001548 raise