blob: e1cece0b283f2be0870b7a45bbc3217da92bb15b [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)
Victor Stinner83a2c282017-05-15 17:33:45 +0200321 except OSError as exc:
322 # The server might already have closed the connection.
323 # On Windows, this may result in WSAEINVAL (error 10022):
324 # An invalid operation was attempted.
325 if (exc.errno != errno.ENOTCONN
326 and getattr(exc, 'winerror', 0) != 10022):
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000327 raise
328 finally:
329 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000330
331
332 def socket(self):
333 """Return socket instance used to connect to IMAP4 server.
334
335 socket = <instance>.socket()
336 """
337 return self.sock
338
339
340
341 # Utility methods
342
343
Tim Peters07e99cb2001-01-14 23:47:14 +0000344 def recent(self):
345 """Return most recent 'RECENT' responses if any exist,
346 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000347
Tim Peters07e99cb2001-01-14 23:47:14 +0000348 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000349
Tim Peters07e99cb2001-01-14 23:47:14 +0000350 'data' is None if no new messages,
351 else list of RECENT responses, most recent last.
352 """
353 name = 'RECENT'
354 typ, dat = self._untagged_response('OK', [None], name)
355 if dat[-1]:
356 return typ, dat
357 typ, dat = self.noop() # Prod server for response
358 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000359
360
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 def response(self, code):
362 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000363
Tim Peters07e99cb2001-01-14 23:47:14 +0000364 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000365
Tim Peters07e99cb2001-01-14 23:47:14 +0000366 (code, [data]) = <instance>.response(code)
367 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000368 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000369
370
Guido van Rossum26367a01998-09-28 15:34:46 +0000371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000373
374
Tim Peters07e99cb2001-01-14 23:47:14 +0000375 def append(self, mailbox, flags, date_time, message):
376 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000377
Tim Peters07e99cb2001-01-14 23:47:14 +0000378 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 All args except `message' can be None.
381 """
382 name = 'APPEND'
383 if not mailbox:
384 mailbox = 'INBOX'
385 if flags:
386 if (flags[0],flags[-1]) != ('(',')'):
387 flags = '(%s)' % flags
388 else:
389 flags = None
390 if date_time:
391 date_time = Time2Internaldate(date_time)
392 else:
393 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400394 literal = MapCRLF.sub(CRLF, message)
395 if self.utf8_enabled:
396 literal = b'UTF8 (' + literal + b')'
397 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
400
Tim Peters07e99cb2001-01-14 23:47:14 +0000401 def authenticate(self, mechanism, authobject):
402 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 'mechanism' specifies which authentication mechanism is to
405 be used - it must appear in <instance>.capabilities in the
406 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000407
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000409
Tim Peters07e99cb2001-01-14 23:47:14 +0000410 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000411
R David Murray774a39f2013-02-19 12:17:31 -0500412 It will be called to process server continuation responses; the
413 response argument it is passed will be a bytes. It should return bytes
414 data that will be base64 encoded and sent to the server. It should
415 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000417 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000418 # XXX: shouldn't this code be removed, not commented out?
419 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000420 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000421 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000422 self.literal = _Authenticator(authobject).process
423 typ, dat = self._simple_command('AUTHENTICATE', mech)
424 if typ != 'OK':
R David Murrayb079c072016-12-24 21:32:26 -0500425 raise self.error(dat[-1].decode('utf-8', 'replace'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000426 self.state = 'AUTH'
427 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000428
429
Piers Lauderd80ef022005-06-01 23:50:52 +0000430 def capability(self):
431 """(typ, [data]) = <instance>.capability()
432 Fetch capabilities list from server."""
433
434 name = 'CAPABILITY'
435 typ, dat = self._simple_command(name)
436 return self._untagged_response(typ, dat, name)
437
438
Tim Peters07e99cb2001-01-14 23:47:14 +0000439 def check(self):
440 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Tim Peters07e99cb2001-01-14 23:47:14 +0000442 (typ, [data]) = <instance>.check()
443 """
444 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000445
446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 def close(self):
448 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 Deleted messages are removed from writable mailbox.
451 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000452
Tim Peters07e99cb2001-01-14 23:47:14 +0000453 (typ, [data]) = <instance>.close()
454 """
455 try:
456 typ, dat = self._simple_command('CLOSE')
457 finally:
458 self.state = 'AUTH'
459 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000460
461
Tim Peters07e99cb2001-01-14 23:47:14 +0000462 def copy(self, message_set, new_mailbox):
463 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000464
Tim Peters07e99cb2001-01-14 23:47:14 +0000465 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
466 """
467 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000468
469
Tim Peters07e99cb2001-01-14 23:47:14 +0000470 def create(self, mailbox):
471 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000472
Tim Peters07e99cb2001-01-14 23:47:14 +0000473 (typ, [data]) = <instance>.create(mailbox)
474 """
475 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000476
477
Tim Peters07e99cb2001-01-14 23:47:14 +0000478 def delete(self, mailbox):
479 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000480
Tim Peters07e99cb2001-01-14 23:47:14 +0000481 (typ, [data]) = <instance>.delete(mailbox)
482 """
483 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000484
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000485 def deleteacl(self, mailbox, who):
486 """Delete the ACLs (remove any rights) set for who on mailbox.
487
488 (typ, [data]) = <instance>.deleteacl(mailbox, who)
489 """
490 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000491
R David Murraya6429db2015-05-10 19:17:23 -0400492 def enable(self, capability):
493 """Send an RFC5161 enable string to the server.
494
495 (typ, [data]) = <intance>.enable(capability)
496 """
497 if 'ENABLE' not in self.capabilities:
498 raise IMAP4.error("Server does not support ENABLE")
499 typ, data = self._simple_command('ENABLE', capability)
500 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
501 self._mode_utf8()
502 return typ, data
503
Tim Peters07e99cb2001-01-14 23:47:14 +0000504 def expunge(self):
505 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000506
Tim Peters07e99cb2001-01-14 23:47:14 +0000507 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000508
Tim Peters07e99cb2001-01-14 23:47:14 +0000509 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000510
Tim Peters07e99cb2001-01-14 23:47:14 +0000511 'data' is list of 'EXPUNGE'd message numbers in order received.
512 """
513 name = 'EXPUNGE'
514 typ, dat = self._simple_command(name)
515 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000516
517
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 def fetch(self, message_set, message_parts):
519 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000520
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000522
Tim Peters07e99cb2001-01-14 23:47:14 +0000523 'message_parts' should be a string of selected parts
524 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000525
Tim Peters07e99cb2001-01-14 23:47:14 +0000526 'data' are tuples of message part envelope and data.
527 """
528 name = 'FETCH'
529 typ, dat = self._simple_command(name, message_set, message_parts)
530 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000531
532
Piers Lauder15e5d532001-07-20 10:52:06 +0000533 def getacl(self, mailbox):
534 """Get the ACLs for a mailbox.
535
536 (typ, [data]) = <instance>.getacl(mailbox)
537 """
538 typ, dat = self._simple_command('GETACL', mailbox)
539 return self._untagged_response(typ, dat, 'ACL')
540
541
Piers Lauderd80ef022005-06-01 23:50:52 +0000542 def getannotation(self, mailbox, entry, attribute):
543 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
544 Retrieve ANNOTATIONs."""
545
546 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
547 return self._untagged_response(typ, dat, 'ANNOTATION')
548
549
Piers Lauder3fca2912002-06-17 07:07:20 +0000550 def getquota(self, root):
551 """Get the quota root's resource usage and limits.
552
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000553 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000554
555 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000556 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000557 typ, dat = self._simple_command('GETQUOTA', root)
558 return self._untagged_response(typ, dat, 'QUOTA')
559
560
561 def getquotaroot(self, mailbox):
562 """Get the list of quota roots for the named mailbox.
563
564 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000565 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000566 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000567 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
568 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000569 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000570
571
Tim Peters07e99cb2001-01-14 23:47:14 +0000572 def list(self, directory='""', pattern='*'):
573 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000574
Tim Peters07e99cb2001-01-14 23:47:14 +0000575 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000576
Tim Peters07e99cb2001-01-14 23:47:14 +0000577 'data' is list of LIST responses.
578 """
579 name = 'LIST'
580 typ, dat = self._simple_command(name, directory, pattern)
581 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000582
583
Tim Peters07e99cb2001-01-14 23:47:14 +0000584 def login(self, user, password):
585 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000586
Tim Peters07e99cb2001-01-14 23:47:14 +0000587 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000588
Tim Peters07e99cb2001-01-14 23:47:14 +0000589 NB: 'password' will be quoted.
590 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
592 if typ != 'OK':
593 raise self.error(dat[-1])
594 self.state = 'AUTH'
595 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000596
597
Piers Laudere0273de2002-11-22 05:53:04 +0000598 def login_cram_md5(self, user, password):
599 """ Force use of CRAM-MD5 authentication.
600
601 (typ, [data]) = <instance>.login_cram_md5(user, password)
602 """
603 self.user, self.password = user, password
604 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
605
606
607 def _CRAM_MD5_AUTH(self, challenge):
608 """ Authobject to use with CRAM-MD5 authentication. """
609 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400610 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500611 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100612 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000613
614
Tim Peters07e99cb2001-01-14 23:47:14 +0000615 def logout(self):
616 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000617
Tim Peters07e99cb2001-01-14 23:47:14 +0000618 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000619
Tim Peters07e99cb2001-01-14 23:47:14 +0000620 Returns server 'BYE' response.
621 """
622 self.state = 'LOGOUT'
623 try: typ, dat = self._simple_command('LOGOUT')
624 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000625 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000626 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000627 return 'BYE', self.untagged_responses['BYE']
628 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000629
630
Tim Peters07e99cb2001-01-14 23:47:14 +0000631 def lsub(self, directory='""', pattern='*'):
632 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000633
Tim Peters07e99cb2001-01-14 23:47:14 +0000634 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
Tim Peters07e99cb2001-01-14 23:47:14 +0000636 'data' are tuples of message part envelope and data.
637 """
638 name = 'LSUB'
639 typ, dat = self._simple_command(name, directory, pattern)
640 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000641
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000642 def myrights(self, mailbox):
643 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
644
645 (typ, [data]) = <instance>.myrights(mailbox)
646 """
647 typ,dat = self._simple_command('MYRIGHTS', mailbox)
648 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000649
Piers Lauder15e5d532001-07-20 10:52:06 +0000650 def namespace(self):
651 """ Returns IMAP namespaces ala rfc2342
652
653 (typ, [data, ...]) = <instance>.namespace()
654 """
655 name = 'NAMESPACE'
656 typ, dat = self._simple_command(name)
657 return self._untagged_response(typ, dat, name)
658
659
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 def noop(self):
661 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000662
Piers Laudere0273de2002-11-22 05:53:04 +0000663 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 """
665 if __debug__:
666 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000667 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000668 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000669
670
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 def partial(self, message_num, message_part, start, length):
672 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000673
Tim Peters07e99cb2001-01-14 23:47:14 +0000674 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000675
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 'data' is tuple of message part envelope and data.
677 """
678 name = 'PARTIAL'
679 typ, dat = self._simple_command(name, message_num, message_part, start, length)
680 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000681
682
Piers Laudere0273de2002-11-22 05:53:04 +0000683 def proxyauth(self, user):
684 """Assume authentication as "user".
685
686 Allows an authorised administrator to proxy into any user's
687 mailbox.
688
689 (typ, [data]) = <instance>.proxyauth(user)
690 """
691
692 name = 'PROXYAUTH'
693 return self._simple_command('PROXYAUTH', user)
694
695
Tim Peters07e99cb2001-01-14 23:47:14 +0000696 def rename(self, oldmailbox, newmailbox):
697 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000698
Piers Laudere0273de2002-11-22 05:53:04 +0000699 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000700 """
701 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000702
703
Tim Peters07e99cb2001-01-14 23:47:14 +0000704 def search(self, charset, *criteria):
705 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000706
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000707 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000708
Tim Peters07e99cb2001-01-14 23:47:14 +0000709 'data' is space separated list of matching message numbers.
R David Murraya6429db2015-05-10 19:17:23 -0400710 If UTF8 is enabled, charset MUST be None.
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 """
712 name = 'SEARCH'
713 if charset:
R David Murraya6429db2015-05-10 19:17:23 -0400714 if self.utf8_enabled:
715 raise IMAP4.error("Non-None charset not valid in UTF8 mode")
Guido van Rossum68468eb2003-02-27 20:14:51 +0000716 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000717 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000718 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000720
721
Piers Lauder14f39402005-08-31 10:46:29 +0000722 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000724
Tim Peters07e99cb2001-01-14 23:47:14 +0000725 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000726
Piers Lauder14f39402005-08-31 10:46:29 +0000727 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000728
Tim Peters07e99cb2001-01-14 23:47:14 +0000729 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000730
731 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
732 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 self.untagged_responses = {} # Flush old responses.
735 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000736 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 name = 'EXAMINE'
738 else:
739 name = 'SELECT'
740 typ, dat = self._simple_command(name, mailbox)
741 if typ != 'OK':
742 self.state = 'AUTH' # Might have been 'SELECTED'
743 return typ, dat
744 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000745 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000746 and not readonly:
747 if __debug__:
748 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000749 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 raise self.readonly('%s is not writable' % mailbox)
751 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000752
753
Piers Lauder15e5d532001-07-20 10:52:06 +0000754 def setacl(self, mailbox, who, what):
755 """Set a mailbox acl.
756
Piers Lauderf167dc32004-03-25 00:12:21 +0000757 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000758 """
759 return self._simple_command('SETACL', mailbox, who, what)
760
761
Piers Lauderd80ef022005-06-01 23:50:52 +0000762 def setannotation(self, *args):
763 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
764 Set ANNOTATIONs."""
765
766 typ, dat = self._simple_command('SETANNOTATION', *args)
767 return self._untagged_response(typ, dat, 'ANNOTATION')
768
769
Piers Lauder3fca2912002-06-17 07:07:20 +0000770 def setquota(self, root, limits):
771 """Set the quota root's resource limits.
772
773 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000774 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000775 typ, dat = self._simple_command('SETQUOTA', root, limits)
776 return self._untagged_response(typ, dat, 'QUOTA')
777
778
Piers Lauder15e5d532001-07-20 10:52:06 +0000779 def sort(self, sort_criteria, charset, *search_criteria):
780 """IMAP4rev1 extension SORT command.
781
782 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
783 """
784 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000785 #if not name in self.capabilities: # Let the server decide!
786 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000787 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000788 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000789 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000790 return self._untagged_response(typ, dat, name)
791
792
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000793 def starttls(self, ssl_context=None):
794 name = 'STARTTLS'
795 if not HAVE_SSL:
796 raise self.error('SSL support missing')
797 if self._tls_established:
798 raise self.abort('TLS session already established')
799 if name not in self.capabilities:
800 raise self.abort('TLS not supported by server')
801 # Generate a default SSL context if none was passed.
802 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100803 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000804 typ, dat = self._simple_command(name)
805 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100806 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600807 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000808 self.file = self.sock.makefile('rb')
809 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000810 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000811 else:
812 raise self.error("Couldn't establish TLS session")
813 return self._untagged_response(typ, dat, name)
814
815
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 def status(self, mailbox, names):
817 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 (typ, [data]) = <instance>.status(mailbox, names)
820 """
821 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000822 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000823 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 typ, dat = self._simple_command(name, mailbox, names)
825 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 def store(self, message_set, command, flags):
829 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000830
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 (typ, [data]) = <instance>.store(message_set, command, flags)
832 """
833 if (flags[0],flags[-1]) != ('(',')'):
834 flags = '(%s)' % flags # Avoid quoting the flags
835 typ, dat = self._simple_command('STORE', message_set, command, flags)
836 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000837
838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 def subscribe(self, mailbox):
840 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 (typ, [data]) = <instance>.subscribe(mailbox)
843 """
844 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000845
846
Martin v. Löwisd8921372003-11-10 06:44:44 +0000847 def thread(self, threading_algorithm, charset, *search_criteria):
848 """IMAPrev1 extension THREAD command.
849
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000850 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000851 """
852 name = 'THREAD'
853 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
854 return self._untagged_response(typ, dat, name)
855
856
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 def uid(self, command, *args):
858 """Execute "command arg ..." with messages identified by UID,
859 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 Returns response appropriate to 'command'.
864 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000865 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000866 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 raise self.error("Unknown IMAP4 UID command: %s" % command)
868 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000869 raise self.error("command %s illegal in state %s, "
870 "only allowed in states %s" %
871 (command, self.state,
872 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000874 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000875 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000876 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 else:
878 name = 'FETCH'
879 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000880
881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 def unsubscribe(self, mailbox):
883 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 (typ, [data]) = <instance>.unsubscribe(mailbox)
886 """
887 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000888
889
Tim Peters07e99cb2001-01-14 23:47:14 +0000890 def xatom(self, name, *args):
891 """Allow simple extension commands
892 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000893
Piers Lauder15e5d532001-07-20 10:52:06 +0000894 Assumes command is legal in current state.
895
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000897
898 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000900 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000901 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000902 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000903 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000904 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000905 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000906
907
908
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000910
911
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000913 if dat is None:
914 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 ur = self.untagged_responses
916 if __debug__:
917 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000918 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000920 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 ur[typ].append(dat)
922 else:
923 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
925
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 def _check_bye(self):
927 bye = self.untagged_responses.get('BYE')
928 if bye:
R David Murraya6429db2015-05-10 19:17:23 -0400929 raise self.abort(bye[-1].decode(self._encoding, 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000930
931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 if self.state not in Commands[name]:
935 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000936 raise self.error("command %s illegal in state %s, "
937 "only allowed in states %s" %
938 (name, self.state,
939 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000942 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000944
Raymond Hettinger54f02222002-06-01 14:18:47 +0000945 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 and not self.is_readonly:
947 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 tag = self._new_tag()
R David Murraya6429db2015-05-10 19:17:23 -0400950 name = bytes(name, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000951 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 for arg in args:
953 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000954 if isinstance(arg, str):
R David Murraya6429db2015-05-10 19:17:23 -0400955 arg = bytes(arg, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000956 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000957
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 literal = self.literal
959 if literal is not None:
960 self.literal = None
961 if type(literal) is type(self._command):
962 literator = literal
963 else:
964 literator = None
R David Murraya6429db2015-05-10 19:17:23 -0400965 data = data + bytes(' {%s}' % len(literal), self._encoding)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 if __debug__:
968 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000969 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000971 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000974 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200975 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 if literal is None:
979 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 while 1:
982 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000983
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 while self._get_response():
985 if self.tagged_commands[tag]: # BAD/NO?
986 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 if literator:
991 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 if __debug__:
994 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000995 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000998 self.send(literal)
999 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +02001000 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 if not literator:
1004 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001005
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001007
1008
Tim Peters07e99cb2001-01-14 23:47:14 +00001009 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +00001010 # BYE is expected after LOGOUT
1011 if name != 'LOGOUT':
1012 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 try:
1014 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +00001015 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001017 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +00001019 if name != 'LOGOUT':
1020 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 if typ == 'BAD':
1022 raise self.error('%s command error: %s %s' % (name, typ, data))
1023 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001024
1025
Antoine Pitroudbe75192010-11-16 17:55:26 +00001026 def _get_capabilities(self):
1027 typ, dat = self.capability()
1028 if dat == [None]:
1029 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001030 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001031 dat = dat.upper()
1032 self.capabilities = tuple(dat.split())
1033
1034
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001036
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 # Read response and store.
1038 #
1039 # Returns None for continuation responses,
1040 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001041
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001043
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001045
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 if self._match(self.tagre, resp):
1047 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001048 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001049 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001050
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001052 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 dat = self.mo.group('data')
1054 self.tagged_commands[tag] = (typ, [dat])
1055 else:
1056 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001061 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001062 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001063
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 if self.mo is None:
1065 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001066
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 if self._match(Continuation, resp):
1068 self.continuation_response = self.mo.group('data')
1069 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001070
R David Murraya6429db2015-05-10 19:17:23 -04001071 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001072
Tim Peters07e99cb2001-01-14 23:47:14 +00001073 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001074 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001076 if dat is None: dat = b'' # Null untagged response
1077 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001078
Tim Peters07e99cb2001-01-14 23:47:14 +00001079 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001080
R David Murraya6429db2015-05-10 19:17:23 -04001081 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001082
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001084
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001085 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001086 if __debug__:
1087 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001088 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001089 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001090
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001092
Tim Peters07e99cb2001-01-14 23:47:14 +00001093 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001094
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001096
Tim Peters07e99cb2001-01-14 23:47:14 +00001097 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001098
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001100
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001102
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001104 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001105 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001106 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001107
Tim Peters07e99cb2001-01-14 23:47:14 +00001108 if __debug__:
1109 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001110 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001111
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001113
1114
Tim Peters07e99cb2001-01-14 23:47:14 +00001115 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001116
Tim Peters07e99cb2001-01-14 23:47:14 +00001117 while 1:
1118 result = self.tagged_commands[tag]
1119 if result is not None:
1120 del self.tagged_commands[tag]
1121 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001122
R David Murray95ff7232014-02-07 13:44:57 -05001123 # If we've seen a BYE at this point, the socket will be
1124 # closed, so report the BYE now.
1125
1126 self._check_bye()
1127
Tim Peters07e99cb2001-01-14 23:47:14 +00001128 # Some have reported "unexpected response" exceptions.
1129 # Note that ignoring them here causes loops.
1130 # Instead, send me details of the unexpected response and
1131 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001132
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 try:
1134 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001135 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001136 if __debug__:
1137 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001138 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001139 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001140
1141
Tim Peters07e99cb2001-01-14 23:47:14 +00001142 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001143
Piers Lauder15e5d532001-07-20 10:52:06 +00001144 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001145 if not line:
1146 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001147
Tim Peters07e99cb2001-01-14 23:47:14 +00001148 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001149 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001150 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001151
Tim Peters07e99cb2001-01-14 23:47:14 +00001152 line = line[:-2]
1153 if __debug__:
1154 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001155 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001156 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001157 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001158 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001159
1160
Tim Peters07e99cb2001-01-14 23:47:14 +00001161 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001162
Tim Peters07e99cb2001-01-14 23:47:14 +00001163 # Run compiled regular expression match method on 's'.
1164 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001165
Tim Peters07e99cb2001-01-14 23:47:14 +00001166 self.mo = cre.match(s)
1167 if __debug__:
1168 if self.mo is not None and self.debug >= 5:
Serhiy Storchakaa4a30202017-11-28 22:54:42 +02001169 self._mesg("\tmatched %r => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001170 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001171
1172
Tim Peters07e99cb2001-01-14 23:47:14 +00001173 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001174
R David Murraya6429db2015-05-10 19:17:23 -04001175 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001176 self.tagnum = self.tagnum + 1
1177 self.tagged_commands[tag] = None
1178 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001179
1180
Tim Peters07e99cb2001-01-14 23:47:14 +00001181 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001182
Antoine Pitroub1436f12010-11-09 22:55:55 +00001183 arg = arg.replace('\\', '\\\\')
1184 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001185
Antoine Pitroub1436f12010-11-09 22:55:55 +00001186 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001187
1188
Tim Peters07e99cb2001-01-14 23:47:14 +00001189 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001190
Guido van Rossum68468eb2003-02-27 20:14:51 +00001191 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001192
1193
Tim Peters07e99cb2001-01-14 23:47:14 +00001194 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001195 if typ == 'NO':
1196 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001197 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001198 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001199 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001200 if __debug__:
1201 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001202 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001203 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001204
1205
Piers Lauderf2d7d152002-02-22 01:15:17 +00001206 if __debug__:
1207
1208 def _mesg(self, s, secs=None):
1209 if secs is None:
1210 secs = time.time()
1211 tm = time.strftime('%M:%S', time.localtime(secs))
1212 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1213 sys.stderr.flush()
1214
1215 def _dump_ur(self, dict):
1216 # Dump untagged responses (in `dict').
1217 l = dict.items()
1218 if not l: return
1219 t = '\n\t\t'
1220 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1221 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1222
1223 def _log(self, line):
1224 # Keep log of last `_cmd_log_len' interactions for debugging.
1225 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1226 self._cmd_log_idx += 1
1227 if self._cmd_log_idx >= self._cmd_log_len:
1228 self._cmd_log_idx = 0
1229
1230 def print_log(self):
1231 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1232 i, n = self._cmd_log_idx, self._cmd_log_len
1233 while n:
1234 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001235 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001236 except:
1237 pass
1238 i += 1
1239 if i >= self._cmd_log_len:
1240 i = 0
1241 n -= 1
1242
1243
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001244if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001245
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001246 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001247
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001248 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001249
Antoine Pitrou08728162011-05-06 18:49:52 +02001250 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001251
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001252 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001253 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001254 keyfile - PEM formatted file that contains your private key (default: None);
1255 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001256 ssl_context - a SSLContext object that contains your certificate chain
1257 and private key (default: None)
1258 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001259 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001260
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001261 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001262 """
Piers Laudera4f83132002-03-08 01:53:24 +00001263
1264
R David Murraya6429db2015-05-10 19:17:23 -04001265 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
1266 certfile=None, ssl_context=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001267 if ssl_context is not None and keyfile is not None:
1268 raise ValueError("ssl_context and keyfile arguments are mutually "
1269 "exclusive")
1270 if ssl_context is not None and certfile is not None:
1271 raise ValueError("ssl_context and certfile arguments are mutually "
1272 "exclusive")
Christian Heimesd0486372016-09-10 23:23:33 +02001273 if keyfile is not None or certfile is not None:
1274 import warnings
1275 warnings.warn("keyfile and certfile are deprecated, use a"
1276 "custom ssl_context instead", DeprecationWarning, 2)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001277 self.keyfile = keyfile
1278 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001279 if ssl_context is None:
1280 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1281 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001282 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001283 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001284
Christian Heimesfb5faf02008-11-05 19:39:50 +00001285 def _create_socket(self):
1286 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001287 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001288 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001289
Christian Heimesfb5faf02008-11-05 19:39:50 +00001290 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001291 """Setup connection to remote server on "host:port".
1292 (default: localhost:standard IMAP4 SSL port).
1293 This connection will be used by the routines:
1294 read, readline, send, shutdown.
1295 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001296 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001297
1298 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001299
1300
Piers Laudere0273de2002-11-22 05:53:04 +00001301class IMAP4_stream(IMAP4):
1302
1303 """IMAP4 client class over a stream
1304
1305 Instantiate with: IMAP4_stream(command)
1306
R David Murraya6429db2015-05-10 19:17:23 -04001307 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001308
1309 for more documentation see the docstring of the parent class IMAP4.
1310 """
1311
1312
1313 def __init__(self, command):
1314 self.command = command
1315 IMAP4.__init__(self)
1316
1317
1318 def open(self, host = None, port = None):
1319 """Setup a stream connection.
1320 This connection will be used by the routines:
1321 read, readline, send, shutdown.
1322 """
1323 self.host = None # For compatibility with parent class
1324 self.port = None
1325 self.sock = None
1326 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001327 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001328 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001329 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1330 shell=True, close_fds=True)
1331 self.writefile = self.process.stdin
1332 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001333
1334 def read(self, size):
1335 """Read 'size' bytes from remote."""
1336 return self.readfile.read(size)
1337
1338
1339 def readline(self):
1340 """Read line from remote."""
1341 return self.readfile.readline()
1342
1343
1344 def send(self, data):
1345 """Send data to remote."""
1346 self.writefile.write(data)
1347 self.writefile.flush()
1348
1349
1350 def shutdown(self):
1351 """Close I/O established in "open"."""
1352 self.readfile.close()
1353 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001354 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001355
1356
1357
Guido van Rossumeda960a1998-06-18 14:24:28 +00001358class _Authenticator:
1359
Tim Peters07e99cb2001-01-14 23:47:14 +00001360 """Private class to provide en/decoding
1361 for base64-based authentication conversation.
1362 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001363
Tim Peters07e99cb2001-01-14 23:47:14 +00001364 def __init__(self, mechinst):
1365 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001366
Tim Peters07e99cb2001-01-14 23:47:14 +00001367 def process(self, data):
1368 ret = self.mech(self.decode(data))
1369 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001370 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001371 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001372
Tim Peters07e99cb2001-01-14 23:47:14 +00001373 def encode(self, inp):
1374 #
1375 # Invoke binascii.b2a_base64 iteratively with
1376 # short even length buffers, strip the trailing
1377 # line feed from the result and append. "Even"
1378 # means a number that factors to both 6 and 8,
1379 # so when it gets to the end of the 8-bit input
1380 # there's no partial 6-bit output.
1381 #
R David Murray774a39f2013-02-19 12:17:31 -05001382 oup = b''
1383 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001384 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001385 while inp:
1386 if len(inp) > 48:
1387 t = inp[:48]
1388 inp = inp[48:]
1389 else:
1390 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001391 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001392 e = binascii.b2a_base64(t)
1393 if e:
1394 oup = oup + e[:-1]
1395 return oup
1396
1397 def decode(self, inp):
1398 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001399 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001400 return binascii.a2b_base64(inp)
1401
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001402Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1403Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001404
1405def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001406 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001407
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001408 Return corresponding local time. The return value is a
1409 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001410 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001411
Tim Peters07e99cb2001-01-14 23:47:14 +00001412 mo = InternalDate.match(resp)
1413 if not mo:
1414 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001415
Tim Peters07e99cb2001-01-14 23:47:14 +00001416 mon = Mon2num[mo.group('mon')]
1417 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001418
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001419 day = int(mo.group('day'))
1420 year = int(mo.group('year'))
1421 hour = int(mo.group('hour'))
1422 min = int(mo.group('min'))
1423 sec = int(mo.group('sec'))
1424 zoneh = int(mo.group('zoneh'))
1425 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001426
Tim Peters07e99cb2001-01-14 23:47:14 +00001427 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001428
Tim Peters07e99cb2001-01-14 23:47:14 +00001429 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001430 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001431 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001434 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001435
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001436 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001437
1438
1439
1440def Int2AP(num):
1441
Tim Peters07e99cb2001-01-14 23:47:14 +00001442 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001443
Christian Heimesfb5faf02008-11-05 19:39:50 +00001444 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 num = int(abs(num))
1446 while num:
1447 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001448 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001449 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001450
1451
1452
1453def ParseFlags(resp):
1454
Tim Peters07e99cb2001-01-14 23:47:14 +00001455 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001456
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 mo = Flags.match(resp)
1458 if not mo:
1459 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001460
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001461 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001462
1463
1464def Time2Internaldate(date_time):
1465
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001466 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001467
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001468 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001469 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001470 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001471 representing local time, an instance of time.struct_time (as
1472 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001473 double-quoted string. In the last case, it is assumed to already
1474 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001475 """
Fred Drakedb519202002-01-05 17:17:09 +00001476 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001477 dt = datetime.fromtimestamp(date_time,
1478 timezone.utc).astimezone()
1479 elif isinstance(date_time, tuple):
1480 try:
1481 gmtoff = date_time.tm_gmtoff
1482 except AttributeError:
1483 if time.daylight:
1484 dst = date_time[8]
1485 if dst == -1:
1486 dst = time.localtime(time.mktime(date_time))[8]
1487 gmtoff = -(time.timezone, time.altzone)[dst]
1488 else:
1489 gmtoff = -time.timezone
1490 delta = timedelta(seconds=gmtoff)
1491 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1492 elif isinstance(date_time, datetime):
1493 if date_time.tzinfo is None:
1494 raise ValueError("date_time must be aware")
1495 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001496 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001497 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001498 else:
1499 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001500 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1501 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001502
1503
1504
Guido van Rossum8c062211999-12-13 23:27:45 +00001505if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001506
Piers Laudere0273de2002-11-22 05:53:04 +00001507 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1508 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1509 # to test the IMAP4_stream class
1510
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001511 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001512
Tim Peters07e99cb2001-01-14 23:47:14 +00001513 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001514 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001515 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001516 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001517
Piers Laudere0273de2002-11-22 05:53:04 +00001518 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001519 for opt,val in optlist:
1520 if opt == '-d':
1521 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001522 elif opt == '-s':
1523 stream_command = val
1524 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001525
Tim Peters07e99cb2001-01-14 23:47:14 +00001526 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001527
Tim Peters07e99cb2001-01-14 23:47:14 +00001528 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001529
Tim Peters07e99cb2001-01-14 23:47:14 +00001530 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001531 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001532
Piers Lauder47404ff2003-04-29 23:40:59 +00001533 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 +00001534 test_seq1 = (
1535 ('login', (USER, PASSWD)),
1536 ('create', ('/tmp/xxx 1',)),
1537 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1538 ('CREATE', ('/tmp/yyz 2',)),
1539 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1540 ('list', ('/tmp', 'yy*')),
1541 ('select', ('/tmp/yyz 2',)),
1542 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001543 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
R David Murray44b548d2016-09-08 13:59:53 -04001544 ('store', ('1', 'FLAGS', r'(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001545 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001546 ('expunge', ()),
1547 ('recent', ()),
1548 ('close', ()),
1549 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001550
Tim Peters07e99cb2001-01-14 23:47:14 +00001551 test_seq2 = (
1552 ('select', ()),
1553 ('response',('UIDVALIDITY',)),
1554 ('uid', ('SEARCH', 'ALL')),
1555 ('response', ('EXISTS',)),
1556 ('append', (None, None, None, test_mesg)),
1557 ('recent', ()),
1558 ('logout', ()),
1559 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001560
Tim Peters07e99cb2001-01-14 23:47:14 +00001561 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001562 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001563 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001564 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001565 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001566 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001567
Tim Peters07e99cb2001-01-14 23:47:14 +00001568 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001569 if stream_command:
1570 M = IMAP4_stream(stream_command)
1571 else:
1572 M = IMAP4(host)
1573 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001574 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001575 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001576 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001577
Tim Peters07e99cb2001-01-14 23:47:14 +00001578 for cmd,args in test_seq1:
1579 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001580
Tim Peters07e99cb2001-01-14 23:47:14 +00001581 for ml in run('list', ('/tmp/', 'yy%')):
1582 mo = re.match(r'.*"([^"]+)"$', ml)
1583 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001584 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001585 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001586
Tim Peters07e99cb2001-01-14 23:47:14 +00001587 for cmd,args in test_seq2:
1588 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001589
Tim Peters07e99cb2001-01-14 23:47:14 +00001590 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1591 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001592
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001593 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001594 if not uid: continue
1595 run('uid', ('FETCH', '%s' % uid[-1],
1596 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001597
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001598 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001599
Tim Peters07e99cb2001-01-14 23:47:14 +00001600 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001601 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001602
Tim Peters07e99cb2001-01-14 23:47:14 +00001603 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001604 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001605If you would like to see debugging output,
1606try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001607''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001608
Tim Peters07e99cb2001-01-14 23:47:14 +00001609 raise