blob: 341ee25ae965143a63922690a49fb28c13fb5caa [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'),
Matěj Ceplcaa331d2018-07-23 13:28:54 +020082 'MOVE': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000083 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000084 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000085 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000086 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000087 'RENAME': ('AUTH', 'SELECTED'),
88 'SEARCH': ('SELECTED',),
89 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000090 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000091 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000092 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000093 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000094 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000095 'STATUS': ('AUTH', 'SELECTED'),
96 'STORE': ('SELECTED',),
97 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000098 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000099 'UID': ('SELECTED',),
100 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
101 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102
Tim Peters07e99cb2001-01-14 23:47:14 +0000103# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104
Christian Heimesfb5faf02008-11-05 19:39:50 +0000105Continuation = re.compile(br'\+( (?P<data>.*))?')
106Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
107InternalDate = re.compile(br'.*INTERNALDATE "'
108 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
109 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
110 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
111 br'"')
R David Murraya6429db2015-05-10 19:17:23 -0400112# Literal is no longer used; kept for backward compatibility.
Christian Heimesfb5faf02008-11-05 19:39:50 +0000113Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
114MapCRLF = re.compile(br'\r\n|\r|\n')
R David Murray317f64f2016-01-02 17:18:34 -0500115# We no longer exclude the ']' character from the data portion of the response
116# code, even though it violates the RFC. Popular IMAP servers such as Gmail
117# allow flags with ']', and there are programs (including imaplib!) that can
118# produce them. The problem with this is if the 'text' portion of the response
119# includes a ']' we'll parse the response wrong (which is the point of the RFC
120# restriction). However, that seems less likely to be a problem in practice
121# than being unable to correctly parse flags that include ']' chars, which
122# was reported as a real-world problem in issue #21815.
123Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>.*))?\]')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000124Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
R David Murraya6429db2015-05-10 19:17:23 -0400125# Untagged_status is no longer used; kept for backward compatibility
Antoine Pitroufd036452008-08-19 17:56:33 +0000126Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000127 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
R David Murraya6429db2015-05-10 19:17:23 -0400128# We compile these in _mode_xxx.
129_Literal = br'.*{(?P<size>\d+)}$'
130_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000131
132
133
134class IMAP4:
135
R David Murray44b548d2016-09-08 13:59:53 -0400136 r"""IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000137
Tim Peters07e99cb2001-01-14 23:47:14 +0000138 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000139
Tim Peters07e99cb2001-01-14 23:47:14 +0000140 host - host's name (default: localhost);
141 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000142
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 All IMAP4rev1 commands are supported by methods of the same
144 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000145
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 All arguments to commands are converted to strings, except for
147 AUTHENTICATE, and the last argument to APPEND which is passed as
148 an IMAP4 literal. If necessary (the string contains any
149 non-printing characters or white-space and isn't enclosed with
150 either parentheses or double quotes) each string is quoted.
151 However, the 'password' argument to the LOGIN command is always
152 quoted. If you want to avoid having an argument string quoted
153 (eg: the 'flags' argument to STORE) then enclose the string in
154 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000155
Tim Peters07e99cb2001-01-14 23:47:14 +0000156 Each command returns a tuple: (type, [data, ...]) where 'type'
157 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000158 tagged response, or untagged results from command. Each 'data'
159 is either a string, or a tuple. If a tuple, then the first part
160 is the header of the response, and the second part contains
161 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000162
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 Errors raise the exception class <instance>.error("<reason>").
164 IMAP4 server errors raise <instance>.abort("<reason>"),
165 which is a sub-class of 'error'. Mailbox status changes
166 from READ-WRITE to READ-ONLY raise the exception class
167 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000168
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 "error" exceptions imply a program error.
170 "abort" exceptions imply the connection should be reset, and
171 the command re-tried.
172 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000173
Piers Lauderd80ef022005-06-01 23:50:52 +0000174 Note: to use this module, you must read the RFCs pertaining to the
175 IMAP4 protocol, as the semantics of the arguments to each IMAP4
176 command are left to the invoker, not to mention the results. Also,
177 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000178 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000179
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 class error(Exception): pass # Logical errors - debug required
181 class abort(error): pass # Service errors - close and retry
182 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
R David Murraya6429db2015-05-10 19:17:23 -0400184 def __init__(self, host='', port=IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 self.debug = Debug
186 self.state = 'LOGOUT'
187 self.literal = None # A literal argument to a command
188 self.tagged_commands = {} # Tagged commands awaiting response
189 self.untagged_responses = {} # {typ: [data, ...], ...}
190 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000191 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000192 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000193 self._tls_established = False
R David Murraya6429db2015-05-10 19:17:23 -0400194 self._mode_ascii()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000195
Tim Peters07e99cb2001-01-14 23:47:14 +0000196 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000197
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000199
Victor Stinner33e649c2011-01-05 23:01:37 +0000200 try:
201 self._connect()
202 except Exception:
203 try:
204 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200205 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000206 pass
207 raise
208
R David Murraya6429db2015-05-10 19:17:23 -0400209 def _mode_ascii(self):
210 self.utf8_enabled = False
211 self._encoding = 'ascii'
212 self.Literal = re.compile(_Literal, re.ASCII)
213 self.Untagged_status = re.compile(_Untagged_status, re.ASCII)
214
215
216 def _mode_utf8(self):
217 self.utf8_enabled = True
218 self._encoding = 'utf-8'
219 self.Literal = re.compile(_Literal)
220 self.Untagged_status = re.compile(_Untagged_status)
221
Victor Stinner33e649c2011-01-05 23:01:37 +0000222
223 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000224 # Create unique tag for this session,
225 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000226
Piers Lauder2dfc1682005-07-05 04:20:07 +0000227 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000228 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000229 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000230 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000231
Tim Peters07e99cb2001-01-14 23:47:14 +0000232 # Get server welcome message,
233 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000234
Tim Peters07e99cb2001-01-14 23:47:14 +0000235 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000236 self._cmd_log_len = 10
237 self._cmd_log_idx = 0
238 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000239 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000240 self._mesg('imaplib version %s' % __version__)
241 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000242
Tim Peters07e99cb2001-01-14 23:47:14 +0000243 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000244 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000245 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000246 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000247 self.state = 'NONAUTH'
248 else:
249 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000250
Antoine Pitroudbe75192010-11-16 17:55:26 +0000251 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000252 if __debug__:
253 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000254 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000255
Tim Peters07e99cb2001-01-14 23:47:14 +0000256 for version in AllowedVersions:
257 if not version in self.capabilities:
258 continue
259 self.PROTOCOL_VERSION = version
260 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000261
Tim Peters07e99cb2001-01-14 23:47:14 +0000262 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000263
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000264
Tim Peters07e99cb2001-01-14 23:47:14 +0000265 def __getattr__(self, attr):
266 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000267 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000268 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000269 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000270
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300271 def __enter__(self):
272 return self
273
274 def __exit__(self, *args):
Victor Stinner74125a62019-04-15 18:23:20 +0200275 if self.state == "LOGOUT":
276 return
277
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300278 try:
279 self.logout()
280 except OSError:
281 pass
Guido van Rossum26367a01998-09-28 15:34:46 +0000282
283
Piers Lauder15e5d532001-07-20 10:52:06 +0000284 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000285
286
Christian Heimesfb5faf02008-11-05 19:39:50 +0000287 def _create_socket(self):
Berker Peksage4dcbbd2018-08-07 05:12:18 +0300288 # Default value of IMAP4.host is '', but socket.getaddrinfo()
289 # (which is used by socket.create_connection()) expects None
290 # as a default value for host.
291 host = None if not self.host else self.host
292 return socket.create_connection((host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000293
Piers Lauderf97b2d72002-06-05 22:31:57 +0000294 def open(self, host = '', port = IMAP4_PORT):
295 """Setup connection to remote server on "host:port"
296 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000297 This connection will be used by the routines:
298 read, readline, send, shutdown.
299 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000300 self.host = host
301 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000302 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000303 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000304
305
Piers Lauder15e5d532001-07-20 10:52:06 +0000306 def read(self, size):
307 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200308 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000309
310
311 def readline(self):
312 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100313 line = self.file.readline(_MAXLINE + 1)
314 if len(line) > _MAXLINE:
315 raise self.error("got more than %d bytes" % _MAXLINE)
316 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000317
318
319 def send(self, data):
320 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000321 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000322
Piers Lauderf2d7d152002-02-22 01:15:17 +0000323
Piers Lauder15e5d532001-07-20 10:52:06 +0000324 def shutdown(self):
325 """Close I/O established in "open"."""
326 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000327 try:
328 self.sock.shutdown(socket.SHUT_RDWR)
Victor Stinner83a2c282017-05-15 17:33:45 +0200329 except OSError as exc:
330 # The server might already have closed the connection.
331 # On Windows, this may result in WSAEINVAL (error 10022):
332 # An invalid operation was attempted.
333 if (exc.errno != errno.ENOTCONN
334 and getattr(exc, 'winerror', 0) != 10022):
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000335 raise
336 finally:
337 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000338
339
340 def socket(self):
341 """Return socket instance used to connect to IMAP4 server.
342
343 socket = <instance>.socket()
344 """
345 return self.sock
346
347
348
349 # Utility methods
350
351
Tim Peters07e99cb2001-01-14 23:47:14 +0000352 def recent(self):
353 """Return most recent 'RECENT' responses if any exist,
354 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000355
Tim Peters07e99cb2001-01-14 23:47:14 +0000356 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000357
Tim Peters07e99cb2001-01-14 23:47:14 +0000358 'data' is None if no new messages,
359 else list of RECENT responses, most recent last.
360 """
361 name = 'RECENT'
362 typ, dat = self._untagged_response('OK', [None], name)
363 if dat[-1]:
364 return typ, dat
365 typ, dat = self.noop() # Prod server for response
366 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000367
368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 def response(self, code):
370 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 (code, [data]) = <instance>.response(code)
375 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000376 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000377
378
Guido van Rossum26367a01998-09-28 15:34:46 +0000379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000381
382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 def append(self, mailbox, flags, date_time, message):
384 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
Tim Peters07e99cb2001-01-14 23:47:14 +0000386 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 All args except `message' can be None.
389 """
390 name = 'APPEND'
391 if not mailbox:
392 mailbox = 'INBOX'
393 if flags:
394 if (flags[0],flags[-1]) != ('(',')'):
395 flags = '(%s)' % flags
396 else:
397 flags = None
398 if date_time:
399 date_time = Time2Internaldate(date_time)
400 else:
401 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400402 literal = MapCRLF.sub(CRLF, message)
403 if self.utf8_enabled:
404 literal = b'UTF8 (' + literal + b')'
405 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000406 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
408
Tim Peters07e99cb2001-01-14 23:47:14 +0000409 def authenticate(self, mechanism, authobject):
410 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 'mechanism' specifies which authentication mechanism is to
413 be used - it must appear in <instance>.capabilities in the
414 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000419
R David Murray774a39f2013-02-19 12:17:31 -0500420 It will be called to process server continuation responses; the
421 response argument it is passed will be a bytes. It should return bytes
422 data that will be base64 encoded and sent to the server. It should
423 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000424 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000425 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000426 # XXX: shouldn't this code be removed, not commented out?
427 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000428 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000429 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 self.literal = _Authenticator(authobject).process
431 typ, dat = self._simple_command('AUTHENTICATE', mech)
432 if typ != 'OK':
R David Murrayb079c072016-12-24 21:32:26 -0500433 raise self.error(dat[-1].decode('utf-8', 'replace'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000434 self.state = 'AUTH'
435 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000436
437
Piers Lauderd80ef022005-06-01 23:50:52 +0000438 def capability(self):
439 """(typ, [data]) = <instance>.capability()
440 Fetch capabilities list from server."""
441
442 name = 'CAPABILITY'
443 typ, dat = self._simple_command(name)
444 return self._untagged_response(typ, dat, name)
445
446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 def check(self):
448 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 (typ, [data]) = <instance>.check()
451 """
452 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000453
454
Tim Peters07e99cb2001-01-14 23:47:14 +0000455 def close(self):
456 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000457
Tim Peters07e99cb2001-01-14 23:47:14 +0000458 Deleted messages are removed from writable mailbox.
459 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000460
Tim Peters07e99cb2001-01-14 23:47:14 +0000461 (typ, [data]) = <instance>.close()
462 """
463 try:
464 typ, dat = self._simple_command('CLOSE')
465 finally:
466 self.state = 'AUTH'
467 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000468
469
Tim Peters07e99cb2001-01-14 23:47:14 +0000470 def copy(self, message_set, new_mailbox):
471 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000472
Tim Peters07e99cb2001-01-14 23:47:14 +0000473 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
474 """
475 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000476
477
Tim Peters07e99cb2001-01-14 23:47:14 +0000478 def create(self, mailbox):
479 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000480
Tim Peters07e99cb2001-01-14 23:47:14 +0000481 (typ, [data]) = <instance>.create(mailbox)
482 """
483 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000484
485
Tim Peters07e99cb2001-01-14 23:47:14 +0000486 def delete(self, mailbox):
487 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000488
Tim Peters07e99cb2001-01-14 23:47:14 +0000489 (typ, [data]) = <instance>.delete(mailbox)
490 """
491 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000492
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000493 def deleteacl(self, mailbox, who):
494 """Delete the ACLs (remove any rights) set for who on mailbox.
495
496 (typ, [data]) = <instance>.deleteacl(mailbox, who)
497 """
498 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000499
R David Murraya6429db2015-05-10 19:17:23 -0400500 def enable(self, capability):
501 """Send an RFC5161 enable string to the server.
502
503 (typ, [data]) = <intance>.enable(capability)
504 """
505 if 'ENABLE' not in self.capabilities:
506 raise IMAP4.error("Server does not support ENABLE")
507 typ, data = self._simple_command('ENABLE', capability)
508 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
509 self._mode_utf8()
510 return typ, data
511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 def expunge(self):
513 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000516
Tim Peters07e99cb2001-01-14 23:47:14 +0000517 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000518
Tim Peters07e99cb2001-01-14 23:47:14 +0000519 'data' is list of 'EXPUNGE'd message numbers in order received.
520 """
521 name = 'EXPUNGE'
522 typ, dat = self._simple_command(name)
523 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000524
525
Tim Peters07e99cb2001-01-14 23:47:14 +0000526 def fetch(self, message_set, message_parts):
527 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000528
Tim Peters07e99cb2001-01-14 23:47:14 +0000529 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 'message_parts' should be a string of selected parts
532 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000533
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 'data' are tuples of message part envelope and data.
535 """
536 name = 'FETCH'
537 typ, dat = self._simple_command(name, message_set, message_parts)
538 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000539
540
Piers Lauder15e5d532001-07-20 10:52:06 +0000541 def getacl(self, mailbox):
542 """Get the ACLs for a mailbox.
543
544 (typ, [data]) = <instance>.getacl(mailbox)
545 """
546 typ, dat = self._simple_command('GETACL', mailbox)
547 return self._untagged_response(typ, dat, 'ACL')
548
549
Piers Lauderd80ef022005-06-01 23:50:52 +0000550 def getannotation(self, mailbox, entry, attribute):
551 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
552 Retrieve ANNOTATIONs."""
553
554 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
555 return self._untagged_response(typ, dat, 'ANNOTATION')
556
557
Piers Lauder3fca2912002-06-17 07:07:20 +0000558 def getquota(self, root):
559 """Get the quota root's resource usage and limits.
560
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000561 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000562
563 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000564 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000565 typ, dat = self._simple_command('GETQUOTA', root)
566 return self._untagged_response(typ, dat, 'QUOTA')
567
568
569 def getquotaroot(self, mailbox):
570 """Get the list of quota roots for the named mailbox.
571
572 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000573 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000574 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000575 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
576 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000577 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000578
579
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 def list(self, directory='""', pattern='*'):
581 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000582
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000584
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 'data' is list of LIST responses.
586 """
587 name = 'LIST'
588 typ, dat = self._simple_command(name, directory, pattern)
589 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000590
591
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 def login(self, user, password):
593 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000596
Tim Peters07e99cb2001-01-14 23:47:14 +0000597 NB: 'password' will be quoted.
598 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000599 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
600 if typ != 'OK':
601 raise self.error(dat[-1])
602 self.state = 'AUTH'
603 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000604
605
Piers Laudere0273de2002-11-22 05:53:04 +0000606 def login_cram_md5(self, user, password):
607 """ Force use of CRAM-MD5 authentication.
608
609 (typ, [data]) = <instance>.login_cram_md5(user, password)
610 """
611 self.user, self.password = user, password
612 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
613
614
615 def _CRAM_MD5_AUTH(self, challenge):
616 """ Authobject to use with CRAM-MD5 authentication. """
617 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400618 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500619 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100620 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000621
622
Tim Peters07e99cb2001-01-14 23:47:14 +0000623 def logout(self):
624 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000625
Tim Peters07e99cb2001-01-14 23:47:14 +0000626 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 Returns server 'BYE' response.
629 """
630 self.state = 'LOGOUT'
Victor Stinner74125a62019-04-15 18:23:20 +0200631 typ, dat = self._simple_command('LOGOUT')
Piers Lauder15e5d532001-07-20 10:52:06 +0000632 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000634
635
Tim Peters07e99cb2001-01-14 23:47:14 +0000636 def lsub(self, directory='""', pattern='*'):
637 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000640
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 'data' are tuples of message part envelope and data.
642 """
643 name = 'LSUB'
644 typ, dat = self._simple_command(name, directory, pattern)
645 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000646
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000647 def myrights(self, mailbox):
648 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
649
650 (typ, [data]) = <instance>.myrights(mailbox)
651 """
652 typ,dat = self._simple_command('MYRIGHTS', mailbox)
653 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
Piers Lauder15e5d532001-07-20 10:52:06 +0000655 def namespace(self):
656 """ Returns IMAP namespaces ala rfc2342
657
658 (typ, [data, ...]) = <instance>.namespace()
659 """
660 name = 'NAMESPACE'
661 typ, dat = self._simple_command(name)
662 return self._untagged_response(typ, dat, name)
663
664
Tim Peters07e99cb2001-01-14 23:47:14 +0000665 def noop(self):
666 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000667
Piers Laudere0273de2002-11-22 05:53:04 +0000668 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000669 """
670 if __debug__:
671 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000672 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000673 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000674
675
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 def partial(self, message_num, message_part, start, length):
677 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000678
Tim Peters07e99cb2001-01-14 23:47:14 +0000679 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000680
Tim Peters07e99cb2001-01-14 23:47:14 +0000681 'data' is tuple of message part envelope and data.
682 """
683 name = 'PARTIAL'
684 typ, dat = self._simple_command(name, message_num, message_part, start, length)
685 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000686
687
Piers Laudere0273de2002-11-22 05:53:04 +0000688 def proxyauth(self, user):
689 """Assume authentication as "user".
690
691 Allows an authorised administrator to proxy into any user's
692 mailbox.
693
694 (typ, [data]) = <instance>.proxyauth(user)
695 """
696
697 name = 'PROXYAUTH'
698 return self._simple_command('PROXYAUTH', user)
699
700
Tim Peters07e99cb2001-01-14 23:47:14 +0000701 def rename(self, oldmailbox, newmailbox):
702 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000703
Piers Laudere0273de2002-11-22 05:53:04 +0000704 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000705 """
706 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000707
708
Tim Peters07e99cb2001-01-14 23:47:14 +0000709 def search(self, charset, *criteria):
710 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000711
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000712 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000713
Tim Peters07e99cb2001-01-14 23:47:14 +0000714 'data' is space separated list of matching message numbers.
R David Murraya6429db2015-05-10 19:17:23 -0400715 If UTF8 is enabled, charset MUST be None.
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 """
717 name = 'SEARCH'
718 if charset:
R David Murraya6429db2015-05-10 19:17:23 -0400719 if self.utf8_enabled:
720 raise IMAP4.error("Non-None charset not valid in UTF8 mode")
Guido van Rossum68468eb2003-02-27 20:14:51 +0000721 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000722 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000723 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000724 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000725
726
Piers Lauder14f39402005-08-31 10:46:29 +0000727 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000728 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000731
Piers Lauder14f39402005-08-31 10:46:29 +0000732 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000733
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000735
736 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
737 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000738 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000739 self.untagged_responses = {} # Flush old responses.
740 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000741 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000742 name = 'EXAMINE'
743 else:
744 name = 'SELECT'
745 typ, dat = self._simple_command(name, mailbox)
746 if typ != 'OK':
747 self.state = 'AUTH' # Might have been 'SELECTED'
748 return typ, dat
749 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000750 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000751 and not readonly:
752 if __debug__:
753 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000754 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 raise self.readonly('%s is not writable' % mailbox)
756 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000757
758
Piers Lauder15e5d532001-07-20 10:52:06 +0000759 def setacl(self, mailbox, who, what):
760 """Set a mailbox acl.
761
Piers Lauderf167dc32004-03-25 00:12:21 +0000762 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000763 """
764 return self._simple_command('SETACL', mailbox, who, what)
765
766
Piers Lauderd80ef022005-06-01 23:50:52 +0000767 def setannotation(self, *args):
768 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
769 Set ANNOTATIONs."""
770
771 typ, dat = self._simple_command('SETANNOTATION', *args)
772 return self._untagged_response(typ, dat, 'ANNOTATION')
773
774
Piers Lauder3fca2912002-06-17 07:07:20 +0000775 def setquota(self, root, limits):
776 """Set the quota root's resource limits.
777
778 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000779 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000780 typ, dat = self._simple_command('SETQUOTA', root, limits)
781 return self._untagged_response(typ, dat, 'QUOTA')
782
783
Piers Lauder15e5d532001-07-20 10:52:06 +0000784 def sort(self, sort_criteria, charset, *search_criteria):
785 """IMAP4rev1 extension SORT command.
786
787 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
788 """
789 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000790 #if not name in self.capabilities: # Let the server decide!
791 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000792 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000793 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000794 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000795 return self._untagged_response(typ, dat, name)
796
797
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000798 def starttls(self, ssl_context=None):
799 name = 'STARTTLS'
800 if not HAVE_SSL:
801 raise self.error('SSL support missing')
802 if self._tls_established:
803 raise self.abort('TLS session already established')
804 if name not in self.capabilities:
805 raise self.abort('TLS not supported by server')
806 # Generate a default SSL context if none was passed.
807 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100808 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000809 typ, dat = self._simple_command(name)
810 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100811 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600812 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000813 self.file = self.sock.makefile('rb')
814 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000815 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000816 else:
817 raise self.error("Couldn't establish TLS session")
818 return self._untagged_response(typ, dat, name)
819
820
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 def status(self, mailbox, names):
822 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 (typ, [data]) = <instance>.status(mailbox, names)
825 """
826 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000827 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000828 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000829 typ, dat = self._simple_command(name, mailbox, names)
830 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 def store(self, message_set, command, flags):
834 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 (typ, [data]) = <instance>.store(message_set, command, flags)
837 """
838 if (flags[0],flags[-1]) != ('(',')'):
839 flags = '(%s)' % flags # Avoid quoting the flags
840 typ, dat = self._simple_command('STORE', message_set, command, flags)
841 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000842
843
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 def subscribe(self, mailbox):
845 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000846
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 (typ, [data]) = <instance>.subscribe(mailbox)
848 """
849 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000850
851
Martin v. Löwisd8921372003-11-10 06:44:44 +0000852 def thread(self, threading_algorithm, charset, *search_criteria):
853 """IMAPrev1 extension THREAD command.
854
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000855 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000856 """
857 name = 'THREAD'
858 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
859 return self._untagged_response(typ, dat, name)
860
861
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 def uid(self, command, *args):
863 """Execute "command arg ..." with messages identified by UID,
864 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000865
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 Returns response appropriate to 'command'.
869 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000870 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000871 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 raise self.error("Unknown IMAP4 UID command: %s" % command)
873 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000874 raise self.error("command %s illegal in state %s, "
875 "only allowed in states %s" %
876 (command, self.state,
877 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000879 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000880 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000881 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 else:
883 name = 'FETCH'
884 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 def unsubscribe(self, mailbox):
888 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000889
Tim Peters07e99cb2001-01-14 23:47:14 +0000890 (typ, [data]) = <instance>.unsubscribe(mailbox)
891 """
892 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000893
894
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 def xatom(self, name, *args):
896 """Allow simple extension commands
897 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000898
Piers Lauder15e5d532001-07-20 10:52:06 +0000899 Assumes command is legal in current state.
900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000902
903 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000905 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000906 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000907 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000908 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000909 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000910 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000911
912
913
Tim Peters07e99cb2001-01-14 23:47:14 +0000914 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
916
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000918 if dat is None:
919 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 ur = self.untagged_responses
921 if __debug__:
922 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000923 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000925 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 ur[typ].append(dat)
927 else:
928 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 def _check_bye(self):
932 bye = self.untagged_responses.get('BYE')
933 if bye:
R David Murraya6429db2015-05-10 19:17:23 -0400934 raise self.abort(bye[-1].decode(self._encoding, 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000935
936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000938
Tim Peters07e99cb2001-01-14 23:47:14 +0000939 if self.state not in Commands[name]:
940 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000941 raise self.error("command %s illegal in state %s, "
942 "only allowed in states %s" %
943 (name, self.state,
944 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000947 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000949
Raymond Hettinger54f02222002-06-01 14:18:47 +0000950 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000951 and not self.is_readonly:
952 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000953
Tim Peters07e99cb2001-01-14 23:47:14 +0000954 tag = self._new_tag()
R David Murraya6429db2015-05-10 19:17:23 -0400955 name = bytes(name, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000956 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 for arg in args:
958 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000959 if isinstance(arg, str):
R David Murraya6429db2015-05-10 19:17:23 -0400960 arg = bytes(arg, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000961 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 literal = self.literal
964 if literal is not None:
965 self.literal = None
966 if type(literal) is type(self._command):
967 literator = literal
968 else:
969 literator = None
R David Murraya6429db2015-05-10 19:17:23 -0400970 data = data + bytes(' {%s}' % len(literal), self._encoding)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000971
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 if __debug__:
973 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000974 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000976 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000979 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200980 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 if literal is None:
984 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000985
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 while 1:
987 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 while self._get_response():
990 if self.tagged_commands[tag]: # BAD/NO?
991 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 if literator:
996 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 if __debug__:
999 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001000 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +00001001
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 try:
Piers Lauder15e5d532001-07-20 10:52:06 +00001003 self.send(literal)
1004 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +02001005 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001007
Tim Peters07e99cb2001-01-14 23:47:14 +00001008 if not literator:
1009 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001010
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001012
1013
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 def _command_complete(self, name, tag):
Victor Stinner74125a62019-04-15 18:23:20 +02001015 logout = (name == 'LOGOUT')
Antoine Pitroudac47912010-11-10 00:18:40 +00001016 # BYE is expected after LOGOUT
Victor Stinner74125a62019-04-15 18:23:20 +02001017 if not logout:
Antoine Pitroudac47912010-11-10 00:18:40 +00001018 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 try:
Victor Stinner74125a62019-04-15 18:23:20 +02001020 typ, data = self._get_tagged_response(tag, expect_bye=logout)
Guido van Rossumb940e112007-01-10 16:19:56 +00001021 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001023 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 raise self.error('command: %s => %s' % (name, val))
Victor Stinner74125a62019-04-15 18:23:20 +02001025 if not logout:
Antoine Pitroudac47912010-11-10 00:18:40 +00001026 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001027 if typ == 'BAD':
1028 raise self.error('%s command error: %s %s' % (name, typ, data))
1029 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001030
1031
Antoine Pitroudbe75192010-11-16 17:55:26 +00001032 def _get_capabilities(self):
1033 typ, dat = self.capability()
1034 if dat == [None]:
1035 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001036 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001037 dat = dat.upper()
1038 self.capabilities = tuple(dat.split())
1039
1040
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 # Read response and store.
1044 #
1045 # Returns None for continuation responses,
1046 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 if self._match(self.tagre, resp):
1053 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001054 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001055 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001056
Tim Peters07e99cb2001-01-14 23:47:14 +00001057 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001058 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 dat = self.mo.group('data')
1060 self.tagged_commands[tag] = (typ, [dat])
1061 else:
1062 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001063
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001067 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001069
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 if self.mo is None:
1071 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001072
Tim Peters07e99cb2001-01-14 23:47:14 +00001073 if self._match(Continuation, resp):
1074 self.continuation_response = self.mo.group('data')
1075 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001076
R David Murraya6429db2015-05-10 19:17:23 -04001077 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001078
Tim Peters07e99cb2001-01-14 23:47:14 +00001079 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001080 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001081 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001082 if dat is None: dat = b'' # Null untagged response
1083 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001084
Tim Peters07e99cb2001-01-14 23:47:14 +00001085 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001086
R David Murraya6429db2015-05-10 19:17:23 -04001087 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001088
Tim Peters07e99cb2001-01-14 23:47:14 +00001089 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001090
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001091 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 if __debug__:
1093 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001094 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001095 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001096
Tim Peters07e99cb2001-01-14 23:47:14 +00001097 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001098
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001100
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001102
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001104
Tim Peters07e99cb2001-01-14 23:47:14 +00001105 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001106
Tim Peters07e99cb2001-01-14 23:47:14 +00001107 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001108
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001110 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001111 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001112 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001113
Tim Peters07e99cb2001-01-14 23:47:14 +00001114 if __debug__:
1115 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001116 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001117
Tim Peters07e99cb2001-01-14 23:47:14 +00001118 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001119
1120
Victor Stinner74125a62019-04-15 18:23:20 +02001121 def _get_tagged_response(self, tag, expect_bye=False):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001122
Tim Peters07e99cb2001-01-14 23:47:14 +00001123 while 1:
1124 result = self.tagged_commands[tag]
1125 if result is not None:
1126 del self.tagged_commands[tag]
1127 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001128
Victor Stinner74125a62019-04-15 18:23:20 +02001129 if expect_bye:
1130 typ = 'BYE'
1131 bye = self.untagged_responses.pop(typ, None)
1132 if bye is not None:
1133 # Server replies to the "LOGOUT" command with "BYE"
1134 return (typ, bye)
1135
R David Murray95ff7232014-02-07 13:44:57 -05001136 # If we've seen a BYE at this point, the socket will be
1137 # closed, so report the BYE now.
R David Murray95ff7232014-02-07 13:44:57 -05001138 self._check_bye()
1139
Tim Peters07e99cb2001-01-14 23:47:14 +00001140 # Some have reported "unexpected response" exceptions.
1141 # Note that ignoring them here causes loops.
1142 # Instead, send me details of the unexpected response and
1143 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001144
Tim Peters07e99cb2001-01-14 23:47:14 +00001145 try:
1146 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001147 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001148 if __debug__:
1149 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001150 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001151 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001152
1153
Tim Peters07e99cb2001-01-14 23:47:14 +00001154 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001155
Piers Lauder15e5d532001-07-20 10:52:06 +00001156 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001157 if not line:
1158 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001159
Tim Peters07e99cb2001-01-14 23:47:14 +00001160 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001161 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001162 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001163
Tim Peters07e99cb2001-01-14 23:47:14 +00001164 line = line[:-2]
1165 if __debug__:
1166 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001167 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001168 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001169 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001170 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001171
1172
Tim Peters07e99cb2001-01-14 23:47:14 +00001173 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001174
Tim Peters07e99cb2001-01-14 23:47:14 +00001175 # Run compiled regular expression match method on 's'.
1176 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001177
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 self.mo = cre.match(s)
1179 if __debug__:
1180 if self.mo is not None and self.debug >= 5:
Serhiy Storchakaa4a30202017-11-28 22:54:42 +02001181 self._mesg("\tmatched %r => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001182 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001183
1184
Tim Peters07e99cb2001-01-14 23:47:14 +00001185 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001186
R David Murraya6429db2015-05-10 19:17:23 -04001187 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001188 self.tagnum = self.tagnum + 1
1189 self.tagged_commands[tag] = None
1190 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001191
1192
Tim Peters07e99cb2001-01-14 23:47:14 +00001193 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001194
Antoine Pitroub1436f12010-11-09 22:55:55 +00001195 arg = arg.replace('\\', '\\\\')
1196 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001197
Antoine Pitroub1436f12010-11-09 22:55:55 +00001198 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001199
1200
Tim Peters07e99cb2001-01-14 23:47:14 +00001201 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001202
Guido van Rossum68468eb2003-02-27 20:14:51 +00001203 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001204
1205
Tim Peters07e99cb2001-01-14 23:47:14 +00001206 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001207 if typ == 'NO':
1208 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001209 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001210 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001211 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001212 if __debug__:
1213 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001214 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001215 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001216
1217
Piers Lauderf2d7d152002-02-22 01:15:17 +00001218 if __debug__:
1219
1220 def _mesg(self, s, secs=None):
1221 if secs is None:
1222 secs = time.time()
1223 tm = time.strftime('%M:%S', time.localtime(secs))
1224 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1225 sys.stderr.flush()
1226
1227 def _dump_ur(self, dict):
1228 # Dump untagged responses (in `dict').
1229 l = dict.items()
1230 if not l: return
1231 t = '\n\t\t'
1232 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1233 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1234
1235 def _log(self, line):
1236 # Keep log of last `_cmd_log_len' interactions for debugging.
1237 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1238 self._cmd_log_idx += 1
1239 if self._cmd_log_idx >= self._cmd_log_len:
1240 self._cmd_log_idx = 0
1241
1242 def print_log(self):
1243 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1244 i, n = self._cmd_log_idx, self._cmd_log_len
1245 while n:
1246 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001247 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001248 except:
1249 pass
1250 i += 1
1251 if i >= self._cmd_log_len:
1252 i = 0
1253 n -= 1
1254
1255
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001256if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001257
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001258 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001259
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001260 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001261
Antoine Pitrou08728162011-05-06 18:49:52 +02001262 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001263
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001264 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001265 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001266 keyfile - PEM formatted file that contains your private key (default: None);
1267 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001268 ssl_context - a SSLContext object that contains your certificate chain
1269 and private key (default: None)
1270 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001271 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001272
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001273 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001274 """
Piers Laudera4f83132002-03-08 01:53:24 +00001275
1276
R David Murraya6429db2015-05-10 19:17:23 -04001277 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
1278 certfile=None, ssl_context=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001279 if ssl_context is not None and keyfile is not None:
1280 raise ValueError("ssl_context and keyfile arguments are mutually "
1281 "exclusive")
1282 if ssl_context is not None and certfile is not None:
1283 raise ValueError("ssl_context and certfile arguments are mutually "
1284 "exclusive")
Christian Heimesd0486372016-09-10 23:23:33 +02001285 if keyfile is not None or certfile is not None:
1286 import warnings
Pablo Aguiar4b5e62d2018-11-01 11:33:35 +01001287 warnings.warn("keyfile and certfile are deprecated, use a "
Christian Heimesd0486372016-09-10 23:23:33 +02001288 "custom ssl_context instead", DeprecationWarning, 2)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001289 self.keyfile = keyfile
1290 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001291 if ssl_context is None:
1292 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1293 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001294 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001295 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001296
Christian Heimesfb5faf02008-11-05 19:39:50 +00001297 def _create_socket(self):
1298 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001299 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001300 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001301
Christian Heimesfb5faf02008-11-05 19:39:50 +00001302 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001303 """Setup connection to remote server on "host:port".
1304 (default: localhost:standard IMAP4 SSL port).
1305 This connection will be used by the routines:
1306 read, readline, send, shutdown.
1307 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001308 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001309
1310 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001311
1312
Piers Laudere0273de2002-11-22 05:53:04 +00001313class IMAP4_stream(IMAP4):
1314
1315 """IMAP4 client class over a stream
1316
1317 Instantiate with: IMAP4_stream(command)
1318
R David Murraya6429db2015-05-10 19:17:23 -04001319 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001320
1321 for more documentation see the docstring of the parent class IMAP4.
1322 """
1323
1324
1325 def __init__(self, command):
1326 self.command = command
1327 IMAP4.__init__(self)
1328
1329
1330 def open(self, host = None, port = None):
1331 """Setup a stream connection.
1332 This connection will be used by the routines:
1333 read, readline, send, shutdown.
1334 """
1335 self.host = None # For compatibility with parent class
1336 self.port = None
1337 self.sock = None
1338 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001339 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001340 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001341 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1342 shell=True, close_fds=True)
1343 self.writefile = self.process.stdin
1344 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001345
1346 def read(self, size):
1347 """Read 'size' bytes from remote."""
1348 return self.readfile.read(size)
1349
1350
1351 def readline(self):
1352 """Read line from remote."""
1353 return self.readfile.readline()
1354
1355
1356 def send(self, data):
1357 """Send data to remote."""
1358 self.writefile.write(data)
1359 self.writefile.flush()
1360
1361
1362 def shutdown(self):
1363 """Close I/O established in "open"."""
1364 self.readfile.close()
1365 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001366 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001367
1368
1369
Guido van Rossumeda960a1998-06-18 14:24:28 +00001370class _Authenticator:
1371
Tim Peters07e99cb2001-01-14 23:47:14 +00001372 """Private class to provide en/decoding
1373 for base64-based authentication conversation.
1374 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001375
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 def __init__(self, mechinst):
1377 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001378
Tim Peters07e99cb2001-01-14 23:47:14 +00001379 def process(self, data):
1380 ret = self.mech(self.decode(data))
1381 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001382 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001383 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001384
Tim Peters07e99cb2001-01-14 23:47:14 +00001385 def encode(self, inp):
1386 #
1387 # Invoke binascii.b2a_base64 iteratively with
1388 # short even length buffers, strip the trailing
1389 # line feed from the result and append. "Even"
1390 # means a number that factors to both 6 and 8,
1391 # so when it gets to the end of the 8-bit input
1392 # there's no partial 6-bit output.
1393 #
R David Murray774a39f2013-02-19 12:17:31 -05001394 oup = b''
1395 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001396 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 while inp:
1398 if len(inp) > 48:
1399 t = inp[:48]
1400 inp = inp[48:]
1401 else:
1402 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001403 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001404 e = binascii.b2a_base64(t)
1405 if e:
1406 oup = oup + e[:-1]
1407 return oup
1408
1409 def decode(self, inp):
1410 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001411 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001412 return binascii.a2b_base64(inp)
1413
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001414Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1415Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001416
1417def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001418 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001419
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001420 Return corresponding local time. The return value is a
1421 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001422 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 mo = InternalDate.match(resp)
1425 if not mo:
1426 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001427
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 mon = Mon2num[mo.group('mon')]
1429 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001430
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001431 day = int(mo.group('day'))
1432 year = int(mo.group('year'))
1433 hour = int(mo.group('hour'))
1434 min = int(mo.group('min'))
1435 sec = int(mo.group('sec'))
1436 zoneh = int(mo.group('zoneh'))
1437 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001438
Tim Peters07e99cb2001-01-14 23:47:14 +00001439 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001440
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001442 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001444
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001446 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001447
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001448 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001449
1450
1451
1452def Int2AP(num):
1453
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001455
Christian Heimesfb5faf02008-11-05 19:39:50 +00001456 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 num = int(abs(num))
1458 while num:
1459 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001460 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001462
1463
1464
1465def ParseFlags(resp):
1466
Tim Peters07e99cb2001-01-14 23:47:14 +00001467 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001468
Tim Peters07e99cb2001-01-14 23:47:14 +00001469 mo = Flags.match(resp)
1470 if not mo:
1471 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001472
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001473 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001474
1475
1476def Time2Internaldate(date_time):
1477
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001478 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001479
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001480 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001481 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001482 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001483 representing local time, an instance of time.struct_time (as
1484 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001485 double-quoted string. In the last case, it is assumed to already
1486 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001487 """
Fred Drakedb519202002-01-05 17:17:09 +00001488 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001489 dt = datetime.fromtimestamp(date_time,
1490 timezone.utc).astimezone()
1491 elif isinstance(date_time, tuple):
1492 try:
1493 gmtoff = date_time.tm_gmtoff
1494 except AttributeError:
1495 if time.daylight:
1496 dst = date_time[8]
1497 if dst == -1:
1498 dst = time.localtime(time.mktime(date_time))[8]
1499 gmtoff = -(time.timezone, time.altzone)[dst]
1500 else:
1501 gmtoff = -time.timezone
1502 delta = timedelta(seconds=gmtoff)
1503 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1504 elif isinstance(date_time, datetime):
1505 if date_time.tzinfo is None:
1506 raise ValueError("date_time must be aware")
1507 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001508 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001509 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001510 else:
1511 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001512 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1513 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001514
1515
1516
Guido van Rossum8c062211999-12-13 23:27:45 +00001517if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001518
Piers Laudere0273de2002-11-22 05:53:04 +00001519 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1520 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1521 # to test the IMAP4_stream class
1522
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001523 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001524
Tim Peters07e99cb2001-01-14 23:47:14 +00001525 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001526 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001527 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001528 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001529
Piers Laudere0273de2002-11-22 05:53:04 +00001530 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001531 for opt,val in optlist:
1532 if opt == '-d':
1533 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001534 elif opt == '-s':
1535 stream_command = val
1536 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001537
Tim Peters07e99cb2001-01-14 23:47:14 +00001538 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001539
Tim Peters07e99cb2001-01-14 23:47:14 +00001540 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001541
Tim Peters07e99cb2001-01-14 23:47:14 +00001542 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001543 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001544
Piers Lauder47404ff2003-04-29 23:40:59 +00001545 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 +00001546 test_seq1 = (
1547 ('login', (USER, PASSWD)),
1548 ('create', ('/tmp/xxx 1',)),
1549 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1550 ('CREATE', ('/tmp/yyz 2',)),
1551 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1552 ('list', ('/tmp', 'yy*')),
1553 ('select', ('/tmp/yyz 2',)),
1554 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001555 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
R David Murray44b548d2016-09-08 13:59:53 -04001556 ('store', ('1', 'FLAGS', r'(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001557 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001558 ('expunge', ()),
1559 ('recent', ()),
1560 ('close', ()),
1561 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001562
Tim Peters07e99cb2001-01-14 23:47:14 +00001563 test_seq2 = (
1564 ('select', ()),
1565 ('response',('UIDVALIDITY',)),
1566 ('uid', ('SEARCH', 'ALL')),
1567 ('response', ('EXISTS',)),
1568 ('append', (None, None, None, test_mesg)),
1569 ('recent', ()),
1570 ('logout', ()),
1571 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001572
Tim Peters07e99cb2001-01-14 23:47:14 +00001573 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001574 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001575 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001576 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001577 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001578 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001579
Tim Peters07e99cb2001-01-14 23:47:14 +00001580 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001581 if stream_command:
1582 M = IMAP4_stream(stream_command)
1583 else:
1584 M = IMAP4(host)
1585 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001586 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001587 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001588 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001589
Tim Peters07e99cb2001-01-14 23:47:14 +00001590 for cmd,args in test_seq1:
1591 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001592
Tim Peters07e99cb2001-01-14 23:47:14 +00001593 for ml in run('list', ('/tmp/', 'yy%')):
1594 mo = re.match(r'.*"([^"]+)"$', ml)
1595 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001596 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001597 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001598
Tim Peters07e99cb2001-01-14 23:47:14 +00001599 for cmd,args in test_seq2:
1600 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001601
Tim Peters07e99cb2001-01-14 23:47:14 +00001602 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1603 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001604
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001605 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001606 if not uid: continue
1607 run('uid', ('FETCH', '%s' % uid[-1],
1608 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001609
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001610 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001611
Tim Peters07e99cb2001-01-14 23:47:14 +00001612 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001613 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001614
Tim Peters07e99cb2001-01-14 23:47:14 +00001615 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001616 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001617If you would like to see debugging output,
1618try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001619''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001620
Tim Peters07e99cb2001-01-14 23:47:14 +00001621 raise