blob: dd237f7704aca0b66733951be5bdc61133d2b30b [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):
275 try:
276 self.logout()
277 except OSError:
278 pass
Guido van Rossum26367a01998-09-28 15:34:46 +0000279
280
Piers Lauder15e5d532001-07-20 10:52:06 +0000281 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000282
283
Christian Heimesfb5faf02008-11-05 19:39:50 +0000284 def _create_socket(self):
Berker Peksage4dcbbd2018-08-07 05:12:18 +0300285 # Default value of IMAP4.host is '', but socket.getaddrinfo()
286 # (which is used by socket.create_connection()) expects None
287 # as a default value for host.
288 host = None if not self.host else self.host
289 return socket.create_connection((host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000290
Piers Lauderf97b2d72002-06-05 22:31:57 +0000291 def open(self, host = '', port = IMAP4_PORT):
292 """Setup connection to remote server on "host:port"
293 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000294 This connection will be used by the routines:
295 read, readline, send, shutdown.
296 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000297 self.host = host
298 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000299 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000300 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000301
302
Piers Lauder15e5d532001-07-20 10:52:06 +0000303 def read(self, size):
304 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200305 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000306
307
308 def readline(self):
309 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100310 line = self.file.readline(_MAXLINE + 1)
311 if len(line) > _MAXLINE:
312 raise self.error("got more than %d bytes" % _MAXLINE)
313 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000314
315
316 def send(self, data):
317 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000318 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000319
Piers Lauderf2d7d152002-02-22 01:15:17 +0000320
Piers Lauder15e5d532001-07-20 10:52:06 +0000321 def shutdown(self):
322 """Close I/O established in "open"."""
323 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000324 try:
325 self.sock.shutdown(socket.SHUT_RDWR)
Victor Stinner83a2c282017-05-15 17:33:45 +0200326 except OSError as exc:
327 # The server might already have closed the connection.
328 # On Windows, this may result in WSAEINVAL (error 10022):
329 # An invalid operation was attempted.
330 if (exc.errno != errno.ENOTCONN
331 and getattr(exc, 'winerror', 0) != 10022):
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000332 raise
333 finally:
334 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000335
336
337 def socket(self):
338 """Return socket instance used to connect to IMAP4 server.
339
340 socket = <instance>.socket()
341 """
342 return self.sock
343
344
345
346 # Utility methods
347
348
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 def recent(self):
350 """Return most recent 'RECENT' responses if any exist,
351 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000352
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000354
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 'data' is None if no new messages,
356 else list of RECENT responses, most recent last.
357 """
358 name = 'RECENT'
359 typ, dat = self._untagged_response('OK', [None], name)
360 if dat[-1]:
361 return typ, dat
362 typ, dat = self.noop() # Prod server for response
363 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000364
365
Tim Peters07e99cb2001-01-14 23:47:14 +0000366 def response(self, code):
367 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000370
Tim Peters07e99cb2001-01-14 23:47:14 +0000371 (code, [data]) = <instance>.response(code)
372 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000373 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000374
375
Guido van Rossum26367a01998-09-28 15:34:46 +0000376
Tim Peters07e99cb2001-01-14 23:47:14 +0000377 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 def append(self, mailbox, flags, date_time, message):
381 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 All args except `message' can be None.
386 """
387 name = 'APPEND'
388 if not mailbox:
389 mailbox = 'INBOX'
390 if flags:
391 if (flags[0],flags[-1]) != ('(',')'):
392 flags = '(%s)' % flags
393 else:
394 flags = None
395 if date_time:
396 date_time = Time2Internaldate(date_time)
397 else:
398 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400399 literal = MapCRLF.sub(CRLF, message)
400 if self.utf8_enabled:
401 literal = b'UTF8 (' + literal + b')'
402 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000403 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000404
405
Tim Peters07e99cb2001-01-14 23:47:14 +0000406 def authenticate(self, mechanism, authobject):
407 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000408
Tim Peters07e99cb2001-01-14 23:47:14 +0000409 'mechanism' specifies which authentication mechanism is to
410 be used - it must appear in <instance>.capabilities in the
411 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000414
Tim Peters07e99cb2001-01-14 23:47:14 +0000415 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000416
R David Murray774a39f2013-02-19 12:17:31 -0500417 It will be called to process server continuation responses; the
418 response argument it is passed will be a bytes. It should return bytes
419 data that will be base64 encoded and sent to the server. It should
420 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000421 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000422 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000423 # XXX: shouldn't this code be removed, not commented out?
424 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000425 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000426 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000427 self.literal = _Authenticator(authobject).process
428 typ, dat = self._simple_command('AUTHENTICATE', mech)
429 if typ != 'OK':
R David Murrayb079c072016-12-24 21:32:26 -0500430 raise self.error(dat[-1].decode('utf-8', 'replace'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 self.state = 'AUTH'
432 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000433
434
Piers Lauderd80ef022005-06-01 23:50:52 +0000435 def capability(self):
436 """(typ, [data]) = <instance>.capability()
437 Fetch capabilities list from server."""
438
439 name = 'CAPABILITY'
440 typ, dat = self._simple_command(name)
441 return self._untagged_response(typ, dat, name)
442
443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 def check(self):
445 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 (typ, [data]) = <instance>.check()
448 """
449 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000450
451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 def close(self):
453 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000454
Tim Peters07e99cb2001-01-14 23:47:14 +0000455 Deleted messages are removed from writable mailbox.
456 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000457
Tim Peters07e99cb2001-01-14 23:47:14 +0000458 (typ, [data]) = <instance>.close()
459 """
460 try:
461 typ, dat = self._simple_command('CLOSE')
462 finally:
463 self.state = 'AUTH'
464 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
466
Tim Peters07e99cb2001-01-14 23:47:14 +0000467 def copy(self, message_set, new_mailbox):
468 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000469
Tim Peters07e99cb2001-01-14 23:47:14 +0000470 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
471 """
472 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000473
474
Tim Peters07e99cb2001-01-14 23:47:14 +0000475 def create(self, mailbox):
476 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000477
Tim Peters07e99cb2001-01-14 23:47:14 +0000478 (typ, [data]) = <instance>.create(mailbox)
479 """
480 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000481
482
Tim Peters07e99cb2001-01-14 23:47:14 +0000483 def delete(self, mailbox):
484 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000485
Tim Peters07e99cb2001-01-14 23:47:14 +0000486 (typ, [data]) = <instance>.delete(mailbox)
487 """
488 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000489
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000490 def deleteacl(self, mailbox, who):
491 """Delete the ACLs (remove any rights) set for who on mailbox.
492
493 (typ, [data]) = <instance>.deleteacl(mailbox, who)
494 """
495 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000496
R David Murraya6429db2015-05-10 19:17:23 -0400497 def enable(self, capability):
498 """Send an RFC5161 enable string to the server.
499
500 (typ, [data]) = <intance>.enable(capability)
501 """
502 if 'ENABLE' not in self.capabilities:
503 raise IMAP4.error("Server does not support ENABLE")
504 typ, data = self._simple_command('ENABLE', capability)
505 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
506 self._mode_utf8()
507 return typ, data
508
Tim Peters07e99cb2001-01-14 23:47:14 +0000509 def expunge(self):
510 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000515
Tim Peters07e99cb2001-01-14 23:47:14 +0000516 'data' is list of 'EXPUNGE'd message numbers in order received.
517 """
518 name = 'EXPUNGE'
519 typ, dat = self._simple_command(name)
520 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000521
522
Tim Peters07e99cb2001-01-14 23:47:14 +0000523 def fetch(self, message_set, message_parts):
524 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000525
Tim Peters07e99cb2001-01-14 23:47:14 +0000526 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000527
Tim Peters07e99cb2001-01-14 23:47:14 +0000528 'message_parts' should be a string of selected parts
529 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 'data' are tuples of message part envelope and data.
532 """
533 name = 'FETCH'
534 typ, dat = self._simple_command(name, message_set, message_parts)
535 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
537
Piers Lauder15e5d532001-07-20 10:52:06 +0000538 def getacl(self, mailbox):
539 """Get the ACLs for a mailbox.
540
541 (typ, [data]) = <instance>.getacl(mailbox)
542 """
543 typ, dat = self._simple_command('GETACL', mailbox)
544 return self._untagged_response(typ, dat, 'ACL')
545
546
Piers Lauderd80ef022005-06-01 23:50:52 +0000547 def getannotation(self, mailbox, entry, attribute):
548 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
549 Retrieve ANNOTATIONs."""
550
551 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
552 return self._untagged_response(typ, dat, 'ANNOTATION')
553
554
Piers Lauder3fca2912002-06-17 07:07:20 +0000555 def getquota(self, root):
556 """Get the quota root's resource usage and limits.
557
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000558 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000559
560 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000561 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000562 typ, dat = self._simple_command('GETQUOTA', root)
563 return self._untagged_response(typ, dat, 'QUOTA')
564
565
566 def getquotaroot(self, mailbox):
567 """Get the list of quota roots for the named mailbox.
568
569 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000570 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000571 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000572 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
573 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000574 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000575
576
Tim Peters07e99cb2001-01-14 23:47:14 +0000577 def list(self, directory='""', pattern='*'):
578 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000579
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000581
Tim Peters07e99cb2001-01-14 23:47:14 +0000582 'data' is list of LIST responses.
583 """
584 name = 'LIST'
585 typ, dat = self._simple_command(name, directory, pattern)
586 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000587
588
Tim Peters07e99cb2001-01-14 23:47:14 +0000589 def login(self, user, password):
590 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000591
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000593
Tim Peters07e99cb2001-01-14 23:47:14 +0000594 NB: 'password' will be quoted.
595 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000596 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
597 if typ != 'OK':
598 raise self.error(dat[-1])
599 self.state = 'AUTH'
600 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000601
602
Piers Laudere0273de2002-11-22 05:53:04 +0000603 def login_cram_md5(self, user, password):
604 """ Force use of CRAM-MD5 authentication.
605
606 (typ, [data]) = <instance>.login_cram_md5(user, password)
607 """
608 self.user, self.password = user, password
609 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
610
611
612 def _CRAM_MD5_AUTH(self, challenge):
613 """ Authobject to use with CRAM-MD5 authentication. """
614 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400615 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500616 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100617 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000618
619
Tim Peters07e99cb2001-01-14 23:47:14 +0000620 def logout(self):
621 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000622
Tim Peters07e99cb2001-01-14 23:47:14 +0000623 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000624
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 Returns server 'BYE' response.
626 """
627 self.state = 'LOGOUT'
628 try: typ, dat = self._simple_command('LOGOUT')
629 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000630 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000631 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000632 return 'BYE', self.untagged_responses['BYE']
633 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):
Antoine Pitroudac47912010-11-10 00:18:40 +00001015 # BYE is expected after LOGOUT
1016 if name != 'LOGOUT':
1017 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 try:
1019 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +00001020 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001022 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001023 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +00001024 if name != 'LOGOUT':
1025 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 if typ == 'BAD':
1027 raise self.error('%s command error: %s %s' % (name, typ, data))
1028 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
1030
Antoine Pitroudbe75192010-11-16 17:55:26 +00001031 def _get_capabilities(self):
1032 typ, dat = self.capability()
1033 if dat == [None]:
1034 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001035 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001036 dat = dat.upper()
1037 self.capabilities = tuple(dat.split())
1038
1039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001041
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 # Read response and store.
1043 #
1044 # Returns None for continuation responses,
1045 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001046
Tim Peters07e99cb2001-01-14 23:47:14 +00001047 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001048
Tim Peters07e99cb2001-01-14 23:47:14 +00001049 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001050
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 if self._match(self.tagre, resp):
1052 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001053 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001054 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001055
Tim Peters07e99cb2001-01-14 23:47:14 +00001056 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001057 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 dat = self.mo.group('data')
1059 self.tagged_commands[tag] = (typ, [dat])
1060 else:
1061 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001062
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001064
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001066 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001068
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 if self.mo is None:
1070 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001071
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 if self._match(Continuation, resp):
1073 self.continuation_response = self.mo.group('data')
1074 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001075
R David Murraya6429db2015-05-10 19:17:23 -04001076 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001077
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001079 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001081 if dat is None: dat = b'' # Null untagged response
1082 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001083
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001085
R David Murraya6429db2015-05-10 19:17:23 -04001086 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001087
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001089
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001090 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 if __debug__:
1092 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001093 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001094 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001095
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001097
Tim Peters07e99cb2001-01-14 23:47:14 +00001098 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001099
Tim Peters07e99cb2001-01-14 23:47:14 +00001100 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001101
Tim Peters07e99cb2001-01-14 23:47:14 +00001102 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001103
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001105
Tim Peters07e99cb2001-01-14 23:47:14 +00001106 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001107
Tim Peters07e99cb2001-01-14 23:47:14 +00001108 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001109 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001110 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001111 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001112
Tim Peters07e99cb2001-01-14 23:47:14 +00001113 if __debug__:
1114 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001115 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001116
Tim Peters07e99cb2001-01-14 23:47:14 +00001117 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001118
1119
Tim Peters07e99cb2001-01-14 23:47:14 +00001120 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001121
Tim Peters07e99cb2001-01-14 23:47:14 +00001122 while 1:
1123 result = self.tagged_commands[tag]
1124 if result is not None:
1125 del self.tagged_commands[tag]
1126 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001127
R David Murray95ff7232014-02-07 13:44:57 -05001128 # If we've seen a BYE at this point, the socket will be
1129 # closed, so report the BYE now.
1130
1131 self._check_bye()
1132
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 # Some have reported "unexpected response" exceptions.
1134 # Note that ignoring them here causes loops.
1135 # Instead, send me details of the unexpected response and
1136 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001137
Tim Peters07e99cb2001-01-14 23:47:14 +00001138 try:
1139 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001140 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001141 if __debug__:
1142 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001143 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001144 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001145
1146
Tim Peters07e99cb2001-01-14 23:47:14 +00001147 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001148
Piers Lauder15e5d532001-07-20 10:52:06 +00001149 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001150 if not line:
1151 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001152
Tim Peters07e99cb2001-01-14 23:47:14 +00001153 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001154 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001155 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001156
Tim Peters07e99cb2001-01-14 23:47:14 +00001157 line = line[:-2]
1158 if __debug__:
1159 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001160 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001161 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001162 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001163 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001164
1165
Tim Peters07e99cb2001-01-14 23:47:14 +00001166 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001167
Tim Peters07e99cb2001-01-14 23:47:14 +00001168 # Run compiled regular expression match method on 's'.
1169 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001170
Tim Peters07e99cb2001-01-14 23:47:14 +00001171 self.mo = cre.match(s)
1172 if __debug__:
1173 if self.mo is not None and self.debug >= 5:
Serhiy Storchakaa4a30202017-11-28 22:54:42 +02001174 self._mesg("\tmatched %r => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001175 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001176
1177
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001179
R David Murraya6429db2015-05-10 19:17:23 -04001180 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001181 self.tagnum = self.tagnum + 1
1182 self.tagged_commands[tag] = None
1183 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001184
1185
Tim Peters07e99cb2001-01-14 23:47:14 +00001186 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001187
Antoine Pitroub1436f12010-11-09 22:55:55 +00001188 arg = arg.replace('\\', '\\\\')
1189 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001190
Antoine Pitroub1436f12010-11-09 22:55:55 +00001191 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001192
1193
Tim Peters07e99cb2001-01-14 23:47:14 +00001194 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001195
Guido van Rossum68468eb2003-02-27 20:14:51 +00001196 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001197
1198
Tim Peters07e99cb2001-01-14 23:47:14 +00001199 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001200 if typ == 'NO':
1201 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001202 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001203 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001204 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001205 if __debug__:
1206 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001207 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001208 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001209
1210
Piers Lauderf2d7d152002-02-22 01:15:17 +00001211 if __debug__:
1212
1213 def _mesg(self, s, secs=None):
1214 if secs is None:
1215 secs = time.time()
1216 tm = time.strftime('%M:%S', time.localtime(secs))
1217 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1218 sys.stderr.flush()
1219
1220 def _dump_ur(self, dict):
1221 # Dump untagged responses (in `dict').
1222 l = dict.items()
1223 if not l: return
1224 t = '\n\t\t'
1225 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1226 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1227
1228 def _log(self, line):
1229 # Keep log of last `_cmd_log_len' interactions for debugging.
1230 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1231 self._cmd_log_idx += 1
1232 if self._cmd_log_idx >= self._cmd_log_len:
1233 self._cmd_log_idx = 0
1234
1235 def print_log(self):
1236 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1237 i, n = self._cmd_log_idx, self._cmd_log_len
1238 while n:
1239 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001240 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001241 except:
1242 pass
1243 i += 1
1244 if i >= self._cmd_log_len:
1245 i = 0
1246 n -= 1
1247
1248
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001249if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001250
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001251 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001252
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001253 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001254
Antoine Pitrou08728162011-05-06 18:49:52 +02001255 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001256
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001257 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001258 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001259 keyfile - PEM formatted file that contains your private key (default: None);
1260 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001261 ssl_context - a SSLContext object that contains your certificate chain
1262 and private key (default: None)
1263 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001264 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001265
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001266 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001267 """
Piers Laudera4f83132002-03-08 01:53:24 +00001268
1269
R David Murraya6429db2015-05-10 19:17:23 -04001270 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
1271 certfile=None, ssl_context=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001272 if ssl_context is not None and keyfile is not None:
1273 raise ValueError("ssl_context and keyfile arguments are mutually "
1274 "exclusive")
1275 if ssl_context is not None and certfile is not None:
1276 raise ValueError("ssl_context and certfile arguments are mutually "
1277 "exclusive")
Christian Heimesd0486372016-09-10 23:23:33 +02001278 if keyfile is not None or certfile is not None:
1279 import warnings
Pablo Aguiar4b5e62d2018-11-01 11:33:35 +01001280 warnings.warn("keyfile and certfile are deprecated, use a "
Christian Heimesd0486372016-09-10 23:23:33 +02001281 "custom ssl_context instead", DeprecationWarning, 2)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001282 self.keyfile = keyfile
1283 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001284 if ssl_context is None:
1285 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1286 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001287 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001288 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001289
Christian Heimesfb5faf02008-11-05 19:39:50 +00001290 def _create_socket(self):
1291 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001292 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001293 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001294
Christian Heimesfb5faf02008-11-05 19:39:50 +00001295 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001296 """Setup connection to remote server on "host:port".
1297 (default: localhost:standard IMAP4 SSL port).
1298 This connection will be used by the routines:
1299 read, readline, send, shutdown.
1300 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001301 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001302
1303 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001304
1305
Piers Laudere0273de2002-11-22 05:53:04 +00001306class IMAP4_stream(IMAP4):
1307
1308 """IMAP4 client class over a stream
1309
1310 Instantiate with: IMAP4_stream(command)
1311
R David Murraya6429db2015-05-10 19:17:23 -04001312 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001313
1314 for more documentation see the docstring of the parent class IMAP4.
1315 """
1316
1317
1318 def __init__(self, command):
1319 self.command = command
1320 IMAP4.__init__(self)
1321
1322
1323 def open(self, host = None, port = None):
1324 """Setup a stream connection.
1325 This connection will be used by the routines:
1326 read, readline, send, shutdown.
1327 """
1328 self.host = None # For compatibility with parent class
1329 self.port = None
1330 self.sock = None
1331 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001332 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001333 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001334 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1335 shell=True, close_fds=True)
1336 self.writefile = self.process.stdin
1337 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001338
1339 def read(self, size):
1340 """Read 'size' bytes from remote."""
1341 return self.readfile.read(size)
1342
1343
1344 def readline(self):
1345 """Read line from remote."""
1346 return self.readfile.readline()
1347
1348
1349 def send(self, data):
1350 """Send data to remote."""
1351 self.writefile.write(data)
1352 self.writefile.flush()
1353
1354
1355 def shutdown(self):
1356 """Close I/O established in "open"."""
1357 self.readfile.close()
1358 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001359 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001360
1361
1362
Guido van Rossumeda960a1998-06-18 14:24:28 +00001363class _Authenticator:
1364
Tim Peters07e99cb2001-01-14 23:47:14 +00001365 """Private class to provide en/decoding
1366 for base64-based authentication conversation.
1367 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001368
Tim Peters07e99cb2001-01-14 23:47:14 +00001369 def __init__(self, mechinst):
1370 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001371
Tim Peters07e99cb2001-01-14 23:47:14 +00001372 def process(self, data):
1373 ret = self.mech(self.decode(data))
1374 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001375 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001377
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 def encode(self, inp):
1379 #
1380 # Invoke binascii.b2a_base64 iteratively with
1381 # short even length buffers, strip the trailing
1382 # line feed from the result and append. "Even"
1383 # means a number that factors to both 6 and 8,
1384 # so when it gets to the end of the 8-bit input
1385 # there's no partial 6-bit output.
1386 #
R David Murray774a39f2013-02-19 12:17:31 -05001387 oup = b''
1388 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001389 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001390 while inp:
1391 if len(inp) > 48:
1392 t = inp[:48]
1393 inp = inp[48:]
1394 else:
1395 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001396 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 e = binascii.b2a_base64(t)
1398 if e:
1399 oup = oup + e[:-1]
1400 return oup
1401
1402 def decode(self, inp):
1403 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001404 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001405 return binascii.a2b_base64(inp)
1406
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001407Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1408Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001409
1410def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001411 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001413 Return corresponding local time. The return value is a
1414 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001415 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001416
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 mo = InternalDate.match(resp)
1418 if not mo:
1419 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001420
Tim Peters07e99cb2001-01-14 23:47:14 +00001421 mon = Mon2num[mo.group('mon')]
1422 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001424 day = int(mo.group('day'))
1425 year = int(mo.group('year'))
1426 hour = int(mo.group('hour'))
1427 min = int(mo.group('min'))
1428 sec = int(mo.group('sec'))
1429 zoneh = int(mo.group('zoneh'))
1430 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001431
Tim Peters07e99cb2001-01-14 23:47:14 +00001432 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001433
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001435 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001436 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001437
Tim Peters07e99cb2001-01-14 23:47:14 +00001438 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001439 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001440
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001441 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001442
1443
1444
1445def Int2AP(num):
1446
Tim Peters07e99cb2001-01-14 23:47:14 +00001447 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001448
Christian Heimesfb5faf02008-11-05 19:39:50 +00001449 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001450 num = int(abs(num))
1451 while num:
1452 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001453 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001455
1456
1457
1458def ParseFlags(resp):
1459
Tim Peters07e99cb2001-01-14 23:47:14 +00001460 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001461
Tim Peters07e99cb2001-01-14 23:47:14 +00001462 mo = Flags.match(resp)
1463 if not mo:
1464 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001465
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001466 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001467
1468
1469def Time2Internaldate(date_time):
1470
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001471 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001472
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001473 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001474 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001475 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001476 representing local time, an instance of time.struct_time (as
1477 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001478 double-quoted string. In the last case, it is assumed to already
1479 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001480 """
Fred Drakedb519202002-01-05 17:17:09 +00001481 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001482 dt = datetime.fromtimestamp(date_time,
1483 timezone.utc).astimezone()
1484 elif isinstance(date_time, tuple):
1485 try:
1486 gmtoff = date_time.tm_gmtoff
1487 except AttributeError:
1488 if time.daylight:
1489 dst = date_time[8]
1490 if dst == -1:
1491 dst = time.localtime(time.mktime(date_time))[8]
1492 gmtoff = -(time.timezone, time.altzone)[dst]
1493 else:
1494 gmtoff = -time.timezone
1495 delta = timedelta(seconds=gmtoff)
1496 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1497 elif isinstance(date_time, datetime):
1498 if date_time.tzinfo is None:
1499 raise ValueError("date_time must be aware")
1500 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001501 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001502 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001503 else:
1504 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001505 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1506 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001507
1508
1509
Guido van Rossum8c062211999-12-13 23:27:45 +00001510if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001511
Piers Laudere0273de2002-11-22 05:53:04 +00001512 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1513 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1514 # to test the IMAP4_stream class
1515
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001516 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001517
Tim Peters07e99cb2001-01-14 23:47:14 +00001518 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001519 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001520 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001521 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001522
Piers Laudere0273de2002-11-22 05:53:04 +00001523 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001524 for opt,val in optlist:
1525 if opt == '-d':
1526 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001527 elif opt == '-s':
1528 stream_command = val
1529 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001530
Tim Peters07e99cb2001-01-14 23:47:14 +00001531 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001532
Tim Peters07e99cb2001-01-14 23:47:14 +00001533 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001534
Tim Peters07e99cb2001-01-14 23:47:14 +00001535 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001536 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001537
Piers Lauder47404ff2003-04-29 23:40:59 +00001538 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 +00001539 test_seq1 = (
1540 ('login', (USER, PASSWD)),
1541 ('create', ('/tmp/xxx 1',)),
1542 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1543 ('CREATE', ('/tmp/yyz 2',)),
1544 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1545 ('list', ('/tmp', 'yy*')),
1546 ('select', ('/tmp/yyz 2',)),
1547 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001548 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
R David Murray44b548d2016-09-08 13:59:53 -04001549 ('store', ('1', 'FLAGS', r'(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001550 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001551 ('expunge', ()),
1552 ('recent', ()),
1553 ('close', ()),
1554 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001555
Tim Peters07e99cb2001-01-14 23:47:14 +00001556 test_seq2 = (
1557 ('select', ()),
1558 ('response',('UIDVALIDITY',)),
1559 ('uid', ('SEARCH', 'ALL')),
1560 ('response', ('EXISTS',)),
1561 ('append', (None, None, None, test_mesg)),
1562 ('recent', ()),
1563 ('logout', ()),
1564 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001565
Tim Peters07e99cb2001-01-14 23:47:14 +00001566 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001567 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001568 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001569 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001570 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001571 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001572
Tim Peters07e99cb2001-01-14 23:47:14 +00001573 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001574 if stream_command:
1575 M = IMAP4_stream(stream_command)
1576 else:
1577 M = IMAP4(host)
1578 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001579 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001580 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001581 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001582
Tim Peters07e99cb2001-01-14 23:47:14 +00001583 for cmd,args in test_seq1:
1584 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001585
Tim Peters07e99cb2001-01-14 23:47:14 +00001586 for ml in run('list', ('/tmp/', 'yy%')):
1587 mo = re.match(r'.*"([^"]+)"$', ml)
1588 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001589 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001590 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001591
Tim Peters07e99cb2001-01-14 23:47:14 +00001592 for cmd,args in test_seq2:
1593 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001594
Tim Peters07e99cb2001-01-14 23:47:14 +00001595 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1596 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001597
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001598 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001599 if not uid: continue
1600 run('uid', ('FETCH', '%s' % uid[-1],
1601 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001602
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001603 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001604
Tim Peters07e99cb2001-01-14 23:47:14 +00001605 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001606 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001607
Tim Peters07e99cb2001-01-14 23:47:14 +00001608 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001609 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001610If you would like to see debugging output,
1611try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001612''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001613
Tim Peters07e99cb2001-01-14 23:47:14 +00001614 raise