blob: face45ba5fa0dfbdc1a27588f61c7aadf6f2a790 [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
Steve Dower60419a72019-06-24 08:42:54 -0700292 sys.audit("imaplib.IMAP4.open", self, self.host, self.port)
Berker Peksage4dcbbd2018-08-07 05:12:18 +0300293 return socket.create_connection((host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000294
Piers Lauderf97b2d72002-06-05 22:31:57 +0000295 def open(self, host = '', port = IMAP4_PORT):
296 """Setup connection to remote server on "host:port"
297 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000298 This connection will be used by the routines:
299 read, readline, send, shutdown.
300 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000301 self.host = host
302 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000303 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000304 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000305
306
Piers Lauder15e5d532001-07-20 10:52:06 +0000307 def read(self, size):
308 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200309 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000310
311
312 def readline(self):
313 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100314 line = self.file.readline(_MAXLINE + 1)
315 if len(line) > _MAXLINE:
316 raise self.error("got more than %d bytes" % _MAXLINE)
317 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000318
319
320 def send(self, data):
321 """Send data to remote."""
Steve Dower60419a72019-06-24 08:42:54 -0700322 sys.audit("imaplib.IMAP4.send", self, data)
Martin v. Löwise12454f2002-02-16 23:06:19 +0000323 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000324
Piers Lauderf2d7d152002-02-22 01:15:17 +0000325
Piers Lauder15e5d532001-07-20 10:52:06 +0000326 def shutdown(self):
327 """Close I/O established in "open"."""
328 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000329 try:
330 self.sock.shutdown(socket.SHUT_RDWR)
Victor Stinner83a2c282017-05-15 17:33:45 +0200331 except OSError as exc:
332 # The server might already have closed the connection.
333 # On Windows, this may result in WSAEINVAL (error 10022):
334 # An invalid operation was attempted.
335 if (exc.errno != errno.ENOTCONN
336 and getattr(exc, 'winerror', 0) != 10022):
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000337 raise
338 finally:
339 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000340
341
342 def socket(self):
343 """Return socket instance used to connect to IMAP4 server.
344
345 socket = <instance>.socket()
346 """
347 return self.sock
348
349
350
351 # Utility methods
352
353
Tim Peters07e99cb2001-01-14 23:47:14 +0000354 def recent(self):
355 """Return most recent 'RECENT' responses if any exist,
356 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000357
Tim Peters07e99cb2001-01-14 23:47:14 +0000358 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000359
Tim Peters07e99cb2001-01-14 23:47:14 +0000360 'data' is None if no new messages,
361 else list of RECENT responses, most recent last.
362 """
363 name = 'RECENT'
364 typ, dat = self._untagged_response('OK', [None], name)
365 if dat[-1]:
366 return typ, dat
367 typ, dat = self.noop() # Prod server for response
368 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000369
370
Tim Peters07e99cb2001-01-14 23:47:14 +0000371 def response(self, code):
372 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000375
Tim Peters07e99cb2001-01-14 23:47:14 +0000376 (code, [data]) = <instance>.response(code)
377 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000378 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000379
380
Guido van Rossum26367a01998-09-28 15:34:46 +0000381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000383
384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 def append(self, mailbox, flags, date_time, message):
386 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 All args except `message' can be None.
391 """
392 name = 'APPEND'
393 if not mailbox:
394 mailbox = 'INBOX'
395 if flags:
396 if (flags[0],flags[-1]) != ('(',')'):
397 flags = '(%s)' % flags
398 else:
399 flags = None
400 if date_time:
401 date_time = Time2Internaldate(date_time)
402 else:
403 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400404 literal = MapCRLF.sub(CRLF, message)
405 if self.utf8_enabled:
406 literal = b'UTF8 (' + literal + b')'
407 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000409
410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 def authenticate(self, mechanism, authobject):
412 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000413
Tim Peters07e99cb2001-01-14 23:47:14 +0000414 'mechanism' specifies which authentication mechanism is to
415 be used - it must appear in <instance>.capabilities in the
416 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000419
Tim Peters07e99cb2001-01-14 23:47:14 +0000420 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000421
R David Murray774a39f2013-02-19 12:17:31 -0500422 It will be called to process server continuation responses; the
423 response argument it is passed will be a bytes. It should return bytes
424 data that will be base64 encoded and sent to the server. It should
425 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000426 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000427 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000428 # XXX: shouldn't this code be removed, not commented out?
429 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000430 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000431 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000432 self.literal = _Authenticator(authobject).process
433 typ, dat = self._simple_command('AUTHENTICATE', mech)
434 if typ != 'OK':
R David Murrayb079c072016-12-24 21:32:26 -0500435 raise self.error(dat[-1].decode('utf-8', 'replace'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 self.state = 'AUTH'
437 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000438
439
Piers Lauderd80ef022005-06-01 23:50:52 +0000440 def capability(self):
441 """(typ, [data]) = <instance>.capability()
442 Fetch capabilities list from server."""
443
444 name = 'CAPABILITY'
445 typ, dat = self._simple_command(name)
446 return self._untagged_response(typ, dat, name)
447
448
Tim Peters07e99cb2001-01-14 23:47:14 +0000449 def check(self):
450 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 (typ, [data]) = <instance>.check()
453 """
454 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000455
456
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 def close(self):
458 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000459
Tim Peters07e99cb2001-01-14 23:47:14 +0000460 Deleted messages are removed from writable mailbox.
461 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 (typ, [data]) = <instance>.close()
464 """
465 try:
466 typ, dat = self._simple_command('CLOSE')
467 finally:
468 self.state = 'AUTH'
469 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000470
471
Tim Peters07e99cb2001-01-14 23:47:14 +0000472 def copy(self, message_set, new_mailbox):
473 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000474
Tim Peters07e99cb2001-01-14 23:47:14 +0000475 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
476 """
477 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000478
479
Tim Peters07e99cb2001-01-14 23:47:14 +0000480 def create(self, mailbox):
481 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000482
Tim Peters07e99cb2001-01-14 23:47:14 +0000483 (typ, [data]) = <instance>.create(mailbox)
484 """
485 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000486
487
Tim Peters07e99cb2001-01-14 23:47:14 +0000488 def delete(self, mailbox):
489 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000490
Tim Peters07e99cb2001-01-14 23:47:14 +0000491 (typ, [data]) = <instance>.delete(mailbox)
492 """
493 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000494
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000495 def deleteacl(self, mailbox, who):
496 """Delete the ACLs (remove any rights) set for who on mailbox.
497
498 (typ, [data]) = <instance>.deleteacl(mailbox, who)
499 """
500 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000501
R David Murraya6429db2015-05-10 19:17:23 -0400502 def enable(self, capability):
503 """Send an RFC5161 enable string to the server.
504
505 (typ, [data]) = <intance>.enable(capability)
506 """
507 if 'ENABLE' not in self.capabilities:
508 raise IMAP4.error("Server does not support ENABLE")
509 typ, data = self._simple_command('ENABLE', capability)
510 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
511 self._mode_utf8()
512 return typ, data
513
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 def expunge(self):
515 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000516
Tim Peters07e99cb2001-01-14 23:47:14 +0000517 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000518
Tim Peters07e99cb2001-01-14 23:47:14 +0000519 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000520
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 'data' is list of 'EXPUNGE'd message numbers in order received.
522 """
523 name = 'EXPUNGE'
524 typ, dat = self._simple_command(name)
525 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000526
527
Tim Peters07e99cb2001-01-14 23:47:14 +0000528 def fetch(self, message_set, message_parts):
529 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000530
Tim Peters07e99cb2001-01-14 23:47:14 +0000531 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000532
Tim Peters07e99cb2001-01-14 23:47:14 +0000533 'message_parts' should be a string of selected parts
534 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000535
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 'data' are tuples of message part envelope and data.
537 """
538 name = 'FETCH'
539 typ, dat = self._simple_command(name, message_set, message_parts)
540 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000541
542
Piers Lauder15e5d532001-07-20 10:52:06 +0000543 def getacl(self, mailbox):
544 """Get the ACLs for a mailbox.
545
546 (typ, [data]) = <instance>.getacl(mailbox)
547 """
548 typ, dat = self._simple_command('GETACL', mailbox)
549 return self._untagged_response(typ, dat, 'ACL')
550
551
Piers Lauderd80ef022005-06-01 23:50:52 +0000552 def getannotation(self, mailbox, entry, attribute):
553 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
554 Retrieve ANNOTATIONs."""
555
556 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
557 return self._untagged_response(typ, dat, 'ANNOTATION')
558
559
Piers Lauder3fca2912002-06-17 07:07:20 +0000560 def getquota(self, root):
561 """Get the quota root's resource usage and limits.
562
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000563 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000564
565 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000566 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000567 typ, dat = self._simple_command('GETQUOTA', root)
568 return self._untagged_response(typ, dat, 'QUOTA')
569
570
571 def getquotaroot(self, mailbox):
572 """Get the list of quota roots for the named mailbox.
573
574 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000575 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000576 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000577 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
578 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000579 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000580
581
Tim Peters07e99cb2001-01-14 23:47:14 +0000582 def list(self, directory='""', pattern='*'):
583 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000584
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000586
Tim Peters07e99cb2001-01-14 23:47:14 +0000587 'data' is list of LIST responses.
588 """
589 name = 'LIST'
590 typ, dat = self._simple_command(name, directory, pattern)
591 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000592
593
Tim Peters07e99cb2001-01-14 23:47:14 +0000594 def login(self, user, password):
595 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000596
Tim Peters07e99cb2001-01-14 23:47:14 +0000597 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000598
Tim Peters07e99cb2001-01-14 23:47:14 +0000599 NB: 'password' will be quoted.
600 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000601 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
602 if typ != 'OK':
603 raise self.error(dat[-1])
604 self.state = 'AUTH'
605 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000606
607
Piers Laudere0273de2002-11-22 05:53:04 +0000608 def login_cram_md5(self, user, password):
609 """ Force use of CRAM-MD5 authentication.
610
611 (typ, [data]) = <instance>.login_cram_md5(user, password)
612 """
613 self.user, self.password = user, password
614 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
615
616
617 def _CRAM_MD5_AUTH(self, challenge):
618 """ Authobject to use with CRAM-MD5 authentication. """
619 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400620 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500621 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100622 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000623
624
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 def logout(self):
626 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000629
Tim Peters07e99cb2001-01-14 23:47:14 +0000630 Returns server 'BYE' response.
631 """
632 self.state = 'LOGOUT'
Victor Stinner74125a62019-04-15 18:23:20 +0200633 typ, dat = self._simple_command('LOGOUT')
Piers Lauder15e5d532001-07-20 10:52:06 +0000634 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000635 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
637
Tim Peters07e99cb2001-01-14 23:47:14 +0000638 def lsub(self, directory='""', pattern='*'):
639 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000640
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
Tim Peters07e99cb2001-01-14 23:47:14 +0000643 'data' are tuples of message part envelope and data.
644 """
645 name = 'LSUB'
646 typ, dat = self._simple_command(name, directory, pattern)
647 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000648
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000649 def myrights(self, mailbox):
650 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
651
652 (typ, [data]) = <instance>.myrights(mailbox)
653 """
654 typ,dat = self._simple_command('MYRIGHTS', mailbox)
655 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000656
Piers Lauder15e5d532001-07-20 10:52:06 +0000657 def namespace(self):
658 """ Returns IMAP namespaces ala rfc2342
659
660 (typ, [data, ...]) = <instance>.namespace()
661 """
662 name = 'NAMESPACE'
663 typ, dat = self._simple_command(name)
664 return self._untagged_response(typ, dat, name)
665
666
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 def noop(self):
668 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000669
Piers Laudere0273de2002-11-22 05:53:04 +0000670 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 """
672 if __debug__:
673 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000674 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000676
677
Tim Peters07e99cb2001-01-14 23:47:14 +0000678 def partial(self, message_num, message_part, start, length):
679 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000680
Tim Peters07e99cb2001-01-14 23:47:14 +0000681 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000682
Tim Peters07e99cb2001-01-14 23:47:14 +0000683 'data' is tuple of message part envelope and data.
684 """
685 name = 'PARTIAL'
686 typ, dat = self._simple_command(name, message_num, message_part, start, length)
687 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000688
689
Piers Laudere0273de2002-11-22 05:53:04 +0000690 def proxyauth(self, user):
691 """Assume authentication as "user".
692
693 Allows an authorised administrator to proxy into any user's
694 mailbox.
695
696 (typ, [data]) = <instance>.proxyauth(user)
697 """
698
699 name = 'PROXYAUTH'
700 return self._simple_command('PROXYAUTH', user)
701
702
Tim Peters07e99cb2001-01-14 23:47:14 +0000703 def rename(self, oldmailbox, newmailbox):
704 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000705
Piers Laudere0273de2002-11-22 05:53:04 +0000706 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000707 """
708 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000709
710
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 def search(self, charset, *criteria):
712 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000713
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000714 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000715
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 'data' is space separated list of matching message numbers.
R David Murraya6429db2015-05-10 19:17:23 -0400717 If UTF8 is enabled, charset MUST be None.
Tim Peters07e99cb2001-01-14 23:47:14 +0000718 """
719 name = 'SEARCH'
720 if charset:
R David Murraya6429db2015-05-10 19:17:23 -0400721 if self.utf8_enabled:
722 raise IMAP4.error("Non-None charset not valid in UTF8 mode")
Guido van Rossum68468eb2003-02-27 20:14:51 +0000723 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000724 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000725 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
728
Piers Lauder14f39402005-08-31 10:46:29 +0000729 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000731
Tim Peters07e99cb2001-01-14 23:47:14 +0000732 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000733
Piers Lauder14f39402005-08-31 10:46:29 +0000734 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000735
Tim Peters07e99cb2001-01-14 23:47:14 +0000736 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000737
738 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
739 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000741 self.untagged_responses = {} # Flush old responses.
742 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000743 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000744 name = 'EXAMINE'
745 else:
746 name = 'SELECT'
747 typ, dat = self._simple_command(name, mailbox)
748 if typ != 'OK':
749 self.state = 'AUTH' # Might have been 'SELECTED'
750 return typ, dat
751 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000752 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 and not readonly:
754 if __debug__:
755 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000756 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000757 raise self.readonly('%s is not writable' % mailbox)
758 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000759
760
Piers Lauder15e5d532001-07-20 10:52:06 +0000761 def setacl(self, mailbox, who, what):
762 """Set a mailbox acl.
763
Piers Lauderf167dc32004-03-25 00:12:21 +0000764 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000765 """
766 return self._simple_command('SETACL', mailbox, who, what)
767
768
Piers Lauderd80ef022005-06-01 23:50:52 +0000769 def setannotation(self, *args):
770 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
771 Set ANNOTATIONs."""
772
773 typ, dat = self._simple_command('SETANNOTATION', *args)
774 return self._untagged_response(typ, dat, 'ANNOTATION')
775
776
Piers Lauder3fca2912002-06-17 07:07:20 +0000777 def setquota(self, root, limits):
778 """Set the quota root's resource limits.
779
780 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000781 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000782 typ, dat = self._simple_command('SETQUOTA', root, limits)
783 return self._untagged_response(typ, dat, 'QUOTA')
784
785
Piers Lauder15e5d532001-07-20 10:52:06 +0000786 def sort(self, sort_criteria, charset, *search_criteria):
787 """IMAP4rev1 extension SORT command.
788
789 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
790 """
791 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000792 #if not name in self.capabilities: # Let the server decide!
793 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000794 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000795 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000796 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000797 return self._untagged_response(typ, dat, name)
798
799
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000800 def starttls(self, ssl_context=None):
801 name = 'STARTTLS'
802 if not HAVE_SSL:
803 raise self.error('SSL support missing')
804 if self._tls_established:
805 raise self.abort('TLS session already established')
806 if name not in self.capabilities:
807 raise self.abort('TLS not supported by server')
808 # Generate a default SSL context if none was passed.
809 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100810 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000811 typ, dat = self._simple_command(name)
812 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100813 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600814 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000815 self.file = self.sock.makefile('rb')
816 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000817 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000818 else:
819 raise self.error("Couldn't establish TLS session")
820 return self._untagged_response(typ, dat, name)
821
822
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 def status(self, mailbox, names):
824 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000825
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 (typ, [data]) = <instance>.status(mailbox, names)
827 """
828 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000829 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000830 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 typ, dat = self._simple_command(name, mailbox, names)
832 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000833
834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 def store(self, message_set, command, flags):
836 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000837
Tim Peters07e99cb2001-01-14 23:47:14 +0000838 (typ, [data]) = <instance>.store(message_set, command, flags)
839 """
840 if (flags[0],flags[-1]) != ('(',')'):
841 flags = '(%s)' % flags # Avoid quoting the flags
842 typ, dat = self._simple_command('STORE', message_set, command, flags)
843 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000844
845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 def subscribe(self, mailbox):
847 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000848
Tim Peters07e99cb2001-01-14 23:47:14 +0000849 (typ, [data]) = <instance>.subscribe(mailbox)
850 """
851 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000852
853
Martin v. Löwisd8921372003-11-10 06:44:44 +0000854 def thread(self, threading_algorithm, charset, *search_criteria):
855 """IMAPrev1 extension THREAD command.
856
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000857 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000858 """
859 name = 'THREAD'
860 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
861 return self._untagged_response(typ, dat, name)
862
863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 def uid(self, command, *args):
865 """Execute "command arg ..." with messages identified by UID,
866 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 Returns response appropriate to 'command'.
871 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000872 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000873 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 raise self.error("Unknown IMAP4 UID command: %s" % command)
875 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000876 raise self.error("command %s illegal in state %s, "
877 "only allowed in states %s" %
878 (command, self.state,
879 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000881 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000882 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000883 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 else:
885 name = 'FETCH'
886 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000887
888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 def unsubscribe(self, mailbox):
890 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000891
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 (typ, [data]) = <instance>.unsubscribe(mailbox)
893 """
894 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000895
896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 def xatom(self, name, *args):
898 """Allow simple extension commands
899 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000900
Piers Lauder15e5d532001-07-20 10:52:06 +0000901 Assumes command is legal in current state.
902
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000904
905 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000906 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000907 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000908 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000909 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000910 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000911 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000912 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000913
914
915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000917
918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000920 if dat is None:
921 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000922 ur = self.untagged_responses
923 if __debug__:
924 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000925 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000927 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 ur[typ].append(dat)
929 else:
930 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000931
932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 def _check_bye(self):
934 bye = self.untagged_responses.get('BYE')
935 if bye:
R David Murraya6429db2015-05-10 19:17:23 -0400936 raise self.abort(bye[-1].decode(self._encoding, 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000937
938
Tim Peters07e99cb2001-01-14 23:47:14 +0000939 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 if self.state not in Commands[name]:
942 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000943 raise self.error("command %s illegal in state %s, "
944 "only allowed in states %s" %
945 (name, self.state,
946 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000947
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000949 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000951
Raymond Hettinger54f02222002-06-01 14:18:47 +0000952 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 and not self.is_readonly:
954 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 tag = self._new_tag()
R David Murraya6429db2015-05-10 19:17:23 -0400957 name = bytes(name, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000958 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 for arg in args:
960 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000961 if isinstance(arg, str):
R David Murraya6429db2015-05-10 19:17:23 -0400962 arg = bytes(arg, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000963 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 literal = self.literal
966 if literal is not None:
967 self.literal = None
968 if type(literal) is type(self._command):
969 literator = literal
970 else:
971 literator = None
R David Murraya6429db2015-05-10 19:17:23 -0400972 data = data + bytes(' {%s}' % len(literal), self._encoding)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000973
Tim Peters07e99cb2001-01-14 23:47:14 +0000974 if __debug__:
975 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000976 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000978 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000981 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200982 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 if literal is None:
986 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 while 1:
989 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 while self._get_response():
992 if self.tagged_commands[tag]: # BAD/NO?
993 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 if literator:
998 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 if __debug__:
1001 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001002 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +00001003
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 try:
Piers Lauder15e5d532001-07-20 10:52:06 +00001005 self.send(literal)
1006 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +02001007 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001008 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 if not literator:
1011 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001012
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001014
1015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 def _command_complete(self, name, tag):
Victor Stinner74125a62019-04-15 18:23:20 +02001017 logout = (name == 'LOGOUT')
Antoine Pitroudac47912010-11-10 00:18:40 +00001018 # BYE is expected after LOGOUT
Victor Stinner74125a62019-04-15 18:23:20 +02001019 if not logout:
Antoine Pitroudac47912010-11-10 00:18:40 +00001020 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 try:
Victor Stinner74125a62019-04-15 18:23:20 +02001022 typ, data = self._get_tagged_response(tag, expect_bye=logout)
Guido van Rossumb940e112007-01-10 16:19:56 +00001023 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001025 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 raise self.error('command: %s => %s' % (name, val))
Victor Stinner74125a62019-04-15 18:23:20 +02001027 if not logout:
Antoine Pitroudac47912010-11-10 00:18:40 +00001028 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001029 if typ == 'BAD':
1030 raise self.error('%s command error: %s %s' % (name, typ, data))
1031 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001032
1033
Antoine Pitroudbe75192010-11-16 17:55:26 +00001034 def _get_capabilities(self):
1035 typ, dat = self.capability()
1036 if dat == [None]:
1037 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001038 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001039 dat = dat.upper()
1040 self.capabilities = tuple(dat.split())
1041
1042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
Tim Peters07e99cb2001-01-14 23:47:14 +00001045 # Read response and store.
1046 #
1047 # Returns None for continuation responses,
1048 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001053
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 if self._match(self.tagre, resp):
1055 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001056 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001057 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001058
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001060 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 dat = self.mo.group('data')
1062 self.tagged_commands[tag] = (typ, [dat])
1063 else:
1064 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001069 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001071
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 if self.mo is None:
1073 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001074
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 if self._match(Continuation, resp):
1076 self.continuation_response = self.mo.group('data')
1077 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001078
R David Murraya6429db2015-05-10 19:17:23 -04001079 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001080
Tim Peters07e99cb2001-01-14 23:47:14 +00001081 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001082 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001084 if dat is None: dat = b'' # Null untagged response
1085 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001086
Tim Peters07e99cb2001-01-14 23:47:14 +00001087 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001088
R David Murraya6429db2015-05-10 19:17:23 -04001089 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001090
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001092
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001093 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001094 if __debug__:
1095 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001096 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001097 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001098
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001100
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001102
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001104
Tim Peters07e99cb2001-01-14 23:47:14 +00001105 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001106
Tim Peters07e99cb2001-01-14 23:47:14 +00001107 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001108
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001110
Tim Peters07e99cb2001-01-14 23:47:14 +00001111 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001112 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001113 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001114 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 if __debug__:
1117 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001118 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001119
Tim Peters07e99cb2001-01-14 23:47:14 +00001120 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001121
1122
Victor Stinner74125a62019-04-15 18:23:20 +02001123 def _get_tagged_response(self, tag, expect_bye=False):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001124
Tim Peters07e99cb2001-01-14 23:47:14 +00001125 while 1:
1126 result = self.tagged_commands[tag]
1127 if result is not None:
1128 del self.tagged_commands[tag]
1129 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001130
Victor Stinner74125a62019-04-15 18:23:20 +02001131 if expect_bye:
1132 typ = 'BYE'
1133 bye = self.untagged_responses.pop(typ, None)
1134 if bye is not None:
1135 # Server replies to the "LOGOUT" command with "BYE"
1136 return (typ, bye)
1137
R David Murray95ff7232014-02-07 13:44:57 -05001138 # If we've seen a BYE at this point, the socket will be
1139 # closed, so report the BYE now.
R David Murray95ff7232014-02-07 13:44:57 -05001140 self._check_bye()
1141
Tim Peters07e99cb2001-01-14 23:47:14 +00001142 # Some have reported "unexpected response" exceptions.
1143 # Note that ignoring them here causes loops.
1144 # Instead, send me details of the unexpected response and
1145 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001146
Tim Peters07e99cb2001-01-14 23:47:14 +00001147 try:
1148 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001149 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001150 if __debug__:
1151 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001152 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001153 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001154
1155
Tim Peters07e99cb2001-01-14 23:47:14 +00001156 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001157
Piers Lauder15e5d532001-07-20 10:52:06 +00001158 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001159 if not line:
1160 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001161
Tim Peters07e99cb2001-01-14 23:47:14 +00001162 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001163 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001164 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001165
Tim Peters07e99cb2001-01-14 23:47:14 +00001166 line = line[:-2]
1167 if __debug__:
1168 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001169 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001170 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001171 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001172 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001173
1174
Tim Peters07e99cb2001-01-14 23:47:14 +00001175 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001176
Tim Peters07e99cb2001-01-14 23:47:14 +00001177 # Run compiled regular expression match method on 's'.
1178 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001179
Tim Peters07e99cb2001-01-14 23:47:14 +00001180 self.mo = cre.match(s)
1181 if __debug__:
1182 if self.mo is not None and self.debug >= 5:
Serhiy Storchakaa4a30202017-11-28 22:54:42 +02001183 self._mesg("\tmatched %r => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001184 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001185
1186
Tim Peters07e99cb2001-01-14 23:47:14 +00001187 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001188
R David Murraya6429db2015-05-10 19:17:23 -04001189 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001190 self.tagnum = self.tagnum + 1
1191 self.tagged_commands[tag] = None
1192 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001193
1194
Tim Peters07e99cb2001-01-14 23:47:14 +00001195 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001196
Antoine Pitroub1436f12010-11-09 22:55:55 +00001197 arg = arg.replace('\\', '\\\\')
1198 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001199
Antoine Pitroub1436f12010-11-09 22:55:55 +00001200 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001201
1202
Tim Peters07e99cb2001-01-14 23:47:14 +00001203 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001204
Guido van Rossum68468eb2003-02-27 20:14:51 +00001205 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001206
1207
Tim Peters07e99cb2001-01-14 23:47:14 +00001208 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001209 if typ == 'NO':
1210 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001211 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001212 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001213 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001214 if __debug__:
1215 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001216 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001217 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001218
1219
Piers Lauderf2d7d152002-02-22 01:15:17 +00001220 if __debug__:
1221
1222 def _mesg(self, s, secs=None):
1223 if secs is None:
1224 secs = time.time()
1225 tm = time.strftime('%M:%S', time.localtime(secs))
1226 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1227 sys.stderr.flush()
1228
1229 def _dump_ur(self, dict):
1230 # Dump untagged responses (in `dict').
1231 l = dict.items()
1232 if not l: return
1233 t = '\n\t\t'
1234 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1235 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1236
1237 def _log(self, line):
1238 # Keep log of last `_cmd_log_len' interactions for debugging.
1239 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1240 self._cmd_log_idx += 1
1241 if self._cmd_log_idx >= self._cmd_log_len:
1242 self._cmd_log_idx = 0
1243
1244 def print_log(self):
1245 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1246 i, n = self._cmd_log_idx, self._cmd_log_len
1247 while n:
1248 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001249 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001250 except:
1251 pass
1252 i += 1
1253 if i >= self._cmd_log_len:
1254 i = 0
1255 n -= 1
1256
1257
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001258if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001259
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001260 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001261
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001262 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001263
Antoine Pitrou08728162011-05-06 18:49:52 +02001264 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001265
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001266 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001267 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001268 keyfile - PEM formatted file that contains your private key (default: None);
1269 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001270 ssl_context - a SSLContext object that contains your certificate chain
1271 and private key (default: None)
1272 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001273 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001274
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001275 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001276 """
Piers Laudera4f83132002-03-08 01:53:24 +00001277
1278
R David Murraya6429db2015-05-10 19:17:23 -04001279 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
1280 certfile=None, ssl_context=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001281 if ssl_context is not None and keyfile is not None:
1282 raise ValueError("ssl_context and keyfile arguments are mutually "
1283 "exclusive")
1284 if ssl_context is not None and certfile is not None:
1285 raise ValueError("ssl_context and certfile arguments are mutually "
1286 "exclusive")
Christian Heimesd0486372016-09-10 23:23:33 +02001287 if keyfile is not None or certfile is not None:
1288 import warnings
Pablo Aguiar4b5e62d2018-11-01 11:33:35 +01001289 warnings.warn("keyfile and certfile are deprecated, use a "
Christian Heimesd0486372016-09-10 23:23:33 +02001290 "custom ssl_context instead", DeprecationWarning, 2)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001291 self.keyfile = keyfile
1292 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001293 if ssl_context is None:
1294 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1295 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001296 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001297 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001298
Christian Heimesfb5faf02008-11-05 19:39:50 +00001299 def _create_socket(self):
1300 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001301 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001302 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001303
Christian Heimesfb5faf02008-11-05 19:39:50 +00001304 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001305 """Setup connection to remote server on "host:port".
1306 (default: localhost:standard IMAP4 SSL port).
1307 This connection will be used by the routines:
1308 read, readline, send, shutdown.
1309 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001310 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001311
1312 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001313
1314
Piers Laudere0273de2002-11-22 05:53:04 +00001315class IMAP4_stream(IMAP4):
1316
1317 """IMAP4 client class over a stream
1318
1319 Instantiate with: IMAP4_stream(command)
1320
R David Murraya6429db2015-05-10 19:17:23 -04001321 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001322
1323 for more documentation see the docstring of the parent class IMAP4.
1324 """
1325
1326
1327 def __init__(self, command):
1328 self.command = command
1329 IMAP4.__init__(self)
1330
1331
1332 def open(self, host = None, port = None):
1333 """Setup a stream connection.
1334 This connection will be used by the routines:
1335 read, readline, send, shutdown.
1336 """
1337 self.host = None # For compatibility with parent class
1338 self.port = None
1339 self.sock = None
1340 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001341 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001342 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001343 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1344 shell=True, close_fds=True)
1345 self.writefile = self.process.stdin
1346 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001347
1348 def read(self, size):
1349 """Read 'size' bytes from remote."""
1350 return self.readfile.read(size)
1351
1352
1353 def readline(self):
1354 """Read line from remote."""
1355 return self.readfile.readline()
1356
1357
1358 def send(self, data):
1359 """Send data to remote."""
1360 self.writefile.write(data)
1361 self.writefile.flush()
1362
1363
1364 def shutdown(self):
1365 """Close I/O established in "open"."""
1366 self.readfile.close()
1367 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001368 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001369
1370
1371
Guido van Rossumeda960a1998-06-18 14:24:28 +00001372class _Authenticator:
1373
Tim Peters07e99cb2001-01-14 23:47:14 +00001374 """Private class to provide en/decoding
1375 for base64-based authentication conversation.
1376 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001377
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 def __init__(self, mechinst):
1379 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001380
Tim Peters07e99cb2001-01-14 23:47:14 +00001381 def process(self, data):
1382 ret = self.mech(self.decode(data))
1383 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001384 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001385 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001386
Tim Peters07e99cb2001-01-14 23:47:14 +00001387 def encode(self, inp):
1388 #
1389 # Invoke binascii.b2a_base64 iteratively with
1390 # short even length buffers, strip the trailing
1391 # line feed from the result and append. "Even"
1392 # means a number that factors to both 6 and 8,
1393 # so when it gets to the end of the 8-bit input
1394 # there's no partial 6-bit output.
1395 #
R David Murray774a39f2013-02-19 12:17:31 -05001396 oup = b''
1397 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001398 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001399 while inp:
1400 if len(inp) > 48:
1401 t = inp[:48]
1402 inp = inp[48:]
1403 else:
1404 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001405 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001406 e = binascii.b2a_base64(t)
1407 if e:
1408 oup = oup + e[:-1]
1409 return oup
1410
1411 def decode(self, inp):
1412 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001413 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001414 return binascii.a2b_base64(inp)
1415
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001416Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1417Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001418
1419def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001420 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001421
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001422 Return corresponding local time. The return value is a
1423 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001425
Tim Peters07e99cb2001-01-14 23:47:14 +00001426 mo = InternalDate.match(resp)
1427 if not mo:
1428 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 mon = Mon2num[mo.group('mon')]
1431 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001433 day = int(mo.group('day'))
1434 year = int(mo.group('year'))
1435 hour = int(mo.group('hour'))
1436 min = int(mo.group('min'))
1437 sec = int(mo.group('sec'))
1438 zoneh = int(mo.group('zoneh'))
1439 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001440
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001442
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001444 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001446
Tim Peters07e99cb2001-01-14 23:47:14 +00001447 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001448 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001449
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001450 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001451
1452
1453
1454def Int2AP(num):
1455
Tim Peters07e99cb2001-01-14 23:47:14 +00001456 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001457
Christian Heimesfb5faf02008-11-05 19:39:50 +00001458 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001459 num = int(abs(num))
1460 while num:
1461 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001462 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001463 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001464
1465
1466
1467def ParseFlags(resp):
1468
Tim Peters07e99cb2001-01-14 23:47:14 +00001469 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001470
Tim Peters07e99cb2001-01-14 23:47:14 +00001471 mo = Flags.match(resp)
1472 if not mo:
1473 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001474
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001475 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001476
1477
1478def Time2Internaldate(date_time):
1479
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001480 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001481
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001482 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001483 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001484 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001485 representing local time, an instance of time.struct_time (as
1486 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001487 double-quoted string. In the last case, it is assumed to already
1488 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001489 """
Fred Drakedb519202002-01-05 17:17:09 +00001490 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001491 dt = datetime.fromtimestamp(date_time,
1492 timezone.utc).astimezone()
1493 elif isinstance(date_time, tuple):
1494 try:
1495 gmtoff = date_time.tm_gmtoff
1496 except AttributeError:
1497 if time.daylight:
1498 dst = date_time[8]
1499 if dst == -1:
1500 dst = time.localtime(time.mktime(date_time))[8]
1501 gmtoff = -(time.timezone, time.altzone)[dst]
1502 else:
1503 gmtoff = -time.timezone
1504 delta = timedelta(seconds=gmtoff)
1505 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1506 elif isinstance(date_time, datetime):
1507 if date_time.tzinfo is None:
1508 raise ValueError("date_time must be aware")
1509 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001510 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001511 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001512 else:
1513 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001514 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1515 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001516
1517
1518
Guido van Rossum8c062211999-12-13 23:27:45 +00001519if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001520
Piers Laudere0273de2002-11-22 05:53:04 +00001521 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1522 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1523 # to test the IMAP4_stream class
1524
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001525 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001526
Tim Peters07e99cb2001-01-14 23:47:14 +00001527 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001528 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001529 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001530 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001531
Piers Laudere0273de2002-11-22 05:53:04 +00001532 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001533 for opt,val in optlist:
1534 if opt == '-d':
1535 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001536 elif opt == '-s':
1537 stream_command = val
1538 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001539
Tim Peters07e99cb2001-01-14 23:47:14 +00001540 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001541
Tim Peters07e99cb2001-01-14 23:47:14 +00001542 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001543
Tim Peters07e99cb2001-01-14 23:47:14 +00001544 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001545 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001546
Piers Lauder47404ff2003-04-29 23:40:59 +00001547 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 +00001548 test_seq1 = (
1549 ('login', (USER, PASSWD)),
1550 ('create', ('/tmp/xxx 1',)),
1551 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1552 ('CREATE', ('/tmp/yyz 2',)),
1553 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1554 ('list', ('/tmp', 'yy*')),
1555 ('select', ('/tmp/yyz 2',)),
1556 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001557 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
R David Murray44b548d2016-09-08 13:59:53 -04001558 ('store', ('1', 'FLAGS', r'(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001559 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001560 ('expunge', ()),
1561 ('recent', ()),
1562 ('close', ()),
1563 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001564
Tim Peters07e99cb2001-01-14 23:47:14 +00001565 test_seq2 = (
1566 ('select', ()),
1567 ('response',('UIDVALIDITY',)),
1568 ('uid', ('SEARCH', 'ALL')),
1569 ('response', ('EXISTS',)),
1570 ('append', (None, None, None, test_mesg)),
1571 ('recent', ()),
1572 ('logout', ()),
1573 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001574
Tim Peters07e99cb2001-01-14 23:47:14 +00001575 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001576 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001577 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001578 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001579 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001580 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001581
Tim Peters07e99cb2001-01-14 23:47:14 +00001582 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001583 if stream_command:
1584 M = IMAP4_stream(stream_command)
1585 else:
1586 M = IMAP4(host)
1587 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001588 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001589 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001590 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001591
Tim Peters07e99cb2001-01-14 23:47:14 +00001592 for cmd,args in test_seq1:
1593 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001594
Tim Peters07e99cb2001-01-14 23:47:14 +00001595 for ml in run('list', ('/tmp/', 'yy%')):
1596 mo = re.match(r'.*"([^"]+)"$', ml)
1597 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001598 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001599 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001600
Tim Peters07e99cb2001-01-14 23:47:14 +00001601 for cmd,args in test_seq2:
1602 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001603
Tim Peters07e99cb2001-01-14 23:47:14 +00001604 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1605 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001606
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001607 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001608 if not uid: continue
1609 run('uid', ('FETCH', '%s' % uid[-1],
1610 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001611
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001612 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001613
Tim Peters07e99cb2001-01-14 23:47:14 +00001614 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001615 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001616
Tim Peters07e99cb2001-01-14 23:47:14 +00001617 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001618 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001619If you would like to see debugging output,
1620try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001621''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001622
Tim Peters07e99cb2001-01-14 23:47:14 +00001623 raise