blob: abfdd737779a0da372bdd08cf10c70132d2cc1b4 [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
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900138 Instantiate with: IMAP4([host[, port[, timeout=None]]])
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).
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900142 timeout - socket timeout (default: None)
143 If timeout is not given or is None,
144 the global default socket timeout is used
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000145
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 All IMAP4rev1 commands are supported by methods of the same
147 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000148
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 All arguments to commands are converted to strings, except for
150 AUTHENTICATE, and the last argument to APPEND which is passed as
151 an IMAP4 literal. If necessary (the string contains any
152 non-printing characters or white-space and isn't enclosed with
153 either parentheses or double quotes) each string is quoted.
154 However, the 'password' argument to the LOGIN command is always
155 quoted. If you want to avoid having an argument string quoted
156 (eg: the 'flags' argument to STORE) then enclose the string in
157 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000158
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 Each command returns a tuple: (type, [data, ...]) where 'type'
160 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000161 tagged response, or untagged results from command. Each 'data'
162 is either a string, or a tuple. If a tuple, then the first part
163 is the header of the response, and the second part contains
164 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000165
Tim Peters07e99cb2001-01-14 23:47:14 +0000166 Errors raise the exception class <instance>.error("<reason>").
167 IMAP4 server errors raise <instance>.abort("<reason>"),
168 which is a sub-class of 'error'. Mailbox status changes
169 from READ-WRITE to READ-ONLY raise the exception class
170 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000171
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 "error" exceptions imply a program error.
173 "abort" exceptions imply the connection should be reset, and
174 the command re-tried.
175 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000176
Piers Lauderd80ef022005-06-01 23:50:52 +0000177 Note: to use this module, you must read the RFCs pertaining to the
178 IMAP4 protocol, as the semantics of the arguments to each IMAP4
179 command are left to the invoker, not to mention the results. Also,
180 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000181 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000182
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 class error(Exception): pass # Logical errors - debug required
184 class abort(error): pass # Service errors - close and retry
185 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000186
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900187 def __init__(self, host='', port=IMAP4_PORT, timeout=None):
Tim Peters07e99cb2001-01-14 23:47:14 +0000188 self.debug = Debug
189 self.state = 'LOGOUT'
190 self.literal = None # A literal argument to a command
191 self.tagged_commands = {} # Tagged commands awaiting response
192 self.untagged_responses = {} # {typ: [data, ...], ...}
193 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000194 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000196 self._tls_established = False
R David Murraya6429db2015-05-10 19:17:23 -0400197 self._mode_ascii()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000198
Tim Peters07e99cb2001-01-14 23:47:14 +0000199 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000200
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900201 self.open(host, port, timeout)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000202
Victor Stinner33e649c2011-01-05 23:01:37 +0000203 try:
204 self._connect()
205 except Exception:
206 try:
207 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200208 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000209 pass
210 raise
211
R David Murraya6429db2015-05-10 19:17:23 -0400212 def _mode_ascii(self):
213 self.utf8_enabled = False
214 self._encoding = 'ascii'
215 self.Literal = re.compile(_Literal, re.ASCII)
216 self.Untagged_status = re.compile(_Untagged_status, re.ASCII)
217
218
219 def _mode_utf8(self):
220 self.utf8_enabled = True
221 self._encoding = 'utf-8'
222 self.Literal = re.compile(_Literal)
223 self.Untagged_status = re.compile(_Untagged_status)
224
Victor Stinner33e649c2011-01-05 23:01:37 +0000225
226 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000227 # Create unique tag for this session,
228 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000229
Piers Lauder2dfc1682005-07-05 04:20:07 +0000230 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000231 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000232 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000233 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000234
Tim Peters07e99cb2001-01-14 23:47:14 +0000235 # Get server welcome message,
236 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000237
Tim Peters07e99cb2001-01-14 23:47:14 +0000238 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000239 self._cmd_log_len = 10
240 self._cmd_log_idx = 0
241 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000242 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000243 self._mesg('imaplib version %s' % __version__)
244 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000245
Tim Peters07e99cb2001-01-14 23:47:14 +0000246 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000247 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000248 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000249 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000250 self.state = 'NONAUTH'
251 else:
252 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000253
Antoine Pitroudbe75192010-11-16 17:55:26 +0000254 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000255 if __debug__:
256 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000257 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000258
Tim Peters07e99cb2001-01-14 23:47:14 +0000259 for version in AllowedVersions:
260 if not version in self.capabilities:
261 continue
262 self.PROTOCOL_VERSION = version
263 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000264
Tim Peters07e99cb2001-01-14 23:47:14 +0000265 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000266
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000267
Tim Peters07e99cb2001-01-14 23:47:14 +0000268 def __getattr__(self, attr):
269 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000270 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000271 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000272 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000273
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300274 def __enter__(self):
275 return self
276
277 def __exit__(self, *args):
Victor Stinner74125a62019-04-15 18:23:20 +0200278 if self.state == "LOGOUT":
279 return
280
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300281 try:
282 self.logout()
283 except OSError:
284 pass
Guido van Rossum26367a01998-09-28 15:34:46 +0000285
286
Piers Lauder15e5d532001-07-20 10:52:06 +0000287 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000288
289
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900290 def _create_socket(self, timeout):
Berker Peksage4dcbbd2018-08-07 05:12:18 +0300291 # Default value of IMAP4.host is '', but socket.getaddrinfo()
292 # (which is used by socket.create_connection()) expects None
293 # as a default value for host.
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900294 if timeout is not None and not timeout:
295 raise ValueError('Non-blocking socket (timeout=0) is not supported')
Berker Peksage4dcbbd2018-08-07 05:12:18 +0300296 host = None if not self.host else self.host
Steve Dower44f91c32019-06-27 10:47:59 -0700297 sys.audit("imaplib.open", self, self.host, self.port)
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900298 address = (host, self.port)
299 if timeout is not None:
300 return socket.create_connection(address, timeout)
301 return socket.create_connection(address)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000302
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900303 def open(self, host='', port=IMAP4_PORT, timeout=None):
Piers Lauderf97b2d72002-06-05 22:31:57 +0000304 """Setup connection to remote server on "host:port"
305 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000306 This connection will be used by the routines:
307 read, readline, send, shutdown.
308 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000309 self.host = host
310 self.port = port
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900311 self.sock = self._create_socket(timeout)
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000312 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000313
314
Piers Lauder15e5d532001-07-20 10:52:06 +0000315 def read(self, size):
316 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200317 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000318
319
320 def readline(self):
321 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100322 line = self.file.readline(_MAXLINE + 1)
323 if len(line) > _MAXLINE:
324 raise self.error("got more than %d bytes" % _MAXLINE)
325 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000326
327
328 def send(self, data):
329 """Send data to remote."""
Steve Dower44f91c32019-06-27 10:47:59 -0700330 sys.audit("imaplib.send", self, data)
Martin v. Löwise12454f2002-02-16 23:06:19 +0000331 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000332
Piers Lauderf2d7d152002-02-22 01:15:17 +0000333
Piers Lauder15e5d532001-07-20 10:52:06 +0000334 def shutdown(self):
335 """Close I/O established in "open"."""
336 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000337 try:
338 self.sock.shutdown(socket.SHUT_RDWR)
Victor Stinner83a2c282017-05-15 17:33:45 +0200339 except OSError as exc:
340 # The server might already have closed the connection.
341 # On Windows, this may result in WSAEINVAL (error 10022):
342 # An invalid operation was attempted.
343 if (exc.errno != errno.ENOTCONN
344 and getattr(exc, 'winerror', 0) != 10022):
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000345 raise
346 finally:
347 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000348
349
350 def socket(self):
351 """Return socket instance used to connect to IMAP4 server.
352
353 socket = <instance>.socket()
354 """
355 return self.sock
356
357
358
359 # Utility methods
360
361
Tim Peters07e99cb2001-01-14 23:47:14 +0000362 def recent(self):
363 """Return most recent 'RECENT' responses if any exist,
364 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000365
Tim Peters07e99cb2001-01-14 23:47:14 +0000366 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000367
Tim Peters07e99cb2001-01-14 23:47:14 +0000368 'data' is None if no new messages,
369 else list of RECENT responses, most recent last.
370 """
371 name = 'RECENT'
372 typ, dat = self._untagged_response('OK', [None], name)
373 if dat[-1]:
374 return typ, dat
375 typ, dat = self.noop() # Prod server for response
376 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000377
378
Tim Peters07e99cb2001-01-14 23:47:14 +0000379 def response(self, code):
380 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 (code, [data]) = <instance>.response(code)
385 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000386 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000387
388
Guido van Rossum26367a01998-09-28 15:34:46 +0000389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000391
392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 def append(self, mailbox, flags, date_time, message):
394 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 All args except `message' can be None.
399 """
400 name = 'APPEND'
401 if not mailbox:
402 mailbox = 'INBOX'
403 if flags:
404 if (flags[0],flags[-1]) != ('(',')'):
405 flags = '(%s)' % flags
406 else:
407 flags = None
408 if date_time:
409 date_time = Time2Internaldate(date_time)
410 else:
411 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400412 literal = MapCRLF.sub(CRLF, message)
413 if self.utf8_enabled:
414 literal = b'UTF8 (' + literal + b')'
415 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 def authenticate(self, mechanism, authobject):
420 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000421
Tim Peters07e99cb2001-01-14 23:47:14 +0000422 'mechanism' specifies which authentication mechanism is to
423 be used - it must appear in <instance>.capabilities in the
424 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000425
Tim Peters07e99cb2001-01-14 23:47:14 +0000426 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000429
R David Murray774a39f2013-02-19 12:17:31 -0500430 It will be called to process server continuation responses; the
431 response argument it is passed will be a bytes. It should return bytes
432 data that will be base64 encoded and sent to the server. It should
433 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000434 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000435 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000436 # XXX: shouldn't this code be removed, not commented out?
437 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000438 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000439 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 self.literal = _Authenticator(authobject).process
441 typ, dat = self._simple_command('AUTHENTICATE', mech)
442 if typ != 'OK':
R David Murrayb079c072016-12-24 21:32:26 -0500443 raise self.error(dat[-1].decode('utf-8', 'replace'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 self.state = 'AUTH'
445 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000446
447
Piers Lauderd80ef022005-06-01 23:50:52 +0000448 def capability(self):
449 """(typ, [data]) = <instance>.capability()
450 Fetch capabilities list from server."""
451
452 name = 'CAPABILITY'
453 typ, dat = self._simple_command(name)
454 return self._untagged_response(typ, dat, name)
455
456
Tim Peters07e99cb2001-01-14 23:47:14 +0000457 def check(self):
458 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000459
Tim Peters07e99cb2001-01-14 23:47:14 +0000460 (typ, [data]) = <instance>.check()
461 """
462 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000463
464
Tim Peters07e99cb2001-01-14 23:47:14 +0000465 def close(self):
466 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000467
Tim Peters07e99cb2001-01-14 23:47:14 +0000468 Deleted messages are removed from writable mailbox.
469 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000470
Tim Peters07e99cb2001-01-14 23:47:14 +0000471 (typ, [data]) = <instance>.close()
472 """
473 try:
474 typ, dat = self._simple_command('CLOSE')
475 finally:
476 self.state = 'AUTH'
477 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000478
479
Tim Peters07e99cb2001-01-14 23:47:14 +0000480 def copy(self, message_set, new_mailbox):
481 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000482
Tim Peters07e99cb2001-01-14 23:47:14 +0000483 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
484 """
485 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000486
487
Tim Peters07e99cb2001-01-14 23:47:14 +0000488 def create(self, mailbox):
489 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000490
Tim Peters07e99cb2001-01-14 23:47:14 +0000491 (typ, [data]) = <instance>.create(mailbox)
492 """
493 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000494
495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 def delete(self, mailbox):
497 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000498
Tim Peters07e99cb2001-01-14 23:47:14 +0000499 (typ, [data]) = <instance>.delete(mailbox)
500 """
501 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000502
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000503 def deleteacl(self, mailbox, who):
504 """Delete the ACLs (remove any rights) set for who on mailbox.
505
506 (typ, [data]) = <instance>.deleteacl(mailbox, who)
507 """
508 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000509
R David Murraya6429db2015-05-10 19:17:23 -0400510 def enable(self, capability):
511 """Send an RFC5161 enable string to the server.
512
Min ho Kimc4cacc82019-07-31 08:16:13 +1000513 (typ, [data]) = <instance>.enable(capability)
R David Murraya6429db2015-05-10 19:17:23 -0400514 """
515 if 'ENABLE' not in self.capabilities:
516 raise IMAP4.error("Server does not support ENABLE")
517 typ, data = self._simple_command('ENABLE', capability)
518 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
519 self._mode_utf8()
520 return typ, data
521
Tim Peters07e99cb2001-01-14 23:47:14 +0000522 def expunge(self):
523 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000524
Tim Peters07e99cb2001-01-14 23:47:14 +0000525 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000526
Tim Peters07e99cb2001-01-14 23:47:14 +0000527 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000528
Tim Peters07e99cb2001-01-14 23:47:14 +0000529 'data' is list of 'EXPUNGE'd message numbers in order received.
530 """
531 name = 'EXPUNGE'
532 typ, dat = self._simple_command(name)
533 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000534
535
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 def fetch(self, message_set, message_parts):
537 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000538
Tim Peters07e99cb2001-01-14 23:47:14 +0000539 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000540
Tim Peters07e99cb2001-01-14 23:47:14 +0000541 'message_parts' should be a string of selected parts
542 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000543
Tim Peters07e99cb2001-01-14 23:47:14 +0000544 'data' are tuples of message part envelope and data.
545 """
546 name = 'FETCH'
547 typ, dat = self._simple_command(name, message_set, message_parts)
548 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000549
550
Piers Lauder15e5d532001-07-20 10:52:06 +0000551 def getacl(self, mailbox):
552 """Get the ACLs for a mailbox.
553
554 (typ, [data]) = <instance>.getacl(mailbox)
555 """
556 typ, dat = self._simple_command('GETACL', mailbox)
557 return self._untagged_response(typ, dat, 'ACL')
558
559
Piers Lauderd80ef022005-06-01 23:50:52 +0000560 def getannotation(self, mailbox, entry, attribute):
561 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
562 Retrieve ANNOTATIONs."""
563
564 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
565 return self._untagged_response(typ, dat, 'ANNOTATION')
566
567
Piers Lauder3fca2912002-06-17 07:07:20 +0000568 def getquota(self, root):
569 """Get the quota root's resource usage and limits.
570
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000571 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000572
573 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000574 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000575 typ, dat = self._simple_command('GETQUOTA', root)
576 return self._untagged_response(typ, dat, 'QUOTA')
577
578
579 def getquotaroot(self, mailbox):
580 """Get the list of quota roots for the named mailbox.
581
582 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000583 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000584 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000585 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
586 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000587 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000588
589
Tim Peters07e99cb2001-01-14 23:47:14 +0000590 def list(self, directory='""', pattern='*'):
591 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000592
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 'data' is list of LIST responses.
596 """
597 name = 'LIST'
598 typ, dat = self._simple_command(name, directory, pattern)
599 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000600
601
Tim Peters07e99cb2001-01-14 23:47:14 +0000602 def login(self, user, password):
603 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000604
Tim Peters07e99cb2001-01-14 23:47:14 +0000605 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000606
Tim Peters07e99cb2001-01-14 23:47:14 +0000607 NB: 'password' will be quoted.
608 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000609 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
610 if typ != 'OK':
611 raise self.error(dat[-1])
612 self.state = 'AUTH'
613 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000614
615
Piers Laudere0273de2002-11-22 05:53:04 +0000616 def login_cram_md5(self, user, password):
617 """ Force use of CRAM-MD5 authentication.
618
619 (typ, [data]) = <instance>.login_cram_md5(user, password)
620 """
621 self.user, self.password = user, password
622 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
623
624
625 def _CRAM_MD5_AUTH(self, challenge):
626 """ Authobject to use with CRAM-MD5 authentication. """
627 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400628 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500629 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100630 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000631
632
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 def logout(self):
634 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
Tim Peters07e99cb2001-01-14 23:47:14 +0000636 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000637
Tim Peters07e99cb2001-01-14 23:47:14 +0000638 Returns server 'BYE' response.
639 """
640 self.state = 'LOGOUT'
Victor Stinner74125a62019-04-15 18:23:20 +0200641 typ, dat = self._simple_command('LOGOUT')
Piers Lauder15e5d532001-07-20 10:52:06 +0000642 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000643 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000644
645
Tim Peters07e99cb2001-01-14 23:47:14 +0000646 def lsub(self, directory='""', pattern='*'):
647 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000648
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000650
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 'data' are tuples of message part envelope and data.
652 """
653 name = 'LSUB'
654 typ, dat = self._simple_command(name, directory, pattern)
655 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000656
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000657 def myrights(self, mailbox):
658 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
659
660 (typ, [data]) = <instance>.myrights(mailbox)
661 """
662 typ,dat = self._simple_command('MYRIGHTS', mailbox)
663 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000664
Piers Lauder15e5d532001-07-20 10:52:06 +0000665 def namespace(self):
666 """ Returns IMAP namespaces ala rfc2342
667
668 (typ, [data, ...]) = <instance>.namespace()
669 """
670 name = 'NAMESPACE'
671 typ, dat = self._simple_command(name)
672 return self._untagged_response(typ, dat, name)
673
674
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 def noop(self):
676 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000677
Piers Laudere0273de2002-11-22 05:53:04 +0000678 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000679 """
680 if __debug__:
681 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000682 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000683 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000684
685
Tim Peters07e99cb2001-01-14 23:47:14 +0000686 def partial(self, message_num, message_part, start, length):
687 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000688
Tim Peters07e99cb2001-01-14 23:47:14 +0000689 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000690
Tim Peters07e99cb2001-01-14 23:47:14 +0000691 'data' is tuple of message part envelope and data.
692 """
693 name = 'PARTIAL'
694 typ, dat = self._simple_command(name, message_num, message_part, start, length)
695 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000696
697
Piers Laudere0273de2002-11-22 05:53:04 +0000698 def proxyauth(self, user):
699 """Assume authentication as "user".
700
701 Allows an authorised administrator to proxy into any user's
702 mailbox.
703
704 (typ, [data]) = <instance>.proxyauth(user)
705 """
706
707 name = 'PROXYAUTH'
708 return self._simple_command('PROXYAUTH', user)
709
710
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 def rename(self, oldmailbox, newmailbox):
712 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000713
Piers Laudere0273de2002-11-22 05:53:04 +0000714 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000715 """
716 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000717
718
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 def search(self, charset, *criteria):
720 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000721
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000722 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000723
Tim Peters07e99cb2001-01-14 23:47:14 +0000724 'data' is space separated list of matching message numbers.
R David Murraya6429db2015-05-10 19:17:23 -0400725 If UTF8 is enabled, charset MUST be None.
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 """
727 name = 'SEARCH'
728 if charset:
R David Murraya6429db2015-05-10 19:17:23 -0400729 if self.utf8_enabled:
730 raise IMAP4.error("Non-None charset not valid in UTF8 mode")
Guido van Rossum68468eb2003-02-27 20:14:51 +0000731 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000732 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000733 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000735
736
Piers Lauder14f39402005-08-31 10:46:29 +0000737 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000738 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000739
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000741
Piers Lauder14f39402005-08-31 10:46:29 +0000742 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000743
Tim Peters07e99cb2001-01-14 23:47:14 +0000744 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000745
746 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
747 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000748 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000749 self.untagged_responses = {} # Flush old responses.
750 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000751 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000752 name = 'EXAMINE'
753 else:
754 name = 'SELECT'
755 typ, dat = self._simple_command(name, mailbox)
756 if typ != 'OK':
757 self.state = 'AUTH' # Might have been 'SELECTED'
758 return typ, dat
759 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000760 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000761 and not readonly:
762 if __debug__:
763 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000764 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000765 raise self.readonly('%s is not writable' % mailbox)
766 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000767
768
Piers Lauder15e5d532001-07-20 10:52:06 +0000769 def setacl(self, mailbox, who, what):
770 """Set a mailbox acl.
771
Piers Lauderf167dc32004-03-25 00:12:21 +0000772 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000773 """
774 return self._simple_command('SETACL', mailbox, who, what)
775
776
Piers Lauderd80ef022005-06-01 23:50:52 +0000777 def setannotation(self, *args):
778 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
779 Set ANNOTATIONs."""
780
781 typ, dat = self._simple_command('SETANNOTATION', *args)
782 return self._untagged_response(typ, dat, 'ANNOTATION')
783
784
Piers Lauder3fca2912002-06-17 07:07:20 +0000785 def setquota(self, root, limits):
786 """Set the quota root's resource limits.
787
788 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000789 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000790 typ, dat = self._simple_command('SETQUOTA', root, limits)
791 return self._untagged_response(typ, dat, 'QUOTA')
792
793
Piers Lauder15e5d532001-07-20 10:52:06 +0000794 def sort(self, sort_criteria, charset, *search_criteria):
795 """IMAP4rev1 extension SORT command.
796
797 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
798 """
799 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000800 #if not name in self.capabilities: # Let the server decide!
801 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000802 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000803 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000804 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000805 return self._untagged_response(typ, dat, name)
806
807
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000808 def starttls(self, ssl_context=None):
809 name = 'STARTTLS'
810 if not HAVE_SSL:
811 raise self.error('SSL support missing')
812 if self._tls_established:
813 raise self.abort('TLS session already established')
814 if name not in self.capabilities:
815 raise self.abort('TLS not supported by server')
816 # Generate a default SSL context if none was passed.
817 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100818 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000819 typ, dat = self._simple_command(name)
820 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100821 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600822 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000823 self.file = self.sock.makefile('rb')
824 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000825 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000826 else:
827 raise self.error("Couldn't establish TLS session")
828 return self._untagged_response(typ, dat, name)
829
830
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 def status(self, mailbox, names):
832 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 (typ, [data]) = <instance>.status(mailbox, names)
835 """
836 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000837 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000838 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 typ, dat = self._simple_command(name, mailbox, names)
840 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 def store(self, message_set, command, flags):
844 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 (typ, [data]) = <instance>.store(message_set, command, flags)
847 """
848 if (flags[0],flags[-1]) != ('(',')'):
849 flags = '(%s)' % flags # Avoid quoting the flags
850 typ, dat = self._simple_command('STORE', message_set, command, flags)
851 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000852
853
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 def subscribe(self, mailbox):
855 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000856
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 (typ, [data]) = <instance>.subscribe(mailbox)
858 """
859 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
861
Martin v. Löwisd8921372003-11-10 06:44:44 +0000862 def thread(self, threading_algorithm, charset, *search_criteria):
863 """IMAPrev1 extension THREAD command.
864
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000865 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000866 """
867 name = 'THREAD'
868 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
869 return self._untagged_response(typ, dat, name)
870
871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 def uid(self, command, *args):
873 """Execute "command arg ..." with messages identified by UID,
874 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 Returns response appropriate to 'command'.
879 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000880 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000881 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 raise self.error("Unknown IMAP4 UID command: %s" % command)
883 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000884 raise self.error("command %s illegal in state %s, "
885 "only allowed in states %s" %
886 (command, self.state,
887 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000888 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000889 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000890 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000891 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 else:
893 name = 'FETCH'
894 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000895
896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 def unsubscribe(self, mailbox):
898 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 (typ, [data]) = <instance>.unsubscribe(mailbox)
901 """
902 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 def xatom(self, name, *args):
906 """Allow simple extension commands
907 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000908
Piers Lauder15e5d532001-07-20 10:52:06 +0000909 Assumes command is legal in current state.
910
Tim Peters07e99cb2001-01-14 23:47:14 +0000911 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000912
913 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000914 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000915 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000916 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000917 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000918 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000919 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000920 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000921
922
923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000928 if dat is None:
929 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 ur = self.untagged_responses
931 if __debug__:
932 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000933 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000935 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 ur[typ].append(dat)
937 else:
938 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000939
940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 def _check_bye(self):
942 bye = self.untagged_responses.get('BYE')
943 if bye:
R David Murraya6429db2015-05-10 19:17:23 -0400944 raise self.abort(bye[-1].decode(self._encoding, 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000945
946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 if self.state not in Commands[name]:
950 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000951 raise self.error("command %s illegal in state %s, "
952 "only allowed in states %s" %
953 (name, self.state,
954 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000957 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000959
Raymond Hettinger54f02222002-06-01 14:18:47 +0000960 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 and not self.is_readonly:
962 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000963
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 tag = self._new_tag()
R David Murraya6429db2015-05-10 19:17:23 -0400965 name = bytes(name, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000966 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 for arg in args:
968 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000969 if isinstance(arg, str):
R David Murraya6429db2015-05-10 19:17:23 -0400970 arg = bytes(arg, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000971 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 literal = self.literal
974 if literal is not None:
975 self.literal = None
976 if type(literal) is type(self._command):
977 literator = literal
978 else:
979 literator = None
R David Murraya6429db2015-05-10 19:17:23 -0400980 data = data + bytes(' {%s}' % len(literal), self._encoding)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 if __debug__:
983 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000984 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000986 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000989 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200990 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 if literal is None:
994 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000995
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 while 1:
997 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
Tim Peters07e99cb2001-01-14 23:47:14 +0000999 while self._get_response():
1000 if self.tagged_commands[tag]: # BAD/NO?
1001 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001004
Tim Peters07e99cb2001-01-14 23:47:14 +00001005 if literator:
1006 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001007
Tim Peters07e99cb2001-01-14 23:47:14 +00001008 if __debug__:
1009 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001010 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +00001011
Tim Peters07e99cb2001-01-14 23:47:14 +00001012 try:
Piers Lauder15e5d532001-07-20 10:52:06 +00001013 self.send(literal)
1014 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +02001015 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 if not literator:
1019 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001020
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001022
1023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 def _command_complete(self, name, tag):
Victor Stinner74125a62019-04-15 18:23:20 +02001025 logout = (name == 'LOGOUT')
Antoine Pitroudac47912010-11-10 00:18:40 +00001026 # BYE is expected after LOGOUT
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 try:
Victor Stinner74125a62019-04-15 18:23:20 +02001030 typ, data = self._get_tagged_response(tag, expect_bye=logout)
Guido van Rossumb940e112007-01-10 16:19:56 +00001031 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001032 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001033 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 raise self.error('command: %s => %s' % (name, val))
Victor Stinner74125a62019-04-15 18:23:20 +02001035 if not logout:
Antoine Pitroudac47912010-11-10 00:18:40 +00001036 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 if typ == 'BAD':
1038 raise self.error('%s command error: %s %s' % (name, typ, data))
1039 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001040
1041
Antoine Pitroudbe75192010-11-16 17:55:26 +00001042 def _get_capabilities(self):
1043 typ, dat = self.capability()
1044 if dat == [None]:
1045 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001046 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001047 dat = dat.upper()
1048 self.capabilities = tuple(dat.split())
1049
1050
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001052
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 # Read response and store.
1054 #
1055 # Returns None for continuation responses,
1056 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001061
Tim Peters07e99cb2001-01-14 23:47:14 +00001062 if self._match(self.tagre, resp):
1063 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001064 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001065 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001066
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001068 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 dat = self.mo.group('data')
1070 self.tagged_commands[tag] = (typ, [dat])
1071 else:
1072 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001075
Tim Peters07e99cb2001-01-14 23:47:14 +00001076 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001077 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 if self.mo is None:
1081 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001082
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 if self._match(Continuation, resp):
1084 self.continuation_response = self.mo.group('data')
1085 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001086
R David Murraya6429db2015-05-10 19:17:23 -04001087 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001088
Tim Peters07e99cb2001-01-14 23:47:14 +00001089 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001090 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001092 if dat is None: dat = b'' # Null untagged response
1093 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001094
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001096
R David Murraya6429db2015-05-10 19:17:23 -04001097 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001098
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001100
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001101 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001102 if __debug__:
1103 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001104 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001105 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001106
Tim Peters07e99cb2001-01-14 23:47:14 +00001107 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001108
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001110
Tim Peters07e99cb2001-01-14 23:47:14 +00001111 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001112
Tim Peters07e99cb2001-01-14 23:47:14 +00001113 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001114
Tim Peters07e99cb2001-01-14 23:47:14 +00001115 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001116
Tim Peters07e99cb2001-01-14 23:47:14 +00001117 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001118
Tim Peters07e99cb2001-01-14 23:47:14 +00001119 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001120 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001121 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001122 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001123
Tim Peters07e99cb2001-01-14 23:47:14 +00001124 if __debug__:
1125 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001126 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001127
Tim Peters07e99cb2001-01-14 23:47:14 +00001128 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001129
1130
Victor Stinner74125a62019-04-15 18:23:20 +02001131 def _get_tagged_response(self, tag, expect_bye=False):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001132
Tim Peters07e99cb2001-01-14 23:47:14 +00001133 while 1:
1134 result = self.tagged_commands[tag]
1135 if result is not None:
1136 del self.tagged_commands[tag]
1137 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001138
Victor Stinner74125a62019-04-15 18:23:20 +02001139 if expect_bye:
1140 typ = 'BYE'
1141 bye = self.untagged_responses.pop(typ, None)
1142 if bye is not None:
1143 # Server replies to the "LOGOUT" command with "BYE"
1144 return (typ, bye)
1145
R David Murray95ff7232014-02-07 13:44:57 -05001146 # If we've seen a BYE at this point, the socket will be
1147 # closed, so report the BYE now.
R David Murray95ff7232014-02-07 13:44:57 -05001148 self._check_bye()
1149
Tim Peters07e99cb2001-01-14 23:47:14 +00001150 # Some have reported "unexpected response" exceptions.
1151 # Note that ignoring them here causes loops.
1152 # Instead, send me details of the unexpected response and
1153 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001154
Tim Peters07e99cb2001-01-14 23:47:14 +00001155 try:
1156 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001157 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001158 if __debug__:
1159 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001160 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001161 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001162
1163
Tim Peters07e99cb2001-01-14 23:47:14 +00001164 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001165
Piers Lauder15e5d532001-07-20 10:52:06 +00001166 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001167 if not line:
1168 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001169
Tim Peters07e99cb2001-01-14 23:47:14 +00001170 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001171 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001172 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001173
Tim Peters07e99cb2001-01-14 23:47:14 +00001174 line = line[:-2]
1175 if __debug__:
1176 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001177 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001179 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001180 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001181
1182
Tim Peters07e99cb2001-01-14 23:47:14 +00001183 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001184
Tim Peters07e99cb2001-01-14 23:47:14 +00001185 # Run compiled regular expression match method on 's'.
1186 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001187
Tim Peters07e99cb2001-01-14 23:47:14 +00001188 self.mo = cre.match(s)
1189 if __debug__:
1190 if self.mo is not None and self.debug >= 5:
Serhiy Storchakaa4a30202017-11-28 22:54:42 +02001191 self._mesg("\tmatched %r => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001192 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001193
1194
Tim Peters07e99cb2001-01-14 23:47:14 +00001195 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001196
R David Murraya6429db2015-05-10 19:17:23 -04001197 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001198 self.tagnum = self.tagnum + 1
1199 self.tagged_commands[tag] = None
1200 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001201
1202
Tim Peters07e99cb2001-01-14 23:47:14 +00001203 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001204
Antoine Pitroub1436f12010-11-09 22:55:55 +00001205 arg = arg.replace('\\', '\\\\')
1206 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001207
Antoine Pitroub1436f12010-11-09 22:55:55 +00001208 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001209
1210
Tim Peters07e99cb2001-01-14 23:47:14 +00001211 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001212
Guido van Rossum68468eb2003-02-27 20:14:51 +00001213 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001214
1215
Tim Peters07e99cb2001-01-14 23:47:14 +00001216 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001217 if typ == 'NO':
1218 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001219 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001220 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001221 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001222 if __debug__:
1223 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001224 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001225 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001226
1227
Piers Lauderf2d7d152002-02-22 01:15:17 +00001228 if __debug__:
1229
1230 def _mesg(self, s, secs=None):
1231 if secs is None:
1232 secs = time.time()
1233 tm = time.strftime('%M:%S', time.localtime(secs))
1234 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1235 sys.stderr.flush()
1236
1237 def _dump_ur(self, dict):
1238 # Dump untagged responses (in `dict').
1239 l = dict.items()
1240 if not l: return
1241 t = '\n\t\t'
1242 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1243 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1244
1245 def _log(self, line):
1246 # Keep log of last `_cmd_log_len' interactions for debugging.
1247 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1248 self._cmd_log_idx += 1
1249 if self._cmd_log_idx >= self._cmd_log_len:
1250 self._cmd_log_idx = 0
1251
1252 def print_log(self):
1253 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1254 i, n = self._cmd_log_idx, self._cmd_log_len
1255 while n:
1256 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001257 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001258 except:
1259 pass
1260 i += 1
1261 if i >= self._cmd_log_len:
1262 i = 0
1263 n -= 1
1264
1265
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001266if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001267
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001268 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001269
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001270 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001271
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001272 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context[, timeout=None]]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001273
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001274 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001275 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001276 keyfile - PEM formatted file that contains your private key (default: None);
1277 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001278 ssl_context - a SSLContext object that contains your certificate chain
1279 and private key (default: None)
1280 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001281 certfile should not be set otherwise ValueError is raised.
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001282 timeout - socket timeout (default: None) If timeout is not given or is None,
1283 the global default socket timeout is used
Piers Laudera4f83132002-03-08 01:53:24 +00001284
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001285 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001286 """
Piers Laudera4f83132002-03-08 01:53:24 +00001287
1288
R David Murraya6429db2015-05-10 19:17:23 -04001289 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001290 certfile=None, ssl_context=None, timeout=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001291 if ssl_context is not None and keyfile is not None:
1292 raise ValueError("ssl_context and keyfile arguments are mutually "
1293 "exclusive")
1294 if ssl_context is not None and certfile is not None:
1295 raise ValueError("ssl_context and certfile arguments are mutually "
1296 "exclusive")
Christian Heimesd0486372016-09-10 23:23:33 +02001297 if keyfile is not None or certfile is not None:
1298 import warnings
Pablo Aguiar4b5e62d2018-11-01 11:33:35 +01001299 warnings.warn("keyfile and certfile are deprecated, use a "
Christian Heimesd0486372016-09-10 23:23:33 +02001300 "custom ssl_context instead", DeprecationWarning, 2)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001301 self.keyfile = keyfile
1302 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001303 if ssl_context is None:
1304 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1305 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001306 self.ssl_context = ssl_context
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001307 IMAP4.__init__(self, host, port, timeout)
Piers Laudera4f83132002-03-08 01:53:24 +00001308
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001309 def _create_socket(self, timeout):
1310 sock = IMAP4._create_socket(self, timeout)
Christian Heimes48aae572013-12-02 20:01:29 +01001311 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001312 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001313
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001314 def open(self, host='', port=IMAP4_SSL_PORT, timeout=None):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001315 """Setup connection to remote server on "host:port".
1316 (default: localhost:standard IMAP4 SSL port).
1317 This connection will be used by the routines:
1318 read, readline, send, shutdown.
1319 """
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001320 IMAP4.open(self, host, port, timeout)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001321
1322 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001323
1324
Piers Laudere0273de2002-11-22 05:53:04 +00001325class IMAP4_stream(IMAP4):
1326
1327 """IMAP4 client class over a stream
1328
1329 Instantiate with: IMAP4_stream(command)
1330
R David Murraya6429db2015-05-10 19:17:23 -04001331 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001332
1333 for more documentation see the docstring of the parent class IMAP4.
1334 """
1335
1336
1337 def __init__(self, command):
1338 self.command = command
1339 IMAP4.__init__(self)
1340
1341
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001342 def open(self, host=None, port=None, timeout=None):
Piers Laudere0273de2002-11-22 05:53:04 +00001343 """Setup a stream connection.
1344 This connection will be used by the routines:
1345 read, readline, send, shutdown.
1346 """
1347 self.host = None # For compatibility with parent class
1348 self.port = None
1349 self.sock = None
1350 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001351 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001352 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001353 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1354 shell=True, close_fds=True)
1355 self.writefile = self.process.stdin
1356 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001357
1358 def read(self, size):
1359 """Read 'size' bytes from remote."""
1360 return self.readfile.read(size)
1361
1362
1363 def readline(self):
1364 """Read line from remote."""
1365 return self.readfile.readline()
1366
1367
1368 def send(self, data):
1369 """Send data to remote."""
1370 self.writefile.write(data)
1371 self.writefile.flush()
1372
1373
1374 def shutdown(self):
1375 """Close I/O established in "open"."""
1376 self.readfile.close()
1377 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001378 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001379
1380
1381
Guido van Rossumeda960a1998-06-18 14:24:28 +00001382class _Authenticator:
1383
Tim Peters07e99cb2001-01-14 23:47:14 +00001384 """Private class to provide en/decoding
1385 for base64-based authentication conversation.
1386 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001387
Tim Peters07e99cb2001-01-14 23:47:14 +00001388 def __init__(self, mechinst):
1389 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001390
Tim Peters07e99cb2001-01-14 23:47:14 +00001391 def process(self, data):
1392 ret = self.mech(self.decode(data))
1393 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001394 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001395 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001396
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 def encode(self, inp):
1398 #
1399 # Invoke binascii.b2a_base64 iteratively with
1400 # short even length buffers, strip the trailing
1401 # line feed from the result and append. "Even"
1402 # means a number that factors to both 6 and 8,
1403 # so when it gets to the end of the 8-bit input
1404 # there's no partial 6-bit output.
1405 #
R David Murray774a39f2013-02-19 12:17:31 -05001406 oup = b''
1407 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001408 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 while inp:
1410 if len(inp) > 48:
1411 t = inp[:48]
1412 inp = inp[48:]
1413 else:
1414 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001415 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001416 e = binascii.b2a_base64(t)
1417 if e:
1418 oup = oup + e[:-1]
1419 return oup
1420
1421 def decode(self, inp):
1422 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001423 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 return binascii.a2b_base64(inp)
1425
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001426Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1427Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001428
1429def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001430 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001431
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001432 Return corresponding local time. The return value is a
1433 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001435
Tim Peters07e99cb2001-01-14 23:47:14 +00001436 mo = InternalDate.match(resp)
1437 if not mo:
1438 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001439
Tim Peters07e99cb2001-01-14 23:47:14 +00001440 mon = Mon2num[mo.group('mon')]
1441 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001442
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001443 day = int(mo.group('day'))
1444 year = int(mo.group('year'))
1445 hour = int(mo.group('hour'))
1446 min = int(mo.group('min'))
1447 sec = int(mo.group('sec'))
1448 zoneh = int(mo.group('zoneh'))
1449 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001450
Tim Peters07e99cb2001-01-14 23:47:14 +00001451 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001452
Tim Peters07e99cb2001-01-14 23:47:14 +00001453 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001454 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001455 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001456
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001458 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001459
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001460 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001461
1462
1463
1464def Int2AP(num):
1465
Tim Peters07e99cb2001-01-14 23:47:14 +00001466 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001467
Christian Heimesfb5faf02008-11-05 19:39:50 +00001468 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001469 num = int(abs(num))
1470 while num:
1471 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001472 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001473 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001474
1475
1476
1477def ParseFlags(resp):
1478
Tim Peters07e99cb2001-01-14 23:47:14 +00001479 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001480
Tim Peters07e99cb2001-01-14 23:47:14 +00001481 mo = Flags.match(resp)
1482 if not mo:
1483 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001484
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001485 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001486
1487
1488def Time2Internaldate(date_time):
1489
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001490 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001491
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001492 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001493 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001494 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001495 representing local time, an instance of time.struct_time (as
1496 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001497 double-quoted string. In the last case, it is assumed to already
1498 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001499 """
Fred Drakedb519202002-01-05 17:17:09 +00001500 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001501 dt = datetime.fromtimestamp(date_time,
1502 timezone.utc).astimezone()
1503 elif isinstance(date_time, tuple):
1504 try:
1505 gmtoff = date_time.tm_gmtoff
1506 except AttributeError:
1507 if time.daylight:
1508 dst = date_time[8]
1509 if dst == -1:
1510 dst = time.localtime(time.mktime(date_time))[8]
1511 gmtoff = -(time.timezone, time.altzone)[dst]
1512 else:
1513 gmtoff = -time.timezone
1514 delta = timedelta(seconds=gmtoff)
1515 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1516 elif isinstance(date_time, datetime):
1517 if date_time.tzinfo is None:
1518 raise ValueError("date_time must be aware")
1519 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001520 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001521 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001522 else:
1523 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001524 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1525 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001526
1527
1528
Guido van Rossum8c062211999-12-13 23:27:45 +00001529if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001530
Piers Laudere0273de2002-11-22 05:53:04 +00001531 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1532 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1533 # to test the IMAP4_stream class
1534
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001535 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001536
Tim Peters07e99cb2001-01-14 23:47:14 +00001537 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001538 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001539 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001540 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001541
Piers Laudere0273de2002-11-22 05:53:04 +00001542 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001543 for opt,val in optlist:
1544 if opt == '-d':
1545 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001546 elif opt == '-s':
1547 stream_command = val
1548 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001549
Tim Peters07e99cb2001-01-14 23:47:14 +00001550 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001551
Tim Peters07e99cb2001-01-14 23:47:14 +00001552 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001553
Tim Peters07e99cb2001-01-14 23:47:14 +00001554 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001555 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001556
Piers Lauder47404ff2003-04-29 23:40:59 +00001557 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 +00001558 test_seq1 = (
1559 ('login', (USER, PASSWD)),
1560 ('create', ('/tmp/xxx 1',)),
1561 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1562 ('CREATE', ('/tmp/yyz 2',)),
1563 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1564 ('list', ('/tmp', 'yy*')),
1565 ('select', ('/tmp/yyz 2',)),
1566 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001567 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
R David Murray44b548d2016-09-08 13:59:53 -04001568 ('store', ('1', 'FLAGS', r'(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001569 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001570 ('expunge', ()),
1571 ('recent', ()),
1572 ('close', ()),
1573 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001574
Tim Peters07e99cb2001-01-14 23:47:14 +00001575 test_seq2 = (
1576 ('select', ()),
1577 ('response',('UIDVALIDITY',)),
1578 ('uid', ('SEARCH', 'ALL')),
1579 ('response', ('EXISTS',)),
1580 ('append', (None, None, None, test_mesg)),
1581 ('recent', ()),
1582 ('logout', ()),
1583 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001584
Tim Peters07e99cb2001-01-14 23:47:14 +00001585 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001586 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001587 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001588 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001589 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001590 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001591
Tim Peters07e99cb2001-01-14 23:47:14 +00001592 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001593 if stream_command:
1594 M = IMAP4_stream(stream_command)
1595 else:
1596 M = IMAP4(host)
1597 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001598 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001599 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001600 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001601
Tim Peters07e99cb2001-01-14 23:47:14 +00001602 for cmd,args in test_seq1:
1603 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001604
Tim Peters07e99cb2001-01-14 23:47:14 +00001605 for ml in run('list', ('/tmp/', 'yy%')):
1606 mo = re.match(r'.*"([^"]+)"$', ml)
1607 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001608 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001609 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001610
Tim Peters07e99cb2001-01-14 23:47:14 +00001611 for cmd,args in test_seq2:
1612 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001613
Tim Peters07e99cb2001-01-14 23:47:14 +00001614 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1615 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001616
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001617 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001618 if not uid: continue
1619 run('uid', ('FETCH', '%s' % uid[-1],
1620 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001621
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001622 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001623
Tim Peters07e99cb2001-01-14 23:47:14 +00001624 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001625 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001626
Tim Peters07e99cb2001-01-14 23:47:14 +00001627 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001628 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001629If you would like to see debugging output,
1630try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001631''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001632
Tim Peters07e99cb2001-01-14 23:47:14 +00001633 raise