blob: 970b6a537f7152001be664a7297c0809b8e80531 [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'),
R David Murraya6429db2015-05-10 19:17:23 -040069 'ENABLE': ('AUTH', ),
Tim Peters07e99cb2001-01-14 23:47:14 +000070 'EXAMINE': ('AUTH', 'SELECTED'),
71 'EXPUNGE': ('SELECTED',),
72 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000073 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000074 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000075 'GETQUOTA': ('AUTH', 'SELECTED'),
76 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000077 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000078 'LIST': ('AUTH', 'SELECTED'),
79 'LOGIN': ('NONAUTH',),
80 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
81 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000082 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000083 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000084 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000085 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000086 'RENAME': ('AUTH', 'SELECTED'),
87 'SEARCH': ('SELECTED',),
88 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000089 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000090 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000091 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000092 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000093 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000094 'STATUS': ('AUTH', 'SELECTED'),
95 'STORE': ('SELECTED',),
96 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000097 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000098 'UID': ('SELECTED',),
99 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
100 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000101
Tim Peters07e99cb2001-01-14 23:47:14 +0000102# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000103
Christian Heimesfb5faf02008-11-05 19:39:50 +0000104Continuation = re.compile(br'\+( (?P<data>.*))?')
105Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
106InternalDate = re.compile(br'.*INTERNALDATE "'
107 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
108 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
109 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
110 br'"')
R David Murraya6429db2015-05-10 19:17:23 -0400111# Literal is no longer used; kept for backward compatibility.
Christian Heimesfb5faf02008-11-05 19:39:50 +0000112Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
113MapCRLF = re.compile(br'\r\n|\r|\n')
114Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
115Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
R David Murraya6429db2015-05-10 19:17:23 -0400116# Untagged_status is no longer used; kept for backward compatibility
Antoine Pitroufd036452008-08-19 17:56:33 +0000117Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000118 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
R David Murraya6429db2015-05-10 19:17:23 -0400119# We compile these in _mode_xxx.
120_Literal = br'.*{(?P<size>\d+)}$'
121_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000122
123
124
125class IMAP4:
126
Tim Peters07e99cb2001-01-14 23:47:14 +0000127 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000130
Tim Peters07e99cb2001-01-14 23:47:14 +0000131 host - host's name (default: localhost);
132 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000133
Tim Peters07e99cb2001-01-14 23:47:14 +0000134 All IMAP4rev1 commands are supported by methods of the same
135 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000136
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 All arguments to commands are converted to strings, except for
138 AUTHENTICATE, and the last argument to APPEND which is passed as
139 an IMAP4 literal. If necessary (the string contains any
140 non-printing characters or white-space and isn't enclosed with
141 either parentheses or double quotes) each string is quoted.
142 However, the 'password' argument to the LOGIN command is always
143 quoted. If you want to avoid having an argument string quoted
144 (eg: the 'flags' argument to STORE) then enclose the string in
145 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000146
Tim Peters07e99cb2001-01-14 23:47:14 +0000147 Each command returns a tuple: (type, [data, ...]) where 'type'
148 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000149 tagged response, or untagged results from command. Each 'data'
150 is either a string, or a tuple. If a tuple, then the first part
151 is the header of the response, and the second part contains
152 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 Errors raise the exception class <instance>.error("<reason>").
155 IMAP4 server errors raise <instance>.abort("<reason>"),
156 which is a sub-class of 'error'. Mailbox status changes
157 from READ-WRITE to READ-ONLY raise the exception class
158 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 "error" exceptions imply a program error.
161 "abort" exceptions imply the connection should be reset, and
162 the command re-tried.
163 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000164
Piers Lauderd80ef022005-06-01 23:50:52 +0000165 Note: to use this module, you must read the RFCs pertaining to the
166 IMAP4 protocol, as the semantics of the arguments to each IMAP4
167 command are left to the invoker, not to mention the results. Also,
168 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000170
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 class error(Exception): pass # Logical errors - debug required
172 class abort(error): pass # Service errors - close and retry
173 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000174
R David Murraya6429db2015-05-10 19:17:23 -0400175 def __init__(self, host='', port=IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000176 self.debug = Debug
177 self.state = 'LOGOUT'
178 self.literal = None # A literal argument to a command
179 self.tagged_commands = {} # Tagged commands awaiting response
180 self.untagged_responses = {} # {typ: [data, ...], ...}
181 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000182 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000184 self._tls_established = False
R David Murraya6429db2015-05-10 19:17:23 -0400185 self._mode_ascii()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000186
Tim Peters07e99cb2001-01-14 23:47:14 +0000187 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000188
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000190
Victor Stinner33e649c2011-01-05 23:01:37 +0000191 try:
192 self._connect()
193 except Exception:
194 try:
195 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200196 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000197 pass
198 raise
199
R David Murraya6429db2015-05-10 19:17:23 -0400200 def _mode_ascii(self):
201 self.utf8_enabled = False
202 self._encoding = 'ascii'
203 self.Literal = re.compile(_Literal, re.ASCII)
204 self.Untagged_status = re.compile(_Untagged_status, re.ASCII)
205
206
207 def _mode_utf8(self):
208 self.utf8_enabled = True
209 self._encoding = 'utf-8'
210 self.Literal = re.compile(_Literal)
211 self.Untagged_status = re.compile(_Untagged_status)
212
Victor Stinner33e649c2011-01-05 23:01:37 +0000213
214 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 # Create unique tag for this session,
216 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000217
Piers Lauder2dfc1682005-07-05 04:20:07 +0000218 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000219 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000220 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000221 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000222
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 # Get server welcome message,
224 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000225
Tim Peters07e99cb2001-01-14 23:47:14 +0000226 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000227 self._cmd_log_len = 10
228 self._cmd_log_idx = 0
229 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000230 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000231 self._mesg('imaplib version %s' % __version__)
232 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000233
Tim Peters07e99cb2001-01-14 23:47:14 +0000234 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000235 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000236 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000237 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000238 self.state = 'NONAUTH'
239 else:
240 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000241
Antoine Pitroudbe75192010-11-16 17:55:26 +0000242 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000243 if __debug__:
244 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000245 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000246
Tim Peters07e99cb2001-01-14 23:47:14 +0000247 for version in AllowedVersions:
248 if not version in self.capabilities:
249 continue
250 self.PROTOCOL_VERSION = version
251 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000252
Tim Peters07e99cb2001-01-14 23:47:14 +0000253 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000254
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000255
Tim Peters07e99cb2001-01-14 23:47:14 +0000256 def __getattr__(self, attr):
257 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000258 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000259 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000260 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000261
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300262 def __enter__(self):
263 return self
264
265 def __exit__(self, *args):
266 try:
267 self.logout()
268 except OSError:
269 pass
Guido van Rossum26367a01998-09-28 15:34:46 +0000270
271
Piers Lauder15e5d532001-07-20 10:52:06 +0000272 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000273
274
Christian Heimesfb5faf02008-11-05 19:39:50 +0000275 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000276 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000277
Piers Lauderf97b2d72002-06-05 22:31:57 +0000278 def open(self, host = '', port = IMAP4_PORT):
279 """Setup connection to remote server on "host:port"
280 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000281 This connection will be used by the routines:
282 read, readline, send, shutdown.
283 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000284 self.host = host
285 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000286 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000287 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000288
289
Piers Lauder15e5d532001-07-20 10:52:06 +0000290 def read(self, size):
291 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200292 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000293
294
295 def readline(self):
296 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100297 line = self.file.readline(_MAXLINE + 1)
298 if len(line) > _MAXLINE:
299 raise self.error("got more than %d bytes" % _MAXLINE)
300 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000301
302
303 def send(self, data):
304 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000305 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000306
Piers Lauderf2d7d152002-02-22 01:15:17 +0000307
Piers Lauder15e5d532001-07-20 10:52:06 +0000308 def shutdown(self):
309 """Close I/O established in "open"."""
310 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000311 try:
312 self.sock.shutdown(socket.SHUT_RDWR)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200313 except OSError as e:
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000314 # The server might already have closed the connection
315 if e.errno != errno.ENOTCONN:
316 raise
317 finally:
318 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000319
320
321 def socket(self):
322 """Return socket instance used to connect to IMAP4 server.
323
324 socket = <instance>.socket()
325 """
326 return self.sock
327
328
329
330 # Utility methods
331
332
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 def recent(self):
334 """Return most recent 'RECENT' responses if any exist,
335 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000336
Tim Peters07e99cb2001-01-14 23:47:14 +0000337 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000338
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 'data' is None if no new messages,
340 else list of RECENT responses, most recent last.
341 """
342 name = 'RECENT'
343 typ, dat = self._untagged_response('OK', [None], name)
344 if dat[-1]:
345 return typ, dat
346 typ, dat = self.noop() # Prod server for response
347 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000348
349
Tim Peters07e99cb2001-01-14 23:47:14 +0000350 def response(self, code):
351 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000352
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000354
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 (code, [data]) = <instance>.response(code)
356 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000357 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000358
359
Guido van Rossum26367a01998-09-28 15:34:46 +0000360
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000362
363
Tim Peters07e99cb2001-01-14 23:47:14 +0000364 def append(self, mailbox, flags, date_time, message):
365 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 All args except `message' can be None.
370 """
371 name = 'APPEND'
372 if not mailbox:
373 mailbox = 'INBOX'
374 if flags:
375 if (flags[0],flags[-1]) != ('(',')'):
376 flags = '(%s)' % flags
377 else:
378 flags = None
379 if date_time:
380 date_time = Time2Internaldate(date_time)
381 else:
382 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400383 literal = MapCRLF.sub(CRLF, message)
384 if self.utf8_enabled:
385 literal = b'UTF8 (' + literal + b')'
386 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000387 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000388
389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 def authenticate(self, mechanism, authobject):
391 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 'mechanism' specifies which authentication mechanism is to
394 be used - it must appear in <instance>.capabilities in the
395 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000400
R David Murray774a39f2013-02-19 12:17:31 -0500401 It will be called to process server continuation responses; the
402 response argument it is passed will be a bytes. It should return bytes
403 data that will be base64 encoded and sent to the server. It should
404 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000406 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000407 # XXX: shouldn't this code be removed, not commented out?
408 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000409 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000410 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 self.literal = _Authenticator(authobject).process
412 typ, dat = self._simple_command('AUTHENTICATE', mech)
413 if typ != 'OK':
414 raise self.error(dat[-1])
415 self.state = 'AUTH'
416 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
418
Piers Lauderd80ef022005-06-01 23:50:52 +0000419 def capability(self):
420 """(typ, [data]) = <instance>.capability()
421 Fetch capabilities list from server."""
422
423 name = 'CAPABILITY'
424 typ, dat = self._simple_command(name)
425 return self._untagged_response(typ, dat, name)
426
427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 def check(self):
429 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 (typ, [data]) = <instance>.check()
432 """
433 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000434
435
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 def close(self):
437 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000438
Tim Peters07e99cb2001-01-14 23:47:14 +0000439 Deleted messages are removed from writable mailbox.
440 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Tim Peters07e99cb2001-01-14 23:47:14 +0000442 (typ, [data]) = <instance>.close()
443 """
444 try:
445 typ, dat = self._simple_command('CLOSE')
446 finally:
447 self.state = 'AUTH'
448 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
450
Tim Peters07e99cb2001-01-14 23:47:14 +0000451 def copy(self, message_set, new_mailbox):
452 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000453
Tim Peters07e99cb2001-01-14 23:47:14 +0000454 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
455 """
456 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000457
458
Tim Peters07e99cb2001-01-14 23:47:14 +0000459 def create(self, mailbox):
460 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
Tim Peters07e99cb2001-01-14 23:47:14 +0000462 (typ, [data]) = <instance>.create(mailbox)
463 """
464 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
466
Tim Peters07e99cb2001-01-14 23:47:14 +0000467 def delete(self, mailbox):
468 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000469
Tim Peters07e99cb2001-01-14 23:47:14 +0000470 (typ, [data]) = <instance>.delete(mailbox)
471 """
472 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000473
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000474 def deleteacl(self, mailbox, who):
475 """Delete the ACLs (remove any rights) set for who on mailbox.
476
477 (typ, [data]) = <instance>.deleteacl(mailbox, who)
478 """
479 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000480
R David Murraya6429db2015-05-10 19:17:23 -0400481 def enable(self, capability):
482 """Send an RFC5161 enable string to the server.
483
484 (typ, [data]) = <intance>.enable(capability)
485 """
486 if 'ENABLE' not in self.capabilities:
487 raise IMAP4.error("Server does not support ENABLE")
488 typ, data = self._simple_command('ENABLE', capability)
489 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
490 self._mode_utf8()
491 return typ, data
492
Tim Peters07e99cb2001-01-14 23:47:14 +0000493 def expunge(self):
494 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000499
Tim Peters07e99cb2001-01-14 23:47:14 +0000500 'data' is list of 'EXPUNGE'd message numbers in order received.
501 """
502 name = 'EXPUNGE'
503 typ, dat = self._simple_command(name)
504 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
506
Tim Peters07e99cb2001-01-14 23:47:14 +0000507 def fetch(self, message_set, message_parts):
508 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 'message_parts' should be a string of selected parts
513 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 'data' are tuples of message part envelope and data.
516 """
517 name = 'FETCH'
518 typ, dat = self._simple_command(name, message_set, message_parts)
519 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000520
521
Piers Lauder15e5d532001-07-20 10:52:06 +0000522 def getacl(self, mailbox):
523 """Get the ACLs for a mailbox.
524
525 (typ, [data]) = <instance>.getacl(mailbox)
526 """
527 typ, dat = self._simple_command('GETACL', mailbox)
528 return self._untagged_response(typ, dat, 'ACL')
529
530
Piers Lauderd80ef022005-06-01 23:50:52 +0000531 def getannotation(self, mailbox, entry, attribute):
532 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
533 Retrieve ANNOTATIONs."""
534
535 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
536 return self._untagged_response(typ, dat, 'ANNOTATION')
537
538
Piers Lauder3fca2912002-06-17 07:07:20 +0000539 def getquota(self, root):
540 """Get the quota root's resource usage and limits.
541
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000542 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000543
544 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000545 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000546 typ, dat = self._simple_command('GETQUOTA', root)
547 return self._untagged_response(typ, dat, 'QUOTA')
548
549
550 def getquotaroot(self, mailbox):
551 """Get the list of quota roots for the named mailbox.
552
553 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000554 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000555 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000556 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
557 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000558 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000559
560
Tim Peters07e99cb2001-01-14 23:47:14 +0000561 def list(self, directory='""', pattern='*'):
562 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000563
Tim Peters07e99cb2001-01-14 23:47:14 +0000564 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000565
Tim Peters07e99cb2001-01-14 23:47:14 +0000566 'data' is list of LIST responses.
567 """
568 name = 'LIST'
569 typ, dat = self._simple_command(name, directory, pattern)
570 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000571
572
Tim Peters07e99cb2001-01-14 23:47:14 +0000573 def login(self, user, password):
574 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000575
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000577
Tim Peters07e99cb2001-01-14 23:47:14 +0000578 NB: 'password' will be quoted.
579 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
581 if typ != 'OK':
582 raise self.error(dat[-1])
583 self.state = 'AUTH'
584 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000585
586
Piers Laudere0273de2002-11-22 05:53:04 +0000587 def login_cram_md5(self, user, password):
588 """ Force use of CRAM-MD5 authentication.
589
590 (typ, [data]) = <instance>.login_cram_md5(user, password)
591 """
592 self.user, self.password = user, password
593 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
594
595
596 def _CRAM_MD5_AUTH(self, challenge):
597 """ Authobject to use with CRAM-MD5 authentication. """
598 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400599 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500600 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100601 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000602
603
Tim Peters07e99cb2001-01-14 23:47:14 +0000604 def logout(self):
605 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000606
Tim Peters07e99cb2001-01-14 23:47:14 +0000607 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000608
Tim Peters07e99cb2001-01-14 23:47:14 +0000609 Returns server 'BYE' response.
610 """
611 self.state = 'LOGOUT'
612 try: typ, dat = self._simple_command('LOGOUT')
613 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000614 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000615 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000616 return 'BYE', self.untagged_responses['BYE']
617 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000618
619
Tim Peters07e99cb2001-01-14 23:47:14 +0000620 def lsub(self, directory='""', pattern='*'):
621 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000622
Tim Peters07e99cb2001-01-14 23:47:14 +0000623 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000624
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 'data' are tuples of message part envelope and data.
626 """
627 name = 'LSUB'
628 typ, dat = self._simple_command(name, directory, pattern)
629 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000630
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000631 def myrights(self, mailbox):
632 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
633
634 (typ, [data]) = <instance>.myrights(mailbox)
635 """
636 typ,dat = self._simple_command('MYRIGHTS', mailbox)
637 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
Piers Lauder15e5d532001-07-20 10:52:06 +0000639 def namespace(self):
640 """ Returns IMAP namespaces ala rfc2342
641
642 (typ, [data, ...]) = <instance>.namespace()
643 """
644 name = 'NAMESPACE'
645 typ, dat = self._simple_command(name)
646 return self._untagged_response(typ, dat, name)
647
648
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 def noop(self):
650 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000651
Piers Laudere0273de2002-11-22 05:53:04 +0000652 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 """
654 if __debug__:
655 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000656 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000657 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000658
659
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 def partial(self, message_num, message_part, start, length):
661 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000662
Tim Peters07e99cb2001-01-14 23:47:14 +0000663 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000664
Tim Peters07e99cb2001-01-14 23:47:14 +0000665 'data' is tuple of message part envelope and data.
666 """
667 name = 'PARTIAL'
668 typ, dat = self._simple_command(name, message_num, message_part, start, length)
669 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000670
671
Piers Laudere0273de2002-11-22 05:53:04 +0000672 def proxyauth(self, user):
673 """Assume authentication as "user".
674
675 Allows an authorised administrator to proxy into any user's
676 mailbox.
677
678 (typ, [data]) = <instance>.proxyauth(user)
679 """
680
681 name = 'PROXYAUTH'
682 return self._simple_command('PROXYAUTH', user)
683
684
Tim Peters07e99cb2001-01-14 23:47:14 +0000685 def rename(self, oldmailbox, newmailbox):
686 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000687
Piers Laudere0273de2002-11-22 05:53:04 +0000688 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000689 """
690 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000691
692
Tim Peters07e99cb2001-01-14 23:47:14 +0000693 def search(self, charset, *criteria):
694 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000695
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000696 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000697
Tim Peters07e99cb2001-01-14 23:47:14 +0000698 'data' is space separated list of matching message numbers.
R David Murraya6429db2015-05-10 19:17:23 -0400699 If UTF8 is enabled, charset MUST be None.
Tim Peters07e99cb2001-01-14 23:47:14 +0000700 """
701 name = 'SEARCH'
702 if charset:
R David Murraya6429db2015-05-10 19:17:23 -0400703 if self.utf8_enabled:
704 raise IMAP4.error("Non-None charset not valid in UTF8 mode")
Guido van Rossum68468eb2003-02-27 20:14:51 +0000705 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000706 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000707 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000708 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000709
710
Piers Lauder14f39402005-08-31 10:46:29 +0000711 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000712 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000713
Tim Peters07e99cb2001-01-14 23:47:14 +0000714 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000715
Piers Lauder14f39402005-08-31 10:46:29 +0000716 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000717
Tim Peters07e99cb2001-01-14 23:47:14 +0000718 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000719
720 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
721 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 self.untagged_responses = {} # Flush old responses.
724 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000725 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 name = 'EXAMINE'
727 else:
728 name = 'SELECT'
729 typ, dat = self._simple_command(name, mailbox)
730 if typ != 'OK':
731 self.state = 'AUTH' # Might have been 'SELECTED'
732 return typ, dat
733 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000734 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000735 and not readonly:
736 if __debug__:
737 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000738 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000739 raise self.readonly('%s is not writable' % mailbox)
740 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000741
742
Piers Lauder15e5d532001-07-20 10:52:06 +0000743 def setacl(self, mailbox, who, what):
744 """Set a mailbox acl.
745
Piers Lauderf167dc32004-03-25 00:12:21 +0000746 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000747 """
748 return self._simple_command('SETACL', mailbox, who, what)
749
750
Piers Lauderd80ef022005-06-01 23:50:52 +0000751 def setannotation(self, *args):
752 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
753 Set ANNOTATIONs."""
754
755 typ, dat = self._simple_command('SETANNOTATION', *args)
756 return self._untagged_response(typ, dat, 'ANNOTATION')
757
758
Piers Lauder3fca2912002-06-17 07:07:20 +0000759 def setquota(self, root, limits):
760 """Set the quota root's resource limits.
761
762 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000763 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000764 typ, dat = self._simple_command('SETQUOTA', root, limits)
765 return self._untagged_response(typ, dat, 'QUOTA')
766
767
Piers Lauder15e5d532001-07-20 10:52:06 +0000768 def sort(self, sort_criteria, charset, *search_criteria):
769 """IMAP4rev1 extension SORT command.
770
771 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
772 """
773 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000774 #if not name in self.capabilities: # Let the server decide!
775 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000776 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000777 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000778 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000779 return self._untagged_response(typ, dat, name)
780
781
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000782 def starttls(self, ssl_context=None):
783 name = 'STARTTLS'
784 if not HAVE_SSL:
785 raise self.error('SSL support missing')
786 if self._tls_established:
787 raise self.abort('TLS session already established')
788 if name not in self.capabilities:
789 raise self.abort('TLS not supported by server')
790 # Generate a default SSL context if none was passed.
791 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100792 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000793 typ, dat = self._simple_command(name)
794 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100795 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600796 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000797 self.file = self.sock.makefile('rb')
798 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000799 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000800 else:
801 raise self.error("Couldn't establish TLS session")
802 return self._untagged_response(typ, dat, name)
803
804
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 def status(self, mailbox, names):
806 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000807
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 (typ, [data]) = <instance>.status(mailbox, names)
809 """
810 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000811 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000812 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 typ, dat = self._simple_command(name, mailbox, names)
814 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 def store(self, message_set, command, flags):
818 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 (typ, [data]) = <instance>.store(message_set, command, flags)
821 """
822 if (flags[0],flags[-1]) != ('(',')'):
823 flags = '(%s)' % flags # Avoid quoting the flags
824 typ, dat = self._simple_command('STORE', message_set, command, flags)
825 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 def subscribe(self, mailbox):
829 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000830
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 (typ, [data]) = <instance>.subscribe(mailbox)
832 """
833 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000834
835
Martin v. Löwisd8921372003-11-10 06:44:44 +0000836 def thread(self, threading_algorithm, charset, *search_criteria):
837 """IMAPrev1 extension THREAD command.
838
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000839 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000840 """
841 name = 'THREAD'
842 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
843 return self._untagged_response(typ, dat, name)
844
845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 def uid(self, command, *args):
847 """Execute "command arg ..." with messages identified by UID,
848 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000851
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 Returns response appropriate to 'command'.
853 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000854 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000855 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 raise self.error("Unknown IMAP4 UID command: %s" % command)
857 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000858 raise self.error("command %s illegal in state %s, "
859 "only allowed in states %s" %
860 (command, self.state,
861 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000863 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000864 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000865 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 else:
867 name = 'FETCH'
868 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 def unsubscribe(self, mailbox):
872 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000873
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 (typ, [data]) = <instance>.unsubscribe(mailbox)
875 """
876 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 def xatom(self, name, *args):
880 """Allow simple extension commands
881 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
Piers Lauder15e5d532001-07-20 10:52:06 +0000883 Assumes command is legal in current state.
884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000886
887 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000888 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000889 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000890 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000891 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000892 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000893 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000894 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000895
896
897
Tim Peters07e99cb2001-01-14 23:47:14 +0000898 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000902 if dat is None:
903 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 ur = self.untagged_responses
905 if __debug__:
906 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000907 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000909 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 ur[typ].append(dat)
911 else:
912 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000913
914
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 def _check_bye(self):
916 bye = self.untagged_responses.get('BYE')
917 if bye:
R David Murraya6429db2015-05-10 19:17:23 -0400918 raise self.abort(bye[-1].decode(self._encoding, 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000919
920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 if self.state not in Commands[name]:
924 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000925 raise self.error("command %s illegal in state %s, "
926 "only allowed in states %s" %
927 (name, self.state,
928 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000931 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000933
Raymond Hettinger54f02222002-06-01 14:18:47 +0000934 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 and not self.is_readonly:
936 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000937
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 tag = self._new_tag()
R David Murraya6429db2015-05-10 19:17:23 -0400939 name = bytes(name, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000940 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 for arg in args:
942 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000943 if isinstance(arg, str):
R David Murraya6429db2015-05-10 19:17:23 -0400944 arg = bytes(arg, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000945 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 literal = self.literal
948 if literal is not None:
949 self.literal = None
950 if type(literal) is type(self._command):
951 literator = literal
952 else:
953 literator = None
R David Murraya6429db2015-05-10 19:17:23 -0400954 data = data + bytes(' {%s}' % len(literal), self._encoding)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 if __debug__:
957 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000958 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000960 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000961
Tim Peters07e99cb2001-01-14 23:47:14 +0000962 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000963 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200964 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 if literal is None:
968 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000969
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 while 1:
971 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 while self._get_response():
974 if self.tagged_commands[tag]: # BAD/NO?
975 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 if literator:
980 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 if __debug__:
983 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000984 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000985
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000987 self.send(literal)
988 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200989 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000991
Tim Peters07e99cb2001-01-14 23:47:14 +0000992 if not literator:
993 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000996
997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +0000999 # BYE is expected after LOGOUT
1000 if name != 'LOGOUT':
1001 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 try:
1003 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +00001004 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001005 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001006 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +00001008 if name != 'LOGOUT':
1009 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 if typ == 'BAD':
1011 raise self.error('%s command error: %s %s' % (name, typ, data))
1012 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001013
1014
Antoine Pitroudbe75192010-11-16 17:55:26 +00001015 def _get_capabilities(self):
1016 typ, dat = self.capability()
1017 if dat == [None]:
1018 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001019 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001020 dat = dat.upper()
1021 self.capabilities = tuple(dat.split())
1022
1023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 # Read response and store.
1027 #
1028 # Returns None for continuation responses,
1029 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001030
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001032
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001034
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 if self._match(self.tagre, resp):
1036 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001037 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001038 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001041 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 dat = self.mo.group('data')
1043 self.tagged_commands[tag] = (typ, [dat])
1044 else:
1045 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001046
Tim Peters07e99cb2001-01-14 23:47:14 +00001047 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001048
Tim Peters07e99cb2001-01-14 23:47:14 +00001049 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001050 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001052
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 if self.mo is None:
1054 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001055
Tim Peters07e99cb2001-01-14 23:47:14 +00001056 if self._match(Continuation, resp):
1057 self.continuation_response = self.mo.group('data')
1058 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001059
R David Murraya6429db2015-05-10 19:17:23 -04001060 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001061
Tim Peters07e99cb2001-01-14 23:47:14 +00001062 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001063 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001065 if dat is None: dat = b'' # Null untagged response
1066 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001069
R David Murraya6429db2015-05-10 19:17:23 -04001070 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001071
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001074 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 if __debug__:
1076 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001077 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001078 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001081
Tim Peters07e99cb2001-01-14 23:47:14 +00001082 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001083
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001085
Tim Peters07e99cb2001-01-14 23:47:14 +00001086 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001087
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001089
Tim Peters07e99cb2001-01-14 23:47:14 +00001090 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001093 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001094 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001095 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001096
Tim Peters07e99cb2001-01-14 23:47:14 +00001097 if __debug__:
1098 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001099 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001100
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001102
1103
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001105
Tim Peters07e99cb2001-01-14 23:47:14 +00001106 while 1:
1107 result = self.tagged_commands[tag]
1108 if result is not None:
1109 del self.tagged_commands[tag]
1110 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001111
R David Murray95ff7232014-02-07 13:44:57 -05001112 # If we've seen a BYE at this point, the socket will be
1113 # closed, so report the BYE now.
1114
1115 self._check_bye()
1116
Tim Peters07e99cb2001-01-14 23:47:14 +00001117 # Some have reported "unexpected response" exceptions.
1118 # Note that ignoring them here causes loops.
1119 # Instead, send me details of the unexpected response and
1120 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001121
Tim Peters07e99cb2001-01-14 23:47:14 +00001122 try:
1123 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001124 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001125 if __debug__:
1126 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001127 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001128 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001129
1130
Tim Peters07e99cb2001-01-14 23:47:14 +00001131 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001132
Piers Lauder15e5d532001-07-20 10:52:06 +00001133 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001134 if not line:
1135 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001136
Tim Peters07e99cb2001-01-14 23:47:14 +00001137 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001138 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001139 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001140
Tim Peters07e99cb2001-01-14 23:47:14 +00001141 line = line[:-2]
1142 if __debug__:
1143 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001144 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001145 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001146 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001147 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001148
1149
Tim Peters07e99cb2001-01-14 23:47:14 +00001150 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001151
Tim Peters07e99cb2001-01-14 23:47:14 +00001152 # Run compiled regular expression match method on 's'.
1153 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001154
Tim Peters07e99cb2001-01-14 23:47:14 +00001155 self.mo = cre.match(s)
1156 if __debug__:
1157 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001158 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001159 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001160
1161
Tim Peters07e99cb2001-01-14 23:47:14 +00001162 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001163
R David Murraya6429db2015-05-10 19:17:23 -04001164 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001165 self.tagnum = self.tagnum + 1
1166 self.tagged_commands[tag] = None
1167 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001168
1169
Tim Peters07e99cb2001-01-14 23:47:14 +00001170 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001171
Antoine Pitroub1436f12010-11-09 22:55:55 +00001172 arg = arg.replace('\\', '\\\\')
1173 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001174
Antoine Pitroub1436f12010-11-09 22:55:55 +00001175 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001176
1177
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001179
Guido van Rossum68468eb2003-02-27 20:14:51 +00001180 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001181
1182
Tim Peters07e99cb2001-01-14 23:47:14 +00001183 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001184 if typ == 'NO':
1185 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001186 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001187 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001188 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001189 if __debug__:
1190 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001191 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001192 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001193
1194
Piers Lauderf2d7d152002-02-22 01:15:17 +00001195 if __debug__:
1196
1197 def _mesg(self, s, secs=None):
1198 if secs is None:
1199 secs = time.time()
1200 tm = time.strftime('%M:%S', time.localtime(secs))
1201 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1202 sys.stderr.flush()
1203
1204 def _dump_ur(self, dict):
1205 # Dump untagged responses (in `dict').
1206 l = dict.items()
1207 if not l: return
1208 t = '\n\t\t'
1209 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1210 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1211
1212 def _log(self, line):
1213 # Keep log of last `_cmd_log_len' interactions for debugging.
1214 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1215 self._cmd_log_idx += 1
1216 if self._cmd_log_idx >= self._cmd_log_len:
1217 self._cmd_log_idx = 0
1218
1219 def print_log(self):
1220 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1221 i, n = self._cmd_log_idx, self._cmd_log_len
1222 while n:
1223 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001224 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001225 except:
1226 pass
1227 i += 1
1228 if i >= self._cmd_log_len:
1229 i = 0
1230 n -= 1
1231
1232
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001233if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001234
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001235 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001236
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001237 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001238
Antoine Pitrou08728162011-05-06 18:49:52 +02001239 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001240
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001241 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001242 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001243 keyfile - PEM formatted file that contains your private key (default: None);
1244 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001245 ssl_context - a SSLContext object that contains your certificate chain
1246 and private key (default: None)
1247 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001248 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001249
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001250 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001251 """
Piers Laudera4f83132002-03-08 01:53:24 +00001252
1253
R David Murraya6429db2015-05-10 19:17:23 -04001254 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
1255 certfile=None, ssl_context=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001256 if ssl_context is not None and keyfile is not None:
1257 raise ValueError("ssl_context and keyfile arguments are mutually "
1258 "exclusive")
1259 if ssl_context is not None and certfile is not None:
1260 raise ValueError("ssl_context and certfile arguments are mutually "
1261 "exclusive")
1262
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001263 self.keyfile = keyfile
1264 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001265 if ssl_context is None:
1266 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1267 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001268 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001269 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001270
Christian Heimesfb5faf02008-11-05 19:39:50 +00001271 def _create_socket(self):
1272 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001273 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001274 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001275
Christian Heimesfb5faf02008-11-05 19:39:50 +00001276 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001277 """Setup connection to remote server on "host:port".
1278 (default: localhost:standard IMAP4 SSL port).
1279 This connection will be used by the routines:
1280 read, readline, send, shutdown.
1281 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001282 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001283
1284 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001285
1286
Piers Laudere0273de2002-11-22 05:53:04 +00001287class IMAP4_stream(IMAP4):
1288
1289 """IMAP4 client class over a stream
1290
1291 Instantiate with: IMAP4_stream(command)
1292
R David Murraya6429db2015-05-10 19:17:23 -04001293 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001294
1295 for more documentation see the docstring of the parent class IMAP4.
1296 """
1297
1298
1299 def __init__(self, command):
1300 self.command = command
1301 IMAP4.__init__(self)
1302
1303
1304 def open(self, host = None, port = None):
1305 """Setup a stream connection.
1306 This connection will be used by the routines:
1307 read, readline, send, shutdown.
1308 """
1309 self.host = None # For compatibility with parent class
1310 self.port = None
1311 self.sock = None
1312 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001313 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001314 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001315 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1316 shell=True, close_fds=True)
1317 self.writefile = self.process.stdin
1318 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001319
1320 def read(self, size):
1321 """Read 'size' bytes from remote."""
1322 return self.readfile.read(size)
1323
1324
1325 def readline(self):
1326 """Read line from remote."""
1327 return self.readfile.readline()
1328
1329
1330 def send(self, data):
1331 """Send data to remote."""
1332 self.writefile.write(data)
1333 self.writefile.flush()
1334
1335
1336 def shutdown(self):
1337 """Close I/O established in "open"."""
1338 self.readfile.close()
1339 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001340 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001341
1342
1343
Guido van Rossumeda960a1998-06-18 14:24:28 +00001344class _Authenticator:
1345
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 """Private class to provide en/decoding
1347 for base64-based authentication conversation.
1348 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001349
Tim Peters07e99cb2001-01-14 23:47:14 +00001350 def __init__(self, mechinst):
1351 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001352
Tim Peters07e99cb2001-01-14 23:47:14 +00001353 def process(self, data):
1354 ret = self.mech(self.decode(data))
1355 if ret is None:
1356 return '*' # Abort conversation
1357 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001358
Tim Peters07e99cb2001-01-14 23:47:14 +00001359 def encode(self, inp):
1360 #
1361 # Invoke binascii.b2a_base64 iteratively with
1362 # short even length buffers, strip the trailing
1363 # line feed from the result and append. "Even"
1364 # means a number that factors to both 6 and 8,
1365 # so when it gets to the end of the 8-bit input
1366 # there's no partial 6-bit output.
1367 #
R David Murray774a39f2013-02-19 12:17:31 -05001368 oup = b''
1369 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001370 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001371 while inp:
1372 if len(inp) > 48:
1373 t = inp[:48]
1374 inp = inp[48:]
1375 else:
1376 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001377 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 e = binascii.b2a_base64(t)
1379 if e:
1380 oup = oup + e[:-1]
1381 return oup
1382
1383 def decode(self, inp):
1384 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001385 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001386 return binascii.a2b_base64(inp)
1387
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001388Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1389Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001390
1391def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001392 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001393
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001394 Return corresponding local time. The return value is a
1395 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001397
Tim Peters07e99cb2001-01-14 23:47:14 +00001398 mo = InternalDate.match(resp)
1399 if not mo:
1400 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001401
Tim Peters07e99cb2001-01-14 23:47:14 +00001402 mon = Mon2num[mo.group('mon')]
1403 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001404
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001405 day = int(mo.group('day'))
1406 year = int(mo.group('year'))
1407 hour = int(mo.group('hour'))
1408 min = int(mo.group('min'))
1409 sec = int(mo.group('sec'))
1410 zoneh = int(mo.group('zoneh'))
1411 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001414
Tim Peters07e99cb2001-01-14 23:47:14 +00001415 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001416 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001418
Tim Peters07e99cb2001-01-14 23:47:14 +00001419 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001420 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001421
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001422 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
1424
1425
1426def Int2AP(num):
1427
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001429
Christian Heimesfb5faf02008-11-05 19:39:50 +00001430 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001431 num = int(abs(num))
1432 while num:
1433 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001434 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001435 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001436
1437
1438
1439def ParseFlags(resp):
1440
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001442
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 mo = Flags.match(resp)
1444 if not mo:
1445 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001446
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001447 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001448
1449
1450def Time2Internaldate(date_time):
1451
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001452 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001453
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001454 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001455 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001456 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001457 representing local time, an instance of time.struct_time (as
1458 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001459 double-quoted string. In the last case, it is assumed to already
1460 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 """
Fred Drakedb519202002-01-05 17:17:09 +00001462 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001463 dt = datetime.fromtimestamp(date_time,
1464 timezone.utc).astimezone()
1465 elif isinstance(date_time, tuple):
1466 try:
1467 gmtoff = date_time.tm_gmtoff
1468 except AttributeError:
1469 if time.daylight:
1470 dst = date_time[8]
1471 if dst == -1:
1472 dst = time.localtime(time.mktime(date_time))[8]
1473 gmtoff = -(time.timezone, time.altzone)[dst]
1474 else:
1475 gmtoff = -time.timezone
1476 delta = timedelta(seconds=gmtoff)
1477 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1478 elif isinstance(date_time, datetime):
1479 if date_time.tzinfo is None:
1480 raise ValueError("date_time must be aware")
1481 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001482 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001483 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001484 else:
1485 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001486 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1487 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001488
1489
1490
Guido van Rossum8c062211999-12-13 23:27:45 +00001491if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001492
Piers Laudere0273de2002-11-22 05:53:04 +00001493 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1494 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1495 # to test the IMAP4_stream class
1496
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001497 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001498
Tim Peters07e99cb2001-01-14 23:47:14 +00001499 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001500 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001501 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001502 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001503
Piers Laudere0273de2002-11-22 05:53:04 +00001504 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001505 for opt,val in optlist:
1506 if opt == '-d':
1507 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001508 elif opt == '-s':
1509 stream_command = val
1510 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001511
Tim Peters07e99cb2001-01-14 23:47:14 +00001512 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001513
Tim Peters07e99cb2001-01-14 23:47:14 +00001514 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001515
Tim Peters07e99cb2001-01-14 23:47:14 +00001516 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001517 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001518
Piers Lauder47404ff2003-04-29 23:40:59 +00001519 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 +00001520 test_seq1 = (
1521 ('login', (USER, PASSWD)),
1522 ('create', ('/tmp/xxx 1',)),
1523 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1524 ('CREATE', ('/tmp/yyz 2',)),
1525 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1526 ('list', ('/tmp', 'yy*')),
1527 ('select', ('/tmp/yyz 2',)),
1528 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001529 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001530 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001531 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001532 ('expunge', ()),
1533 ('recent', ()),
1534 ('close', ()),
1535 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001536
Tim Peters07e99cb2001-01-14 23:47:14 +00001537 test_seq2 = (
1538 ('select', ()),
1539 ('response',('UIDVALIDITY',)),
1540 ('uid', ('SEARCH', 'ALL')),
1541 ('response', ('EXISTS',)),
1542 ('append', (None, None, None, test_mesg)),
1543 ('recent', ()),
1544 ('logout', ()),
1545 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001546
Tim Peters07e99cb2001-01-14 23:47:14 +00001547 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001548 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001549 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001550 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001551 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001552 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001553
Tim Peters07e99cb2001-01-14 23:47:14 +00001554 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001555 if stream_command:
1556 M = IMAP4_stream(stream_command)
1557 else:
1558 M = IMAP4(host)
1559 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001560 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001561 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001562 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001563
Tim Peters07e99cb2001-01-14 23:47:14 +00001564 for cmd,args in test_seq1:
1565 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001566
Tim Peters07e99cb2001-01-14 23:47:14 +00001567 for ml in run('list', ('/tmp/', 'yy%')):
1568 mo = re.match(r'.*"([^"]+)"$', ml)
1569 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001570 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001571 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001572
Tim Peters07e99cb2001-01-14 23:47:14 +00001573 for cmd,args in test_seq2:
1574 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001575
Tim Peters07e99cb2001-01-14 23:47:14 +00001576 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1577 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001578
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001579 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001580 if not uid: continue
1581 run('uid', ('FETCH', '%s' % uid[-1],
1582 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001583
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001584 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001585
Tim Peters07e99cb2001-01-14 23:47:14 +00001586 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001587 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001588
Tim Peters07e99cb2001-01-14 23:47:14 +00001589 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001590 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001591If you would like to see debugging output,
1592try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001593''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001594
Tim Peters07e99cb2001-01-14 23:47:14 +00001595 raise