blob: 2fa90120e7decc9de8ccd61b16ba4c8eb04f5588 [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')
R David Murray317f64f2016-01-02 17:18:34 -0500114# We no longer exclude the ']' character from the data portion of the response
115# code, even though it violates the RFC. Popular IMAP servers such as Gmail
116# allow flags with ']', and there are programs (including imaplib!) that can
117# produce them. The problem with this is if the 'text' portion of the response
118# includes a ']' we'll parse the response wrong (which is the point of the RFC
119# restriction). However, that seems less likely to be a problem in practice
120# than being unable to correctly parse flags that include ']' chars, which
121# was reported as a real-world problem in issue #21815.
122Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>.*))?\]')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000123Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
R David Murraya6429db2015-05-10 19:17:23 -0400124# Untagged_status is no longer used; kept for backward compatibility
Antoine Pitroufd036452008-08-19 17:56:33 +0000125Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000126 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
R David Murraya6429db2015-05-10 19:17:23 -0400127# We compile these in _mode_xxx.
128_Literal = br'.*{(?P<size>\d+)}$'
129_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000130
131
132
133class IMAP4:
134
R David Murray44b548d2016-09-08 13:59:53 -0400135 r"""IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000136
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000138
Tim Peters07e99cb2001-01-14 23:47:14 +0000139 host - host's name (default: localhost);
140 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000141
Tim Peters07e99cb2001-01-14 23:47:14 +0000142 All IMAP4rev1 commands are supported by methods of the same
143 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000144
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 All arguments to commands are converted to strings, except for
146 AUTHENTICATE, and the last argument to APPEND which is passed as
147 an IMAP4 literal. If necessary (the string contains any
148 non-printing characters or white-space and isn't enclosed with
149 either parentheses or double quotes) each string is quoted.
150 However, the 'password' argument to the LOGIN command is always
151 quoted. If you want to avoid having an argument string quoted
152 (eg: the 'flags' argument to STORE) then enclose the string in
153 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000154
Tim Peters07e99cb2001-01-14 23:47:14 +0000155 Each command returns a tuple: (type, [data, ...]) where 'type'
156 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000157 tagged response, or untagged results from command. Each 'data'
158 is either a string, or a tuple. If a tuple, then the first part
159 is the header of the response, and the second part contains
160 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 Errors raise the exception class <instance>.error("<reason>").
163 IMAP4 server errors raise <instance>.abort("<reason>"),
164 which is a sub-class of 'error'. Mailbox status changes
165 from READ-WRITE to READ-ONLY raise the exception class
166 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000167
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 "error" exceptions imply a program error.
169 "abort" exceptions imply the connection should be reset, and
170 the command re-tried.
171 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000172
Piers Lauderd80ef022005-06-01 23:50:52 +0000173 Note: to use this module, you must read the RFCs pertaining to the
174 IMAP4 protocol, as the semantics of the arguments to each IMAP4
175 command are left to the invoker, not to mention the results. Also,
176 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000177 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000178
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 class error(Exception): pass # Logical errors - debug required
180 class abort(error): pass # Service errors - close and retry
181 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000182
R David Murraya6429db2015-05-10 19:17:23 -0400183 def __init__(self, host='', port=IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000184 self.debug = Debug
185 self.state = 'LOGOUT'
186 self.literal = None # A literal argument to a command
187 self.tagged_commands = {} # Tagged commands awaiting response
188 self.untagged_responses = {} # {typ: [data, ...], ...}
189 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000190 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000191 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000192 self._tls_established = False
R David Murraya6429db2015-05-10 19:17:23 -0400193 self._mode_ascii()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000194
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000196
Tim Peters07e99cb2001-01-14 23:47:14 +0000197 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000198
Victor Stinner33e649c2011-01-05 23:01:37 +0000199 try:
200 self._connect()
201 except Exception:
202 try:
203 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200204 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000205 pass
206 raise
207
R David Murraya6429db2015-05-10 19:17:23 -0400208 def _mode_ascii(self):
209 self.utf8_enabled = False
210 self._encoding = 'ascii'
211 self.Literal = re.compile(_Literal, re.ASCII)
212 self.Untagged_status = re.compile(_Untagged_status, re.ASCII)
213
214
215 def _mode_utf8(self):
216 self.utf8_enabled = True
217 self._encoding = 'utf-8'
218 self.Literal = re.compile(_Literal)
219 self.Untagged_status = re.compile(_Untagged_status)
220
Victor Stinner33e649c2011-01-05 23:01:37 +0000221
222 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 # Create unique tag for this session,
224 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000225
Piers Lauder2dfc1682005-07-05 04:20:07 +0000226 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000227 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000228 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000229 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000230
Tim Peters07e99cb2001-01-14 23:47:14 +0000231 # Get server welcome message,
232 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000233
Tim Peters07e99cb2001-01-14 23:47:14 +0000234 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000235 self._cmd_log_len = 10
236 self._cmd_log_idx = 0
237 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000238 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000239 self._mesg('imaplib version %s' % __version__)
240 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000241
Tim Peters07e99cb2001-01-14 23:47:14 +0000242 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000243 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000244 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000245 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000246 self.state = 'NONAUTH'
247 else:
248 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000249
Antoine Pitroudbe75192010-11-16 17:55:26 +0000250 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000251 if __debug__:
252 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000253 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000254
Tim Peters07e99cb2001-01-14 23:47:14 +0000255 for version in AllowedVersions:
256 if not version in self.capabilities:
257 continue
258 self.PROTOCOL_VERSION = version
259 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000260
Tim Peters07e99cb2001-01-14 23:47:14 +0000261 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000262
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000263
Tim Peters07e99cb2001-01-14 23:47:14 +0000264 def __getattr__(self, attr):
265 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000266 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000267 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000268 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000269
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300270 def __enter__(self):
271 return self
272
273 def __exit__(self, *args):
274 try:
275 self.logout()
276 except OSError:
277 pass
Guido van Rossum26367a01998-09-28 15:34:46 +0000278
279
Piers Lauder15e5d532001-07-20 10:52:06 +0000280 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000281
282
Christian Heimesfb5faf02008-11-05 19:39:50 +0000283 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000284 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000285
Piers Lauderf97b2d72002-06-05 22:31:57 +0000286 def open(self, host = '', port = IMAP4_PORT):
287 """Setup connection to remote server on "host:port"
288 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000289 This connection will be used by the routines:
290 read, readline, send, shutdown.
291 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000292 self.host = host
293 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000294 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000295 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000296
297
Piers Lauder15e5d532001-07-20 10:52:06 +0000298 def read(self, size):
299 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200300 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000301
302
303 def readline(self):
304 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100305 line = self.file.readline(_MAXLINE + 1)
306 if len(line) > _MAXLINE:
307 raise self.error("got more than %d bytes" % _MAXLINE)
308 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000309
310
311 def send(self, data):
312 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000313 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000314
Piers Lauderf2d7d152002-02-22 01:15:17 +0000315
Piers Lauder15e5d532001-07-20 10:52:06 +0000316 def shutdown(self):
317 """Close I/O established in "open"."""
318 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000319 try:
320 self.sock.shutdown(socket.SHUT_RDWR)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200321 except OSError as e:
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000322 # The server might already have closed the connection
323 if e.errno != errno.ENOTCONN:
324 raise
325 finally:
326 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000327
328
329 def socket(self):
330 """Return socket instance used to connect to IMAP4 server.
331
332 socket = <instance>.socket()
333 """
334 return self.sock
335
336
337
338 # Utility methods
339
340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 def recent(self):
342 """Return most recent 'RECENT' responses if any exist,
343 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000346
Tim Peters07e99cb2001-01-14 23:47:14 +0000347 'data' is None if no new messages,
348 else list of RECENT responses, most recent last.
349 """
350 name = 'RECENT'
351 typ, dat = self._untagged_response('OK', [None], name)
352 if dat[-1]:
353 return typ, dat
354 typ, dat = self.noop() # Prod server for response
355 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000356
357
Tim Peters07e99cb2001-01-14 23:47:14 +0000358 def response(self, code):
359 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000360
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000362
Tim Peters07e99cb2001-01-14 23:47:14 +0000363 (code, [data]) = <instance>.response(code)
364 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000365 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000366
367
Guido van Rossum26367a01998-09-28 15:34:46 +0000368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000370
371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 def append(self, mailbox, flags, date_time, message):
373 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000374
Tim Peters07e99cb2001-01-14 23:47:14 +0000375 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000376
Tim Peters07e99cb2001-01-14 23:47:14 +0000377 All args except `message' can be None.
378 """
379 name = 'APPEND'
380 if not mailbox:
381 mailbox = 'INBOX'
382 if flags:
383 if (flags[0],flags[-1]) != ('(',')'):
384 flags = '(%s)' % flags
385 else:
386 flags = None
387 if date_time:
388 date_time = Time2Internaldate(date_time)
389 else:
390 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400391 literal = MapCRLF.sub(CRLF, message)
392 if self.utf8_enabled:
393 literal = b'UTF8 (' + literal + b')'
394 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 def authenticate(self, mechanism, authobject):
399 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000400
Tim Peters07e99cb2001-01-14 23:47:14 +0000401 'mechanism' specifies which authentication mechanism is to
402 be used - it must appear in <instance>.capabilities in the
403 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000406
Tim Peters07e99cb2001-01-14 23:47:14 +0000407 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000408
R David Murray774a39f2013-02-19 12:17:31 -0500409 It will be called to process server continuation responses; the
410 response argument it is passed will be a bytes. It should return bytes
411 data that will be base64 encoded and sent to the server. It should
412 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000414 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000415 # XXX: shouldn't this code be removed, not commented out?
416 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000417 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000418 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 self.literal = _Authenticator(authobject).process
420 typ, dat = self._simple_command('AUTHENTICATE', mech)
421 if typ != 'OK':
R David Murrayb079c072016-12-24 21:32:26 -0500422 raise self.error(dat[-1].decode('utf-8', 'replace'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 self.state = 'AUTH'
424 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000425
426
Piers Lauderd80ef022005-06-01 23:50:52 +0000427 def capability(self):
428 """(typ, [data]) = <instance>.capability()
429 Fetch capabilities list from server."""
430
431 name = 'CAPABILITY'
432 typ, dat = self._simple_command(name)
433 return self._untagged_response(typ, dat, name)
434
435
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 def check(self):
437 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000438
Tim Peters07e99cb2001-01-14 23:47:14 +0000439 (typ, [data]) = <instance>.check()
440 """
441 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 def close(self):
445 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 Deleted messages are removed from writable mailbox.
448 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 (typ, [data]) = <instance>.close()
451 """
452 try:
453 typ, dat = self._simple_command('CLOSE')
454 finally:
455 self.state = 'AUTH'
456 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000457
458
Tim Peters07e99cb2001-01-14 23:47:14 +0000459 def copy(self, message_set, new_mailbox):
460 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
Tim Peters07e99cb2001-01-14 23:47:14 +0000462 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
463 """
464 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
466
Tim Peters07e99cb2001-01-14 23:47:14 +0000467 def create(self, mailbox):
468 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000469
Tim Peters07e99cb2001-01-14 23:47:14 +0000470 (typ, [data]) = <instance>.create(mailbox)
471 """
472 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000473
474
Tim Peters07e99cb2001-01-14 23:47:14 +0000475 def delete(self, mailbox):
476 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000477
Tim Peters07e99cb2001-01-14 23:47:14 +0000478 (typ, [data]) = <instance>.delete(mailbox)
479 """
480 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000481
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000482 def deleteacl(self, mailbox, who):
483 """Delete the ACLs (remove any rights) set for who on mailbox.
484
485 (typ, [data]) = <instance>.deleteacl(mailbox, who)
486 """
487 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000488
R David Murraya6429db2015-05-10 19:17:23 -0400489 def enable(self, capability):
490 """Send an RFC5161 enable string to the server.
491
492 (typ, [data]) = <intance>.enable(capability)
493 """
494 if 'ENABLE' not in self.capabilities:
495 raise IMAP4.error("Server does not support ENABLE")
496 typ, data = self._simple_command('ENABLE', capability)
497 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
498 self._mode_utf8()
499 return typ, data
500
Tim Peters07e99cb2001-01-14 23:47:14 +0000501 def expunge(self):
502 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000503
Tim Peters07e99cb2001-01-14 23:47:14 +0000504 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
Tim Peters07e99cb2001-01-14 23:47:14 +0000506 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 'data' is list of 'EXPUNGE'd message numbers in order received.
509 """
510 name = 'EXPUNGE'
511 typ, dat = self._simple_command(name)
512 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 def fetch(self, message_set, message_parts):
516 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000517
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000519
Tim Peters07e99cb2001-01-14 23:47:14 +0000520 'message_parts' should be a string of selected parts
521 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000522
Tim Peters07e99cb2001-01-14 23:47:14 +0000523 'data' are tuples of message part envelope and data.
524 """
525 name = 'FETCH'
526 typ, dat = self._simple_command(name, message_set, message_parts)
527 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000528
529
Piers Lauder15e5d532001-07-20 10:52:06 +0000530 def getacl(self, mailbox):
531 """Get the ACLs for a mailbox.
532
533 (typ, [data]) = <instance>.getacl(mailbox)
534 """
535 typ, dat = self._simple_command('GETACL', mailbox)
536 return self._untagged_response(typ, dat, 'ACL')
537
538
Piers Lauderd80ef022005-06-01 23:50:52 +0000539 def getannotation(self, mailbox, entry, attribute):
540 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
541 Retrieve ANNOTATIONs."""
542
543 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
544 return self._untagged_response(typ, dat, 'ANNOTATION')
545
546
Piers Lauder3fca2912002-06-17 07:07:20 +0000547 def getquota(self, root):
548 """Get the quota root's resource usage and limits.
549
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000550 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000551
552 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000553 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000554 typ, dat = self._simple_command('GETQUOTA', root)
555 return self._untagged_response(typ, dat, 'QUOTA')
556
557
558 def getquotaroot(self, mailbox):
559 """Get the list of quota roots for the named mailbox.
560
561 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000562 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000563 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000564 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
565 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000566 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000567
568
Tim Peters07e99cb2001-01-14 23:47:14 +0000569 def list(self, directory='""', pattern='*'):
570 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000571
Tim Peters07e99cb2001-01-14 23:47:14 +0000572 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000573
Tim Peters07e99cb2001-01-14 23:47:14 +0000574 'data' is list of LIST responses.
575 """
576 name = 'LIST'
577 typ, dat = self._simple_command(name, directory, pattern)
578 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000579
580
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 def login(self, user, password):
582 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000583
Tim Peters07e99cb2001-01-14 23:47:14 +0000584 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000585
Tim Peters07e99cb2001-01-14 23:47:14 +0000586 NB: 'password' will be quoted.
587 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
589 if typ != 'OK':
590 raise self.error(dat[-1])
591 self.state = 'AUTH'
592 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000593
594
Piers Laudere0273de2002-11-22 05:53:04 +0000595 def login_cram_md5(self, user, password):
596 """ Force use of CRAM-MD5 authentication.
597
598 (typ, [data]) = <instance>.login_cram_md5(user, password)
599 """
600 self.user, self.password = user, password
601 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
602
603
604 def _CRAM_MD5_AUTH(self, challenge):
605 """ Authobject to use with CRAM-MD5 authentication. """
606 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400607 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500608 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100609 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000610
611
Tim Peters07e99cb2001-01-14 23:47:14 +0000612 def logout(self):
613 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000614
Tim Peters07e99cb2001-01-14 23:47:14 +0000615 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000616
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 Returns server 'BYE' response.
618 """
619 self.state = 'LOGOUT'
620 try: typ, dat = self._simple_command('LOGOUT')
621 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000622 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000623 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000624 return 'BYE', self.untagged_responses['BYE']
625 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000626
627
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 def lsub(self, directory='""', pattern='*'):
629 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000630
Tim Peters07e99cb2001-01-14 23:47:14 +0000631 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000632
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 'data' are tuples of message part envelope and data.
634 """
635 name = 'LSUB'
636 typ, dat = self._simple_command(name, directory, pattern)
637 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000639 def myrights(self, mailbox):
640 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
641
642 (typ, [data]) = <instance>.myrights(mailbox)
643 """
644 typ,dat = self._simple_command('MYRIGHTS', mailbox)
645 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000646
Piers Lauder15e5d532001-07-20 10:52:06 +0000647 def namespace(self):
648 """ Returns IMAP namespaces ala rfc2342
649
650 (typ, [data, ...]) = <instance>.namespace()
651 """
652 name = 'NAMESPACE'
653 typ, dat = self._simple_command(name)
654 return self._untagged_response(typ, dat, name)
655
656
Tim Peters07e99cb2001-01-14 23:47:14 +0000657 def noop(self):
658 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000659
Piers Laudere0273de2002-11-22 05:53:04 +0000660 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 """
662 if __debug__:
663 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000664 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000665 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000666
667
Tim Peters07e99cb2001-01-14 23:47:14 +0000668 def partial(self, message_num, message_part, start, length):
669 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000670
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000672
Tim Peters07e99cb2001-01-14 23:47:14 +0000673 'data' is tuple of message part envelope and data.
674 """
675 name = 'PARTIAL'
676 typ, dat = self._simple_command(name, message_num, message_part, start, length)
677 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000678
679
Piers Laudere0273de2002-11-22 05:53:04 +0000680 def proxyauth(self, user):
681 """Assume authentication as "user".
682
683 Allows an authorised administrator to proxy into any user's
684 mailbox.
685
686 (typ, [data]) = <instance>.proxyauth(user)
687 """
688
689 name = 'PROXYAUTH'
690 return self._simple_command('PROXYAUTH', user)
691
692
Tim Peters07e99cb2001-01-14 23:47:14 +0000693 def rename(self, oldmailbox, newmailbox):
694 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000695
Piers Laudere0273de2002-11-22 05:53:04 +0000696 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000697 """
698 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000699
700
Tim Peters07e99cb2001-01-14 23:47:14 +0000701 def search(self, charset, *criteria):
702 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000703
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000704 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000705
Tim Peters07e99cb2001-01-14 23:47:14 +0000706 'data' is space separated list of matching message numbers.
R David Murraya6429db2015-05-10 19:17:23 -0400707 If UTF8 is enabled, charset MUST be None.
Tim Peters07e99cb2001-01-14 23:47:14 +0000708 """
709 name = 'SEARCH'
710 if charset:
R David Murraya6429db2015-05-10 19:17:23 -0400711 if self.utf8_enabled:
712 raise IMAP4.error("Non-None charset not valid in UTF8 mode")
Guido van Rossum68468eb2003-02-27 20:14:51 +0000713 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000714 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000715 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000717
718
Piers Lauder14f39402005-08-31 10:46:29 +0000719 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000721
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000723
Piers Lauder14f39402005-08-31 10:46:29 +0000724 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000725
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000727
728 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
729 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000731 self.untagged_responses = {} # Flush old responses.
732 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000733 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 name = 'EXAMINE'
735 else:
736 name = 'SELECT'
737 typ, dat = self._simple_command(name, mailbox)
738 if typ != 'OK':
739 self.state = 'AUTH' # Might have been 'SELECTED'
740 return typ, dat
741 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000742 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 and not readonly:
744 if __debug__:
745 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000746 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 raise self.readonly('%s is not writable' % mailbox)
748 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000749
750
Piers Lauder15e5d532001-07-20 10:52:06 +0000751 def setacl(self, mailbox, who, what):
752 """Set a mailbox acl.
753
Piers Lauderf167dc32004-03-25 00:12:21 +0000754 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000755 """
756 return self._simple_command('SETACL', mailbox, who, what)
757
758
Piers Lauderd80ef022005-06-01 23:50:52 +0000759 def setannotation(self, *args):
760 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
761 Set ANNOTATIONs."""
762
763 typ, dat = self._simple_command('SETANNOTATION', *args)
764 return self._untagged_response(typ, dat, 'ANNOTATION')
765
766
Piers Lauder3fca2912002-06-17 07:07:20 +0000767 def setquota(self, root, limits):
768 """Set the quota root's resource limits.
769
770 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000771 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000772 typ, dat = self._simple_command('SETQUOTA', root, limits)
773 return self._untagged_response(typ, dat, 'QUOTA')
774
775
Piers Lauder15e5d532001-07-20 10:52:06 +0000776 def sort(self, sort_criteria, charset, *search_criteria):
777 """IMAP4rev1 extension SORT command.
778
779 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
780 """
781 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000782 #if not name in self.capabilities: # Let the server decide!
783 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000784 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000785 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000786 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000787 return self._untagged_response(typ, dat, name)
788
789
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000790 def starttls(self, ssl_context=None):
791 name = 'STARTTLS'
792 if not HAVE_SSL:
793 raise self.error('SSL support missing')
794 if self._tls_established:
795 raise self.abort('TLS session already established')
796 if name not in self.capabilities:
797 raise self.abort('TLS not supported by server')
798 # Generate a default SSL context if none was passed.
799 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100800 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000801 typ, dat = self._simple_command(name)
802 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100803 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600804 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000805 self.file = self.sock.makefile('rb')
806 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000807 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000808 else:
809 raise self.error("Couldn't establish TLS session")
810 return self._untagged_response(typ, dat, name)
811
812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 def status(self, mailbox, names):
814 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 (typ, [data]) = <instance>.status(mailbox, names)
817 """
818 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000819 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000820 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 typ, dat = self._simple_command(name, mailbox, names)
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 store(self, message_set, command, flags):
826 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 (typ, [data]) = <instance>.store(message_set, command, flags)
829 """
830 if (flags[0],flags[-1]) != ('(',')'):
831 flags = '(%s)' % flags # Avoid quoting the flags
832 typ, dat = self._simple_command('STORE', message_set, command, flags)
833 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000834
835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 def subscribe(self, mailbox):
837 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 (typ, [data]) = <instance>.subscribe(mailbox)
840 """
841 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000842
843
Martin v. Löwisd8921372003-11-10 06:44:44 +0000844 def thread(self, threading_algorithm, charset, *search_criteria):
845 """IMAPrev1 extension THREAD command.
846
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000847 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000848 """
849 name = 'THREAD'
850 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
851 return self._untagged_response(typ, dat, name)
852
853
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 def uid(self, command, *args):
855 """Execute "command arg ..." with messages identified by UID,
856 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000857
Tim Peters07e99cb2001-01-14 23:47:14 +0000858 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000859
Tim Peters07e99cb2001-01-14 23:47:14 +0000860 Returns response appropriate to 'command'.
861 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000862 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000863 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 raise self.error("Unknown IMAP4 UID command: %s" % command)
865 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000866 raise self.error("command %s illegal in state %s, "
867 "only allowed in states %s" %
868 (command, self.state,
869 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000871 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000872 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000873 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 else:
875 name = 'FETCH'
876 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 def unsubscribe(self, mailbox):
880 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 (typ, [data]) = <instance>.unsubscribe(mailbox)
883 """
884 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 def xatom(self, name, *args):
888 """Allow simple extension commands
889 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000890
Piers Lauder15e5d532001-07-20 10:52:06 +0000891 Assumes command is legal in current state.
892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000894
895 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000897 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000898 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000899 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000900 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000901 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000902 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
904
905
Tim Peters07e99cb2001-01-14 23:47:14 +0000906 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
908
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000910 if dat is None:
911 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 ur = self.untagged_responses
913 if __debug__:
914 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000915 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000917 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 ur[typ].append(dat)
919 else:
920 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000921
922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 def _check_bye(self):
924 bye = self.untagged_responses.get('BYE')
925 if bye:
R David Murraya6429db2015-05-10 19:17:23 -0400926 raise self.abort(bye[-1].decode(self._encoding, 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000927
928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 if self.state not in Commands[name]:
932 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000933 raise self.error("command %s illegal in state %s, "
934 "only allowed in states %s" %
935 (name, self.state,
936 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000937
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000939 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000941
Raymond Hettinger54f02222002-06-01 14:18:47 +0000942 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 and not self.is_readonly:
944 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 tag = self._new_tag()
R David Murraya6429db2015-05-10 19:17:23 -0400947 name = bytes(name, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000948 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 for arg in args:
950 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000951 if isinstance(arg, str):
R David Murraya6429db2015-05-10 19:17:23 -0400952 arg = bytes(arg, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000953 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 literal = self.literal
956 if literal is not None:
957 self.literal = None
958 if type(literal) is type(self._command):
959 literator = literal
960 else:
961 literator = None
R David Murraya6429db2015-05-10 19:17:23 -0400962 data = data + bytes(' {%s}' % len(literal), self._encoding)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000963
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 if __debug__:
965 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000966 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000968 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000969
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000971 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200972 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 if literal is None:
976 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 while 1:
979 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 while self._get_response():
982 if self.tagged_commands[tag]: # BAD/NO?
983 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 if literator:
988 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 if __debug__:
991 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000992 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000995 self.send(literal)
996 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200997 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 if not literator:
1001 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001004
1005
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +00001007 # BYE is expected after LOGOUT
1008 if name != 'LOGOUT':
1009 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 try:
1011 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +00001012 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001014 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001015 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +00001016 if name != 'LOGOUT':
1017 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 if typ == 'BAD':
1019 raise self.error('%s command error: %s %s' % (name, typ, data))
1020 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
1022
Antoine Pitroudbe75192010-11-16 17:55:26 +00001023 def _get_capabilities(self):
1024 typ, dat = self.capability()
1025 if dat == [None]:
1026 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001027 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001028 dat = dat.upper()
1029 self.capabilities = tuple(dat.split())
1030
1031
Tim Peters07e99cb2001-01-14 23:47:14 +00001032 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 # Read response and store.
1035 #
1036 # Returns None for continuation responses,
1037 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001038
Tim Peters07e99cb2001-01-14 23:47:14 +00001039 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001040
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 if self._match(self.tagre, resp):
1044 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001045 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001046 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001049 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 dat = self.mo.group('data')
1051 self.tagged_commands[tag] = (typ, [dat])
1052 else:
1053 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001054
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001056
Tim Peters07e99cb2001-01-14 23:47:14 +00001057 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001058 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001060
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 if self.mo is None:
1062 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001063
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 if self._match(Continuation, resp):
1065 self.continuation_response = self.mo.group('data')
1066 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001067
R David Murraya6429db2015-05-10 19:17:23 -04001068 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001069
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001071 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001073 if dat is None: dat = b'' # Null untagged response
1074 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001075
Tim Peters07e99cb2001-01-14 23:47:14 +00001076 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001077
R David Murraya6429db2015-05-10 19:17:23 -04001078 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001081
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001082 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 if __debug__:
1084 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001085 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001086 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001087
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001089
Tim Peters07e99cb2001-01-14 23:47:14 +00001090 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001093
Tim Peters07e99cb2001-01-14 23:47:14 +00001094 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001095
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001097
Tim Peters07e99cb2001-01-14 23:47:14 +00001098 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001099
Tim Peters07e99cb2001-01-14 23:47:14 +00001100 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001101 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001102 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001103 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001104
Tim Peters07e99cb2001-01-14 23:47:14 +00001105 if __debug__:
1106 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001107 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001108
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001110
1111
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001113
Tim Peters07e99cb2001-01-14 23:47:14 +00001114 while 1:
1115 result = self.tagged_commands[tag]
1116 if result is not None:
1117 del self.tagged_commands[tag]
1118 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001119
R David Murray95ff7232014-02-07 13:44:57 -05001120 # If we've seen a BYE at this point, the socket will be
1121 # closed, so report the BYE now.
1122
1123 self._check_bye()
1124
Tim Peters07e99cb2001-01-14 23:47:14 +00001125 # Some have reported "unexpected response" exceptions.
1126 # Note that ignoring them here causes loops.
1127 # Instead, send me details of the unexpected response and
1128 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001129
Tim Peters07e99cb2001-01-14 23:47:14 +00001130 try:
1131 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001132 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 if __debug__:
1134 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001135 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001136 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001137
1138
Tim Peters07e99cb2001-01-14 23:47:14 +00001139 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001140
Piers Lauder15e5d532001-07-20 10:52:06 +00001141 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001142 if not line:
1143 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001144
Tim Peters07e99cb2001-01-14 23:47:14 +00001145 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001146 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001147 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001148
Tim Peters07e99cb2001-01-14 23:47:14 +00001149 line = line[:-2]
1150 if __debug__:
1151 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001152 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001153 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001154 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001155 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001156
1157
Tim Peters07e99cb2001-01-14 23:47:14 +00001158 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001159
Tim Peters07e99cb2001-01-14 23:47:14 +00001160 # Run compiled regular expression match method on 's'.
1161 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001162
Tim Peters07e99cb2001-01-14 23:47:14 +00001163 self.mo = cre.match(s)
1164 if __debug__:
1165 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001166 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001167 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001168
1169
Tim Peters07e99cb2001-01-14 23:47:14 +00001170 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001171
R David Murraya6429db2015-05-10 19:17:23 -04001172 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001173 self.tagnum = self.tagnum + 1
1174 self.tagged_commands[tag] = None
1175 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001176
1177
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001179
Antoine Pitroub1436f12010-11-09 22:55:55 +00001180 arg = arg.replace('\\', '\\\\')
1181 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001182
Antoine Pitroub1436f12010-11-09 22:55:55 +00001183 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001184
1185
Tim Peters07e99cb2001-01-14 23:47:14 +00001186 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001187
Guido van Rossum68468eb2003-02-27 20:14:51 +00001188 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001189
1190
Tim Peters07e99cb2001-01-14 23:47:14 +00001191 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001192 if typ == 'NO':
1193 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001194 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001195 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001196 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001197 if __debug__:
1198 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001199 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001200 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001201
1202
Piers Lauderf2d7d152002-02-22 01:15:17 +00001203 if __debug__:
1204
1205 def _mesg(self, s, secs=None):
1206 if secs is None:
1207 secs = time.time()
1208 tm = time.strftime('%M:%S', time.localtime(secs))
1209 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1210 sys.stderr.flush()
1211
1212 def _dump_ur(self, dict):
1213 # Dump untagged responses (in `dict').
1214 l = dict.items()
1215 if not l: return
1216 t = '\n\t\t'
1217 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1218 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1219
1220 def _log(self, line):
1221 # Keep log of last `_cmd_log_len' interactions for debugging.
1222 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1223 self._cmd_log_idx += 1
1224 if self._cmd_log_idx >= self._cmd_log_len:
1225 self._cmd_log_idx = 0
1226
1227 def print_log(self):
1228 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1229 i, n = self._cmd_log_idx, self._cmd_log_len
1230 while n:
1231 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001232 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001233 except:
1234 pass
1235 i += 1
1236 if i >= self._cmd_log_len:
1237 i = 0
1238 n -= 1
1239
1240
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001241if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001242
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001243 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001244
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001245 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001246
Antoine Pitrou08728162011-05-06 18:49:52 +02001247 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001248
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001249 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001250 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001251 keyfile - PEM formatted file that contains your private key (default: None);
1252 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001253 ssl_context - a SSLContext object that contains your certificate chain
1254 and private key (default: None)
1255 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001256 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001257
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001258 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001259 """
Piers Laudera4f83132002-03-08 01:53:24 +00001260
1261
R David Murraya6429db2015-05-10 19:17:23 -04001262 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
1263 certfile=None, ssl_context=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001264 if ssl_context is not None and keyfile is not None:
1265 raise ValueError("ssl_context and keyfile arguments are mutually "
1266 "exclusive")
1267 if ssl_context is not None and certfile is not None:
1268 raise ValueError("ssl_context and certfile arguments are mutually "
1269 "exclusive")
Christian Heimesd0486372016-09-10 23:23:33 +02001270 if keyfile is not None or certfile is not None:
1271 import warnings
1272 warnings.warn("keyfile and certfile are deprecated, use a"
1273 "custom ssl_context instead", DeprecationWarning, 2)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001274 self.keyfile = keyfile
1275 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001276 if ssl_context is None:
1277 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1278 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001279 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001280 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001281
Christian Heimesfb5faf02008-11-05 19:39:50 +00001282 def _create_socket(self):
1283 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001284 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001285 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001286
Christian Heimesfb5faf02008-11-05 19:39:50 +00001287 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001288 """Setup connection to remote server on "host:port".
1289 (default: localhost:standard IMAP4 SSL port).
1290 This connection will be used by the routines:
1291 read, readline, send, shutdown.
1292 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001293 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001294
1295 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001296
1297
Piers Laudere0273de2002-11-22 05:53:04 +00001298class IMAP4_stream(IMAP4):
1299
1300 """IMAP4 client class over a stream
1301
1302 Instantiate with: IMAP4_stream(command)
1303
R David Murraya6429db2015-05-10 19:17:23 -04001304 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001305
1306 for more documentation see the docstring of the parent class IMAP4.
1307 """
1308
1309
1310 def __init__(self, command):
1311 self.command = command
1312 IMAP4.__init__(self)
1313
1314
1315 def open(self, host = None, port = None):
1316 """Setup a stream connection.
1317 This connection will be used by the routines:
1318 read, readline, send, shutdown.
1319 """
1320 self.host = None # For compatibility with parent class
1321 self.port = None
1322 self.sock = None
1323 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001324 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001325 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001326 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1327 shell=True, close_fds=True)
1328 self.writefile = self.process.stdin
1329 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001330
1331 def read(self, size):
1332 """Read 'size' bytes from remote."""
1333 return self.readfile.read(size)
1334
1335
1336 def readline(self):
1337 """Read line from remote."""
1338 return self.readfile.readline()
1339
1340
1341 def send(self, data):
1342 """Send data to remote."""
1343 self.writefile.write(data)
1344 self.writefile.flush()
1345
1346
1347 def shutdown(self):
1348 """Close I/O established in "open"."""
1349 self.readfile.close()
1350 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001351 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001352
1353
1354
Guido van Rossumeda960a1998-06-18 14:24:28 +00001355class _Authenticator:
1356
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 """Private class to provide en/decoding
1358 for base64-based authentication conversation.
1359 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 def __init__(self, mechinst):
1362 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001363
Tim Peters07e99cb2001-01-14 23:47:14 +00001364 def process(self, data):
1365 ret = self.mech(self.decode(data))
1366 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001367 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001368 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001369
Tim Peters07e99cb2001-01-14 23:47:14 +00001370 def encode(self, inp):
1371 #
1372 # Invoke binascii.b2a_base64 iteratively with
1373 # short even length buffers, strip the trailing
1374 # line feed from the result and append. "Even"
1375 # means a number that factors to both 6 and 8,
1376 # so when it gets to the end of the 8-bit input
1377 # there's no partial 6-bit output.
1378 #
R David Murray774a39f2013-02-19 12:17:31 -05001379 oup = b''
1380 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001381 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001382 while inp:
1383 if len(inp) > 48:
1384 t = inp[:48]
1385 inp = inp[48:]
1386 else:
1387 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001388 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001389 e = binascii.b2a_base64(t)
1390 if e:
1391 oup = oup + e[:-1]
1392 return oup
1393
1394 def decode(self, inp):
1395 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001396 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 return binascii.a2b_base64(inp)
1398
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001399Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1400Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001401
1402def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001403 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001404
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001405 Return corresponding local time. The return value is a
1406 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001407 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001408
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 mo = InternalDate.match(resp)
1410 if not mo:
1411 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 mon = Mon2num[mo.group('mon')]
1414 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001415
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001416 day = int(mo.group('day'))
1417 year = int(mo.group('year'))
1418 hour = int(mo.group('hour'))
1419 min = int(mo.group('min'))
1420 sec = int(mo.group('sec'))
1421 zoneh = int(mo.group('zoneh'))
1422 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001425
Tim Peters07e99cb2001-01-14 23:47:14 +00001426 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001427 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001431 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001433 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001434
1435
1436
1437def Int2AP(num):
1438
Tim Peters07e99cb2001-01-14 23:47:14 +00001439 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001440
Christian Heimesfb5faf02008-11-05 19:39:50 +00001441 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001442 num = int(abs(num))
1443 while num:
1444 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001445 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001446 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001447
1448
1449
1450def ParseFlags(resp):
1451
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001453
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 mo = Flags.match(resp)
1455 if not mo:
1456 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001457
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001458 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001459
1460
1461def Time2Internaldate(date_time):
1462
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001463 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001464
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001465 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001466 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001467 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001468 representing local time, an instance of time.struct_time (as
1469 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001470 double-quoted string. In the last case, it is assumed to already
1471 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001472 """
Fred Drakedb519202002-01-05 17:17:09 +00001473 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001474 dt = datetime.fromtimestamp(date_time,
1475 timezone.utc).astimezone()
1476 elif isinstance(date_time, tuple):
1477 try:
1478 gmtoff = date_time.tm_gmtoff
1479 except AttributeError:
1480 if time.daylight:
1481 dst = date_time[8]
1482 if dst == -1:
1483 dst = time.localtime(time.mktime(date_time))[8]
1484 gmtoff = -(time.timezone, time.altzone)[dst]
1485 else:
1486 gmtoff = -time.timezone
1487 delta = timedelta(seconds=gmtoff)
1488 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1489 elif isinstance(date_time, datetime):
1490 if date_time.tzinfo is None:
1491 raise ValueError("date_time must be aware")
1492 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001493 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001494 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001495 else:
1496 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001497 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1498 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001499
1500
1501
Guido van Rossum8c062211999-12-13 23:27:45 +00001502if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001503
Piers Laudere0273de2002-11-22 05:53:04 +00001504 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1505 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1506 # to test the IMAP4_stream class
1507
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001508 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001509
Tim Peters07e99cb2001-01-14 23:47:14 +00001510 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001511 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001512 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001513 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001514
Piers Laudere0273de2002-11-22 05:53:04 +00001515 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001516 for opt,val in optlist:
1517 if opt == '-d':
1518 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001519 elif opt == '-s':
1520 stream_command = val
1521 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001522
Tim Peters07e99cb2001-01-14 23:47:14 +00001523 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001524
Tim Peters07e99cb2001-01-14 23:47:14 +00001525 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001526
Tim Peters07e99cb2001-01-14 23:47:14 +00001527 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001528 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001529
Piers Lauder47404ff2003-04-29 23:40:59 +00001530 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 +00001531 test_seq1 = (
1532 ('login', (USER, PASSWD)),
1533 ('create', ('/tmp/xxx 1',)),
1534 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1535 ('CREATE', ('/tmp/yyz 2',)),
1536 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1537 ('list', ('/tmp', 'yy*')),
1538 ('select', ('/tmp/yyz 2',)),
1539 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001540 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
R David Murray44b548d2016-09-08 13:59:53 -04001541 ('store', ('1', 'FLAGS', r'(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001542 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001543 ('expunge', ()),
1544 ('recent', ()),
1545 ('close', ()),
1546 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001547
Tim Peters07e99cb2001-01-14 23:47:14 +00001548 test_seq2 = (
1549 ('select', ()),
1550 ('response',('UIDVALIDITY',)),
1551 ('uid', ('SEARCH', 'ALL')),
1552 ('response', ('EXISTS',)),
1553 ('append', (None, None, None, test_mesg)),
1554 ('recent', ()),
1555 ('logout', ()),
1556 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001557
Tim Peters07e99cb2001-01-14 23:47:14 +00001558 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001559 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001560 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001561 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001562 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001563 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001564
Tim Peters07e99cb2001-01-14 23:47:14 +00001565 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001566 if stream_command:
1567 M = IMAP4_stream(stream_command)
1568 else:
1569 M = IMAP4(host)
1570 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001571 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001572 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001573 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001574
Tim Peters07e99cb2001-01-14 23:47:14 +00001575 for cmd,args in test_seq1:
1576 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001577
Tim Peters07e99cb2001-01-14 23:47:14 +00001578 for ml in run('list', ('/tmp/', 'yy%')):
1579 mo = re.match(r'.*"([^"]+)"$', ml)
1580 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001581 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001582 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001583
Tim Peters07e99cb2001-01-14 23:47:14 +00001584 for cmd,args in test_seq2:
1585 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001586
Tim Peters07e99cb2001-01-14 23:47:14 +00001587 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1588 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001589
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001590 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001591 if not uid: continue
1592 run('uid', ('FETCH', '%s' % uid[-1],
1593 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001594
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001595 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001596
Tim Peters07e99cb2001-01-14 23:47:14 +00001597 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001598 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001599
Tim Peters07e99cb2001-01-14 23:47:14 +00001600 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001601 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001602If you would like to see debugging output,
1603try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001604''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001605
Tim Peters07e99cb2001-01-14 23:47:14 +00001606 raise