blob: d9720f20c390256322cc207bf89fb4981dd0b218 [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'),
Dong-hee Nac5c42812020-04-27 23:52:55 +0900101 'UNSELECT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +0000102 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000103
Tim Peters07e99cb2001-01-14 23:47:14 +0000104# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000105
Christian Heimesfb5faf02008-11-05 19:39:50 +0000106Continuation = re.compile(br'\+( (?P<data>.*))?')
107Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
108InternalDate = re.compile(br'.*INTERNALDATE "'
109 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
110 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
111 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
112 br'"')
R David Murraya6429db2015-05-10 19:17:23 -0400113# Literal is no longer used; kept for backward compatibility.
Christian Heimesfb5faf02008-11-05 19:39:50 +0000114Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
115MapCRLF = re.compile(br'\r\n|\r|\n')
R David Murray317f64f2016-01-02 17:18:34 -0500116# We no longer exclude the ']' character from the data portion of the response
117# code, even though it violates the RFC. Popular IMAP servers such as Gmail
118# allow flags with ']', and there are programs (including imaplib!) that can
119# produce them. The problem with this is if the 'text' portion of the response
120# includes a ']' we'll parse the response wrong (which is the point of the RFC
121# restriction). However, that seems less likely to be a problem in practice
122# than being unable to correctly parse flags that include ']' chars, which
123# was reported as a real-world problem in issue #21815.
124Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>.*))?\]')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000125Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
R David Murraya6429db2015-05-10 19:17:23 -0400126# Untagged_status is no longer used; kept for backward compatibility
Antoine Pitroufd036452008-08-19 17:56:33 +0000127Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000128 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
R David Murraya6429db2015-05-10 19:17:23 -0400129# We compile these in _mode_xxx.
130_Literal = br'.*{(?P<size>\d+)}$'
131_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000132
133
134
135class IMAP4:
136
R David Murray44b548d2016-09-08 13:59:53 -0400137 r"""IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000138
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900139 Instantiate with: IMAP4([host[, port[, timeout=None]]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000140
Tim Peters07e99cb2001-01-14 23:47:14 +0000141 host - host's name (default: localhost);
142 port - port number (default: standard IMAP4 port).
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900143 timeout - socket timeout (default: None)
144 If timeout is not given or is None,
145 the global default socket timeout is used
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000146
Tim Peters07e99cb2001-01-14 23:47:14 +0000147 All IMAP4rev1 commands are supported by methods of the same
148 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000149
Tim Peters07e99cb2001-01-14 23:47:14 +0000150 All arguments to commands are converted to strings, except for
151 AUTHENTICATE, and the last argument to APPEND which is passed as
152 an IMAP4 literal. If necessary (the string contains any
153 non-printing characters or white-space and isn't enclosed with
154 either parentheses or double quotes) each string is quoted.
155 However, the 'password' argument to the LOGIN command is always
156 quoted. If you want to avoid having an argument string quoted
157 (eg: the 'flags' argument to STORE) then enclose the string in
158 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 Each command returns a tuple: (type, [data, ...]) where 'type'
161 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000162 tagged response, or untagged results from command. Each 'data'
163 is either a string, or a tuple. If a tuple, then the first part
164 is the header of the response, and the second part contains
165 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000166
Tim Peters07e99cb2001-01-14 23:47:14 +0000167 Errors raise the exception class <instance>.error("<reason>").
168 IMAP4 server errors raise <instance>.abort("<reason>"),
169 which is a sub-class of 'error'. Mailbox status changes
170 from READ-WRITE to READ-ONLY raise the exception class
171 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000172
Tim Peters07e99cb2001-01-14 23:47:14 +0000173 "error" exceptions imply a program error.
174 "abort" exceptions imply the connection should be reset, and
175 the command re-tried.
176 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000177
Piers Lauderd80ef022005-06-01 23:50:52 +0000178 Note: to use this module, you must read the RFCs pertaining to the
179 IMAP4 protocol, as the semantics of the arguments to each IMAP4
180 command are left to the invoker, not to mention the results. Also,
181 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
Tim Peters07e99cb2001-01-14 23:47:14 +0000184 class error(Exception): pass # Logical errors - debug required
185 class abort(error): pass # Service errors - close and retry
186 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000187
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900188 def __init__(self, host='', port=IMAP4_PORT, timeout=None):
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 self.debug = Debug
190 self.state = 'LOGOUT'
191 self.literal = None # A literal argument to a command
192 self.tagged_commands = {} # Tagged commands awaiting response
193 self.untagged_responses = {} # {typ: [data, ...], ...}
194 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000195 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000196 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000197 self._tls_established = False
R David Murraya6429db2015-05-10 19:17:23 -0400198 self._mode_ascii()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000199
Tim Peters07e99cb2001-01-14 23:47:14 +0000200 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000201
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900202 self.open(host, port, timeout)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000203
Victor Stinner33e649c2011-01-05 23:01:37 +0000204 try:
205 self._connect()
206 except Exception:
207 try:
208 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200209 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000210 pass
211 raise
212
R David Murraya6429db2015-05-10 19:17:23 -0400213 def _mode_ascii(self):
214 self.utf8_enabled = False
215 self._encoding = 'ascii'
216 self.Literal = re.compile(_Literal, re.ASCII)
217 self.Untagged_status = re.compile(_Untagged_status, re.ASCII)
218
219
220 def _mode_utf8(self):
221 self.utf8_enabled = True
222 self._encoding = 'utf-8'
223 self.Literal = re.compile(_Literal)
224 self.Untagged_status = re.compile(_Untagged_status)
225
Victor Stinner33e649c2011-01-05 23:01:37 +0000226
227 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000228 # Create unique tag for this session,
229 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000230
Piers Lauder2dfc1682005-07-05 04:20:07 +0000231 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000232 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000233 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000234 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000235
Tim Peters07e99cb2001-01-14 23:47:14 +0000236 # Get server welcome message,
237 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000238
Tim Peters07e99cb2001-01-14 23:47:14 +0000239 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000240 self._cmd_log_len = 10
241 self._cmd_log_idx = 0
242 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000243 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000244 self._mesg('imaplib version %s' % __version__)
245 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000246
Tim Peters07e99cb2001-01-14 23:47:14 +0000247 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000248 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000249 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000250 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000251 self.state = 'NONAUTH'
252 else:
253 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000254
Antoine Pitroudbe75192010-11-16 17:55:26 +0000255 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000256 if __debug__:
257 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000258 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000259
Tim Peters07e99cb2001-01-14 23:47:14 +0000260 for version in AllowedVersions:
261 if not version in self.capabilities:
262 continue
263 self.PROTOCOL_VERSION = version
264 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000265
Tim Peters07e99cb2001-01-14 23:47:14 +0000266 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000267
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000268
Tim Peters07e99cb2001-01-14 23:47:14 +0000269 def __getattr__(self, attr):
270 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000271 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000272 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000273 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000274
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300275 def __enter__(self):
276 return self
277
278 def __exit__(self, *args):
Victor Stinner74125a62019-04-15 18:23:20 +0200279 if self.state == "LOGOUT":
280 return
281
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300282 try:
283 self.logout()
284 except OSError:
285 pass
Guido van Rossum26367a01998-09-28 15:34:46 +0000286
287
Piers Lauder15e5d532001-07-20 10:52:06 +0000288 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000289
290
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900291 def _create_socket(self, timeout):
Berker Peksage4dcbbd2018-08-07 05:12:18 +0300292 # Default value of IMAP4.host is '', but socket.getaddrinfo()
293 # (which is used by socket.create_connection()) expects None
294 # as a default value for host.
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900295 if timeout is not None and not timeout:
296 raise ValueError('Non-blocking socket (timeout=0) is not supported')
Berker Peksage4dcbbd2018-08-07 05:12:18 +0300297 host = None if not self.host else self.host
Steve Dower44f91c32019-06-27 10:47:59 -0700298 sys.audit("imaplib.open", self, self.host, self.port)
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900299 address = (host, self.port)
300 if timeout is not None:
301 return socket.create_connection(address, timeout)
302 return socket.create_connection(address)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000303
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900304 def open(self, host='', port=IMAP4_PORT, timeout=None):
Piers Lauderf97b2d72002-06-05 22:31:57 +0000305 """Setup connection to remote server on "host:port"
306 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000307 This connection will be used by the routines:
308 read, readline, send, shutdown.
309 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000310 self.host = host
311 self.port = port
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900312 self.sock = self._create_socket(timeout)
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000313 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000314
315
Piers Lauder15e5d532001-07-20 10:52:06 +0000316 def read(self, size):
317 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200318 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000319
320
321 def readline(self):
322 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100323 line = self.file.readline(_MAXLINE + 1)
324 if len(line) > _MAXLINE:
325 raise self.error("got more than %d bytes" % _MAXLINE)
326 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000327
328
329 def send(self, data):
330 """Send data to remote."""
Steve Dower44f91c32019-06-27 10:47:59 -0700331 sys.audit("imaplib.send", self, data)
Martin v. Löwise12454f2002-02-16 23:06:19 +0000332 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000333
Piers Lauderf2d7d152002-02-22 01:15:17 +0000334
Piers Lauder15e5d532001-07-20 10:52:06 +0000335 def shutdown(self):
336 """Close I/O established in "open"."""
337 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000338 try:
339 self.sock.shutdown(socket.SHUT_RDWR)
Victor Stinner83a2c282017-05-15 17:33:45 +0200340 except OSError as exc:
341 # The server might already have closed the connection.
342 # On Windows, this may result in WSAEINVAL (error 10022):
343 # An invalid operation was attempted.
344 if (exc.errno != errno.ENOTCONN
345 and getattr(exc, 'winerror', 0) != 10022):
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000346 raise
347 finally:
348 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000349
350
351 def socket(self):
352 """Return socket instance used to connect to IMAP4 server.
353
354 socket = <instance>.socket()
355 """
356 return self.sock
357
358
359
360 # Utility methods
361
362
Tim Peters07e99cb2001-01-14 23:47:14 +0000363 def recent(self):
364 """Return most recent 'RECENT' responses if any exist,
365 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 'data' is None if no new messages,
370 else list of RECENT responses, most recent last.
371 """
372 name = 'RECENT'
373 typ, dat = self._untagged_response('OK', [None], name)
374 if dat[-1]:
375 return typ, dat
376 typ, dat = self.noop() # Prod server for response
377 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 def response(self, code):
381 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 (code, [data]) = <instance>.response(code)
386 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000387 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000388
389
Guido van Rossum26367a01998-09-28 15:34:46 +0000390
Tim Peters07e99cb2001-01-14 23:47:14 +0000391 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000392
393
Tim Peters07e99cb2001-01-14 23:47:14 +0000394 def append(self, mailbox, flags, date_time, message):
395 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 All args except `message' can be None.
400 """
401 name = 'APPEND'
402 if not mailbox:
403 mailbox = 'INBOX'
404 if flags:
405 if (flags[0],flags[-1]) != ('(',')'):
406 flags = '(%s)' % flags
407 else:
408 flags = None
409 if date_time:
410 date_time = Time2Internaldate(date_time)
411 else:
412 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400413 literal = MapCRLF.sub(CRLF, message)
414 if self.utf8_enabled:
415 literal = b'UTF8 (' + literal + b')'
416 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000417 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000418
419
Tim Peters07e99cb2001-01-14 23:47:14 +0000420 def authenticate(self, mechanism, authobject):
421 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 'mechanism' specifies which authentication mechanism is to
424 be used - it must appear in <instance>.capabilities in the
425 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000426
Tim Peters07e99cb2001-01-14 23:47:14 +0000427 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000428
Tim Peters07e99cb2001-01-14 23:47:14 +0000429 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000430
R David Murray774a39f2013-02-19 12:17:31 -0500431 It will be called to process server continuation responses; the
432 response argument it is passed will be a bytes. It should return bytes
433 data that will be base64 encoded and sent to the server. It should
434 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000435 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000436 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000437 # XXX: shouldn't this code be removed, not commented out?
438 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000439 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000440 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000441 self.literal = _Authenticator(authobject).process
442 typ, dat = self._simple_command('AUTHENTICATE', mech)
443 if typ != 'OK':
R David Murrayb079c072016-12-24 21:32:26 -0500444 raise self.error(dat[-1].decode('utf-8', 'replace'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000445 self.state = 'AUTH'
446 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000447
448
Piers Lauderd80ef022005-06-01 23:50:52 +0000449 def capability(self):
450 """(typ, [data]) = <instance>.capability()
451 Fetch capabilities list from server."""
452
453 name = 'CAPABILITY'
454 typ, dat = self._simple_command(name)
455 return self._untagged_response(typ, dat, name)
456
457
Tim Peters07e99cb2001-01-14 23:47:14 +0000458 def check(self):
459 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000460
Tim Peters07e99cb2001-01-14 23:47:14 +0000461 (typ, [data]) = <instance>.check()
462 """
463 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000464
465
Tim Peters07e99cb2001-01-14 23:47:14 +0000466 def close(self):
467 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000468
Tim Peters07e99cb2001-01-14 23:47:14 +0000469 Deleted messages are removed from writable mailbox.
470 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000471
Tim Peters07e99cb2001-01-14 23:47:14 +0000472 (typ, [data]) = <instance>.close()
473 """
474 try:
475 typ, dat = self._simple_command('CLOSE')
476 finally:
477 self.state = 'AUTH'
478 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000479
480
Tim Peters07e99cb2001-01-14 23:47:14 +0000481 def copy(self, message_set, new_mailbox):
482 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000483
Tim Peters07e99cb2001-01-14 23:47:14 +0000484 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
485 """
486 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000487
488
Tim Peters07e99cb2001-01-14 23:47:14 +0000489 def create(self, mailbox):
490 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000491
Tim Peters07e99cb2001-01-14 23:47:14 +0000492 (typ, [data]) = <instance>.create(mailbox)
493 """
494 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000495
496
Tim Peters07e99cb2001-01-14 23:47:14 +0000497 def delete(self, mailbox):
498 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000499
Tim Peters07e99cb2001-01-14 23:47:14 +0000500 (typ, [data]) = <instance>.delete(mailbox)
501 """
502 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000503
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000504 def deleteacl(self, mailbox, who):
505 """Delete the ACLs (remove any rights) set for who on mailbox.
506
507 (typ, [data]) = <instance>.deleteacl(mailbox, who)
508 """
509 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000510
R David Murraya6429db2015-05-10 19:17:23 -0400511 def enable(self, capability):
512 """Send an RFC5161 enable string to the server.
513
Min ho Kimc4cacc82019-07-31 08:16:13 +1000514 (typ, [data]) = <instance>.enable(capability)
R David Murraya6429db2015-05-10 19:17:23 -0400515 """
516 if 'ENABLE' not in self.capabilities:
517 raise IMAP4.error("Server does not support ENABLE")
518 typ, data = self._simple_command('ENABLE', capability)
519 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
520 self._mode_utf8()
521 return typ, data
522
Tim Peters07e99cb2001-01-14 23:47:14 +0000523 def expunge(self):
524 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000525
Tim Peters07e99cb2001-01-14 23:47:14 +0000526 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000527
Tim Peters07e99cb2001-01-14 23:47:14 +0000528 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000529
Tim Peters07e99cb2001-01-14 23:47:14 +0000530 'data' is list of 'EXPUNGE'd message numbers in order received.
531 """
532 name = 'EXPUNGE'
533 typ, dat = self._simple_command(name)
534 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000535
536
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 def fetch(self, message_set, message_parts):
538 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000539
Tim Peters07e99cb2001-01-14 23:47:14 +0000540 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000541
Tim Peters07e99cb2001-01-14 23:47:14 +0000542 'message_parts' should be a string of selected parts
543 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000544
Tim Peters07e99cb2001-01-14 23:47:14 +0000545 'data' are tuples of message part envelope and data.
546 """
547 name = 'FETCH'
548 typ, dat = self._simple_command(name, message_set, message_parts)
549 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000550
551
Piers Lauder15e5d532001-07-20 10:52:06 +0000552 def getacl(self, mailbox):
553 """Get the ACLs for a mailbox.
554
555 (typ, [data]) = <instance>.getacl(mailbox)
556 """
557 typ, dat = self._simple_command('GETACL', mailbox)
558 return self._untagged_response(typ, dat, 'ACL')
559
560
Piers Lauderd80ef022005-06-01 23:50:52 +0000561 def getannotation(self, mailbox, entry, attribute):
562 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
563 Retrieve ANNOTATIONs."""
564
565 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
566 return self._untagged_response(typ, dat, 'ANNOTATION')
567
568
Piers Lauder3fca2912002-06-17 07:07:20 +0000569 def getquota(self, root):
570 """Get the quota root's resource usage and limits.
571
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000572 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000573
574 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000575 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000576 typ, dat = self._simple_command('GETQUOTA', root)
577 return self._untagged_response(typ, dat, 'QUOTA')
578
579
580 def getquotaroot(self, mailbox):
581 """Get the list of quota roots for the named mailbox.
582
583 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000584 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000585 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000586 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
587 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000588 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000589
590
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 def list(self, directory='""', pattern='*'):
592 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000593
Tim Peters07e99cb2001-01-14 23:47:14 +0000594 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000595
Tim Peters07e99cb2001-01-14 23:47:14 +0000596 'data' is list of LIST responses.
597 """
598 name = 'LIST'
599 typ, dat = self._simple_command(name, directory, pattern)
600 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000601
602
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 def login(self, user, password):
604 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000605
Tim Peters07e99cb2001-01-14 23:47:14 +0000606 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000607
Tim Peters07e99cb2001-01-14 23:47:14 +0000608 NB: 'password' will be quoted.
609 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000610 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
611 if typ != 'OK':
612 raise self.error(dat[-1])
613 self.state = 'AUTH'
614 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000615
616
Piers Laudere0273de2002-11-22 05:53:04 +0000617 def login_cram_md5(self, user, password):
618 """ Force use of CRAM-MD5 authentication.
619
620 (typ, [data]) = <instance>.login_cram_md5(user, password)
621 """
622 self.user, self.password = user, password
623 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
624
625
626 def _CRAM_MD5_AUTH(self, challenge):
627 """ Authobject to use with CRAM-MD5 authentication. """
628 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400629 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500630 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100631 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000632
633
Tim Peters07e99cb2001-01-14 23:47:14 +0000634 def logout(self):
635 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 Returns server 'BYE' response.
640 """
641 self.state = 'LOGOUT'
Victor Stinner74125a62019-04-15 18:23:20 +0200642 typ, dat = self._simple_command('LOGOUT')
Piers Lauder15e5d532001-07-20 10:52:06 +0000643 self.shutdown()
Tim Peters07e99cb2001-01-14 23:47:14 +0000644 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000645
646
Tim Peters07e99cb2001-01-14 23:47:14 +0000647 def lsub(self, directory='""', pattern='*'):
648 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000649
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000651
Tim Peters07e99cb2001-01-14 23:47:14 +0000652 'data' are tuples of message part envelope and data.
653 """
654 name = 'LSUB'
655 typ, dat = self._simple_command(name, directory, pattern)
656 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000657
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000658 def myrights(self, mailbox):
659 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
660
661 (typ, [data]) = <instance>.myrights(mailbox)
662 """
663 typ,dat = self._simple_command('MYRIGHTS', mailbox)
664 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000665
Piers Lauder15e5d532001-07-20 10:52:06 +0000666 def namespace(self):
667 """ Returns IMAP namespaces ala rfc2342
668
669 (typ, [data, ...]) = <instance>.namespace()
670 """
671 name = 'NAMESPACE'
672 typ, dat = self._simple_command(name)
673 return self._untagged_response(typ, dat, name)
674
675
Tim Peters07e99cb2001-01-14 23:47:14 +0000676 def noop(self):
677 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000678
Piers Laudere0273de2002-11-22 05:53:04 +0000679 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 """
681 if __debug__:
682 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000683 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000684 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000685
686
Tim Peters07e99cb2001-01-14 23:47:14 +0000687 def partial(self, message_num, message_part, start, length):
688 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000689
Tim Peters07e99cb2001-01-14 23:47:14 +0000690 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000691
Tim Peters07e99cb2001-01-14 23:47:14 +0000692 'data' is tuple of message part envelope and data.
693 """
694 name = 'PARTIAL'
695 typ, dat = self._simple_command(name, message_num, message_part, start, length)
696 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000697
698
Piers Laudere0273de2002-11-22 05:53:04 +0000699 def proxyauth(self, user):
700 """Assume authentication as "user".
701
702 Allows an authorised administrator to proxy into any user's
703 mailbox.
704
705 (typ, [data]) = <instance>.proxyauth(user)
706 """
707
708 name = 'PROXYAUTH'
709 return self._simple_command('PROXYAUTH', user)
710
711
Tim Peters07e99cb2001-01-14 23:47:14 +0000712 def rename(self, oldmailbox, newmailbox):
713 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000714
Piers Laudere0273de2002-11-22 05:53:04 +0000715 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000716 """
717 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000718
719
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 def search(self, charset, *criteria):
721 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000722
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000723 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000724
Tim Peters07e99cb2001-01-14 23:47:14 +0000725 'data' is space separated list of matching message numbers.
R David Murraya6429db2015-05-10 19:17:23 -0400726 If UTF8 is enabled, charset MUST be None.
Tim Peters07e99cb2001-01-14 23:47:14 +0000727 """
728 name = 'SEARCH'
729 if charset:
R David Murraya6429db2015-05-10 19:17:23 -0400730 if self.utf8_enabled:
731 raise IMAP4.error("Non-None charset not valid in UTF8 mode")
Guido van Rossum68468eb2003-02-27 20:14:51 +0000732 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000733 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000734 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000735 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
737
Piers Lauder14f39402005-08-31 10:46:29 +0000738 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000739 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000740
Tim Peters07e99cb2001-01-14 23:47:14 +0000741 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000742
Piers Lauder14f39402005-08-31 10:46:29 +0000743 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000744
Tim Peters07e99cb2001-01-14 23:47:14 +0000745 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000746
747 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
748 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000749 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 self.untagged_responses = {} # Flush old responses.
751 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000752 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 name = 'EXAMINE'
754 else:
755 name = 'SELECT'
756 typ, dat = self._simple_command(name, mailbox)
757 if typ != 'OK':
758 self.state = 'AUTH' # Might have been 'SELECTED'
759 return typ, dat
760 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000761 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 and not readonly:
763 if __debug__:
764 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000765 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 raise self.readonly('%s is not writable' % mailbox)
767 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000768
769
Piers Lauder15e5d532001-07-20 10:52:06 +0000770 def setacl(self, mailbox, who, what):
771 """Set a mailbox acl.
772
Piers Lauderf167dc32004-03-25 00:12:21 +0000773 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000774 """
775 return self._simple_command('SETACL', mailbox, who, what)
776
777
Piers Lauderd80ef022005-06-01 23:50:52 +0000778 def setannotation(self, *args):
779 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
780 Set ANNOTATIONs."""
781
782 typ, dat = self._simple_command('SETANNOTATION', *args)
783 return self._untagged_response(typ, dat, 'ANNOTATION')
784
785
Piers Lauder3fca2912002-06-17 07:07:20 +0000786 def setquota(self, root, limits):
787 """Set the quota root's resource limits.
788
789 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000790 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000791 typ, dat = self._simple_command('SETQUOTA', root, limits)
792 return self._untagged_response(typ, dat, 'QUOTA')
793
794
Piers Lauder15e5d532001-07-20 10:52:06 +0000795 def sort(self, sort_criteria, charset, *search_criteria):
796 """IMAP4rev1 extension SORT command.
797
798 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
799 """
800 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000801 #if not name in self.capabilities: # Let the server decide!
802 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000803 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000804 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000805 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000806 return self._untagged_response(typ, dat, name)
807
808
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000809 def starttls(self, ssl_context=None):
810 name = 'STARTTLS'
811 if not HAVE_SSL:
812 raise self.error('SSL support missing')
813 if self._tls_established:
814 raise self.abort('TLS session already established')
815 if name not in self.capabilities:
816 raise self.abort('TLS not supported by server')
817 # Generate a default SSL context if none was passed.
818 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100819 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000820 typ, dat = self._simple_command(name)
821 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100822 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600823 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000824 self.file = self.sock.makefile('rb')
825 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000826 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000827 else:
828 raise self.error("Couldn't establish TLS session")
829 return self._untagged_response(typ, dat, name)
830
831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 def status(self, mailbox, names):
833 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 (typ, [data]) = <instance>.status(mailbox, names)
836 """
837 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000838 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000839 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000840 typ, dat = self._simple_command(name, mailbox, names)
841 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000842
843
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 def store(self, message_set, command, flags):
845 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000846
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 (typ, [data]) = <instance>.store(message_set, command, flags)
848 """
849 if (flags[0],flags[-1]) != ('(',')'):
850 flags = '(%s)' % flags # Avoid quoting the flags
851 typ, dat = self._simple_command('STORE', message_set, command, flags)
852 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000853
854
Tim Peters07e99cb2001-01-14 23:47:14 +0000855 def subscribe(self, mailbox):
856 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000857
Tim Peters07e99cb2001-01-14 23:47:14 +0000858 (typ, [data]) = <instance>.subscribe(mailbox)
859 """
860 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000861
862
Martin v. Löwisd8921372003-11-10 06:44:44 +0000863 def thread(self, threading_algorithm, charset, *search_criteria):
864 """IMAPrev1 extension THREAD command.
865
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000866 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000867 """
868 name = 'THREAD'
869 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
870 return self._untagged_response(typ, dat, name)
871
872
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 def uid(self, command, *args):
874 """Execute "command arg ..." with messages identified by UID,
875 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 Returns response appropriate to 'command'.
880 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000881 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000882 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 raise self.error("Unknown IMAP4 UID command: %s" % command)
884 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000885 raise self.error("command %s illegal in state %s, "
886 "only allowed in states %s" %
887 (command, self.state,
888 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000890 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000891 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000892 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 else:
894 name = 'FETCH'
895 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000896
897
Tim Peters07e99cb2001-01-14 23:47:14 +0000898 def unsubscribe(self, mailbox):
899 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 (typ, [data]) = <instance>.unsubscribe(mailbox)
902 """
903 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000904
905
Dong-hee Nac5c42812020-04-27 23:52:55 +0900906 def unselect(self):
907 """Free server's resources associated with the selected mailbox
908 and returns the server to the authenticated state.
909 This command performs the same actions as CLOSE, except
910 that no messages are permanently removed from the currently
911 selected mailbox.
912
913 (typ, [data]) = <instance>.unselect()
914 """
915 try:
916 typ, data = self._simple_command('UNSELECT')
917 finally:
918 self.state = 'AUTH'
919 return typ, data
920
921
Tim Peters07e99cb2001-01-14 23:47:14 +0000922 def xatom(self, name, *args):
923 """Allow simple extension commands
924 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
Piers Lauder15e5d532001-07-20 10:52:06 +0000926 Assumes command is legal in current state.
927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000929
930 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000932 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000933 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000934 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000935 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000936 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000937 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000938
939
940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
943
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000945 if dat is None:
946 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 ur = self.untagged_responses
948 if __debug__:
949 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000950 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000951 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000952 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 ur[typ].append(dat)
954 else:
955 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
957
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 def _check_bye(self):
959 bye = self.untagged_responses.get('BYE')
960 if bye:
R David Murraya6429db2015-05-10 19:17:23 -0400961 raise self.abort(bye[-1].decode(self._encoding, 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000962
963
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000965
Tim Peters07e99cb2001-01-14 23:47:14 +0000966 if self.state not in Commands[name]:
967 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000968 raise self.error("command %s illegal in state %s, "
969 "only allowed in states %s" %
970 (name, self.state,
971 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000974 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000976
Raymond Hettinger54f02222002-06-01 14:18:47 +0000977 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 and not self.is_readonly:
979 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 tag = self._new_tag()
R David Murraya6429db2015-05-10 19:17:23 -0400982 name = bytes(name, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000983 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 for arg in args:
985 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000986 if isinstance(arg, str):
R David Murraya6429db2015-05-10 19:17:23 -0400987 arg = bytes(arg, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000988 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 literal = self.literal
991 if literal is not None:
992 self.literal = None
993 if type(literal) is type(self._command):
994 literator = literal
995 else:
996 literator = None
R David Murraya6429db2015-05-10 19:17:23 -0400997 data = data + bytes(' {%s}' % len(literal), self._encoding)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
Tim Peters07e99cb2001-01-14 23:47:14 +0000999 if __debug__:
1000 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001001 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001003 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +00001004
Tim Peters07e99cb2001-01-14 23:47:14 +00001005 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001006 self.send(data + 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 Rossumc2c07fa1998-04-09 13:51:46 +00001009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 if literal is None:
1011 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001012
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 while 1:
1014 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 while self._get_response():
1017 if self.tagged_commands[tag]: # BAD/NO?
1018 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001019
Tim Peters07e99cb2001-01-14 23:47:14 +00001020 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 if literator:
1023 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001024
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 if __debug__:
1026 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001027 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +00001028
Tim Peters07e99cb2001-01-14 23:47:14 +00001029 try:
Piers Lauder15e5d532001-07-20 10:52:06 +00001030 self.send(literal)
1031 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +02001032 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001034
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 if not literator:
1036 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001039
1040
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 def _command_complete(self, name, tag):
Victor Stinner74125a62019-04-15 18:23:20 +02001042 logout = (name == 'LOGOUT')
Antoine Pitroudac47912010-11-10 00:18:40 +00001043 # BYE is expected after LOGOUT
Victor Stinner74125a62019-04-15 18:23:20 +02001044 if not logout:
Antoine Pitroudac47912010-11-10 00:18:40 +00001045 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 try:
Victor Stinner74125a62019-04-15 18:23:20 +02001047 typ, data = self._get_tagged_response(tag, expect_bye=logout)
Guido van Rossumb940e112007-01-10 16:19:56 +00001048 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001049 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001050 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 raise self.error('command: %s => %s' % (name, val))
Victor Stinner74125a62019-04-15 18:23:20 +02001052 if not logout:
Antoine Pitroudac47912010-11-10 00:18:40 +00001053 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 if typ == 'BAD':
1055 raise self.error('%s command error: %s %s' % (name, typ, data))
1056 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001057
1058
Antoine Pitroudbe75192010-11-16 17:55:26 +00001059 def _get_capabilities(self):
1060 typ, dat = self.capability()
1061 if dat == [None]:
1062 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001063 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001064 dat = dat.upper()
1065 self.capabilities = tuple(dat.split())
1066
1067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001069
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 # Read response and store.
1071 #
1072 # Returns None for continuation responses,
1073 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001074
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001076
Tim Peters07e99cb2001-01-14 23:47:14 +00001077 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001078
Tim Peters07e99cb2001-01-14 23:47:14 +00001079 if self._match(self.tagre, resp):
1080 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001081 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001082 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001083
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001085 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001086 dat = self.mo.group('data')
1087 self.tagged_commands[tag] = (typ, [dat])
1088 else:
1089 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001090
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001092
Tim Peters07e99cb2001-01-14 23:47:14 +00001093 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001094 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001096
Tim Peters07e99cb2001-01-14 23:47:14 +00001097 if self.mo is None:
1098 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001099
Tim Peters07e99cb2001-01-14 23:47:14 +00001100 if self._match(Continuation, resp):
1101 self.continuation_response = self.mo.group('data')
1102 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001103
R David Murraya6429db2015-05-10 19:17:23 -04001104 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001105
Tim Peters07e99cb2001-01-14 23:47:14 +00001106 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001107 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001108 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001109 if dat is None: dat = b'' # Null untagged response
1110 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001111
Tim Peters07e99cb2001-01-14 23:47:14 +00001112 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001113
R David Murraya6429db2015-05-10 19:17:23 -04001114 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001117
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001118 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001119 if __debug__:
1120 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001121 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001122 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001123
Tim Peters07e99cb2001-01-14 23:47:14 +00001124 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001125
Tim Peters07e99cb2001-01-14 23:47:14 +00001126 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001127
Tim Peters07e99cb2001-01-14 23:47:14 +00001128 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001129
Tim Peters07e99cb2001-01-14 23:47:14 +00001130 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001131
Tim Peters07e99cb2001-01-14 23:47:14 +00001132 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001133
Tim Peters07e99cb2001-01-14 23:47:14 +00001134 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001135
Tim Peters07e99cb2001-01-14 23:47:14 +00001136 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001137 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001138 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001139 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001140
Tim Peters07e99cb2001-01-14 23:47:14 +00001141 if __debug__:
1142 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001143 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001144
Tim Peters07e99cb2001-01-14 23:47:14 +00001145 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001146
1147
Victor Stinner74125a62019-04-15 18:23:20 +02001148 def _get_tagged_response(self, tag, expect_bye=False):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001149
Tim Peters07e99cb2001-01-14 23:47:14 +00001150 while 1:
1151 result = self.tagged_commands[tag]
1152 if result is not None:
1153 del self.tagged_commands[tag]
1154 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001155
Victor Stinner74125a62019-04-15 18:23:20 +02001156 if expect_bye:
1157 typ = 'BYE'
1158 bye = self.untagged_responses.pop(typ, None)
1159 if bye is not None:
1160 # Server replies to the "LOGOUT" command with "BYE"
1161 return (typ, bye)
1162
R David Murray95ff7232014-02-07 13:44:57 -05001163 # If we've seen a BYE at this point, the socket will be
1164 # closed, so report the BYE now.
R David Murray95ff7232014-02-07 13:44:57 -05001165 self._check_bye()
1166
Tim Peters07e99cb2001-01-14 23:47:14 +00001167 # Some have reported "unexpected response" exceptions.
1168 # Note that ignoring them here causes loops.
1169 # Instead, send me details of the unexpected response and
1170 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001171
Tim Peters07e99cb2001-01-14 23:47:14 +00001172 try:
1173 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001174 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001175 if __debug__:
1176 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001177 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001178 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001179
1180
Tim Peters07e99cb2001-01-14 23:47:14 +00001181 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001182
Piers Lauder15e5d532001-07-20 10:52:06 +00001183 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001184 if not line:
1185 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001186
Tim Peters07e99cb2001-01-14 23:47:14 +00001187 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001188 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001189 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001190
Tim Peters07e99cb2001-01-14 23:47:14 +00001191 line = line[:-2]
1192 if __debug__:
1193 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001194 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001195 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001196 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001197 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001198
1199
Tim Peters07e99cb2001-01-14 23:47:14 +00001200 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001201
Tim Peters07e99cb2001-01-14 23:47:14 +00001202 # Run compiled regular expression match method on 's'.
1203 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001204
Tim Peters07e99cb2001-01-14 23:47:14 +00001205 self.mo = cre.match(s)
1206 if __debug__:
1207 if self.mo is not None and self.debug >= 5:
Serhiy Storchakaa4a30202017-11-28 22:54:42 +02001208 self._mesg("\tmatched %r => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001209 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001210
1211
Tim Peters07e99cb2001-01-14 23:47:14 +00001212 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001213
R David Murraya6429db2015-05-10 19:17:23 -04001214 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001215 self.tagnum = self.tagnum + 1
1216 self.tagged_commands[tag] = None
1217 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001218
1219
Tim Peters07e99cb2001-01-14 23:47:14 +00001220 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001221
Antoine Pitroub1436f12010-11-09 22:55:55 +00001222 arg = arg.replace('\\', '\\\\')
1223 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001224
Antoine Pitroub1436f12010-11-09 22:55:55 +00001225 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001226
1227
Tim Peters07e99cb2001-01-14 23:47:14 +00001228 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001229
Guido van Rossum68468eb2003-02-27 20:14:51 +00001230 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001231
1232
Tim Peters07e99cb2001-01-14 23:47:14 +00001233 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001234 if typ == 'NO':
1235 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001236 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001237 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001238 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001239 if __debug__:
1240 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001241 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001242 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001243
1244
Piers Lauderf2d7d152002-02-22 01:15:17 +00001245 if __debug__:
1246
1247 def _mesg(self, s, secs=None):
1248 if secs is None:
1249 secs = time.time()
1250 tm = time.strftime('%M:%S', time.localtime(secs))
1251 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1252 sys.stderr.flush()
1253
1254 def _dump_ur(self, dict):
1255 # Dump untagged responses (in `dict').
1256 l = dict.items()
1257 if not l: return
1258 t = '\n\t\t'
1259 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1260 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1261
1262 def _log(self, line):
1263 # Keep log of last `_cmd_log_len' interactions for debugging.
1264 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1265 self._cmd_log_idx += 1
1266 if self._cmd_log_idx >= self._cmd_log_len:
1267 self._cmd_log_idx = 0
1268
1269 def print_log(self):
1270 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1271 i, n = self._cmd_log_idx, self._cmd_log_len
1272 while n:
1273 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001274 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001275 except:
1276 pass
1277 i += 1
1278 if i >= self._cmd_log_len:
1279 i = 0
1280 n -= 1
1281
1282
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001283if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001284
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001285 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001286
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001287 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001288
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001289 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context[, timeout=None]]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001290
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001291 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001292 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001293 keyfile - PEM formatted file that contains your private key (default: None);
1294 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001295 ssl_context - a SSLContext object that contains your certificate chain
1296 and private key (default: None)
1297 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001298 certfile should not be set otherwise ValueError is raised.
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001299 timeout - socket timeout (default: None) If timeout is not given or is None,
1300 the global default socket timeout is used
Piers Laudera4f83132002-03-08 01:53:24 +00001301
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001302 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001303 """
Piers Laudera4f83132002-03-08 01:53:24 +00001304
1305
R David Murraya6429db2015-05-10 19:17:23 -04001306 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001307 certfile=None, ssl_context=None, timeout=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001308 if ssl_context is not None and keyfile is not None:
1309 raise ValueError("ssl_context and keyfile arguments are mutually "
1310 "exclusive")
1311 if ssl_context is not None and certfile is not None:
1312 raise ValueError("ssl_context and certfile arguments are mutually "
1313 "exclusive")
Christian Heimesd0486372016-09-10 23:23:33 +02001314 if keyfile is not None or certfile is not None:
1315 import warnings
Pablo Aguiar4b5e62d2018-11-01 11:33:35 +01001316 warnings.warn("keyfile and certfile are deprecated, use a "
Christian Heimesd0486372016-09-10 23:23:33 +02001317 "custom ssl_context instead", DeprecationWarning, 2)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001318 self.keyfile = keyfile
1319 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001320 if ssl_context is None:
1321 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1322 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001323 self.ssl_context = ssl_context
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001324 IMAP4.__init__(self, host, port, timeout)
Piers Laudera4f83132002-03-08 01:53:24 +00001325
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001326 def _create_socket(self, timeout):
1327 sock = IMAP4._create_socket(self, timeout)
Christian Heimes48aae572013-12-02 20:01:29 +01001328 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001329 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001330
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001331 def open(self, host='', port=IMAP4_SSL_PORT, timeout=None):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001332 """Setup connection to remote server on "host:port".
1333 (default: localhost:standard IMAP4 SSL port).
1334 This connection will be used by the routines:
1335 read, readline, send, shutdown.
1336 """
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001337 IMAP4.open(self, host, port, timeout)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001338
1339 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001340
1341
Piers Laudere0273de2002-11-22 05:53:04 +00001342class IMAP4_stream(IMAP4):
1343
1344 """IMAP4 client class over a stream
1345
1346 Instantiate with: IMAP4_stream(command)
1347
R David Murraya6429db2015-05-10 19:17:23 -04001348 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001349
1350 for more documentation see the docstring of the parent class IMAP4.
1351 """
1352
1353
1354 def __init__(self, command):
1355 self.command = command
1356 IMAP4.__init__(self)
1357
1358
Dong-hee Na13a7ee82020-01-08 02:28:10 +09001359 def open(self, host=None, port=None, timeout=None):
Piers Laudere0273de2002-11-22 05:53:04 +00001360 """Setup a stream connection.
1361 This connection will be used by the routines:
1362 read, readline, send, shutdown.
1363 """
1364 self.host = None # For compatibility with parent class
1365 self.port = None
1366 self.sock = None
1367 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001368 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001369 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001370 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1371 shell=True, close_fds=True)
1372 self.writefile = self.process.stdin
1373 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001374
1375 def read(self, size):
1376 """Read 'size' bytes from remote."""
1377 return self.readfile.read(size)
1378
1379
1380 def readline(self):
1381 """Read line from remote."""
1382 return self.readfile.readline()
1383
1384
1385 def send(self, data):
1386 """Send data to remote."""
1387 self.writefile.write(data)
1388 self.writefile.flush()
1389
1390
1391 def shutdown(self):
1392 """Close I/O established in "open"."""
1393 self.readfile.close()
1394 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001395 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001396
1397
1398
Guido van Rossumeda960a1998-06-18 14:24:28 +00001399class _Authenticator:
1400
Tim Peters07e99cb2001-01-14 23:47:14 +00001401 """Private class to provide en/decoding
1402 for base64-based authentication conversation.
1403 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001404
Tim Peters07e99cb2001-01-14 23:47:14 +00001405 def __init__(self, mechinst):
1406 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001407
Tim Peters07e99cb2001-01-14 23:47:14 +00001408 def process(self, data):
1409 ret = self.mech(self.decode(data))
1410 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001411 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001412 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001413
Tim Peters07e99cb2001-01-14 23:47:14 +00001414 def encode(self, inp):
1415 #
1416 # Invoke binascii.b2a_base64 iteratively with
1417 # short even length buffers, strip the trailing
1418 # line feed from the result and append. "Even"
1419 # means a number that factors to both 6 and 8,
1420 # so when it gets to the end of the 8-bit input
1421 # there's no partial 6-bit output.
1422 #
R David Murray774a39f2013-02-19 12:17:31 -05001423 oup = b''
1424 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001425 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001426 while inp:
1427 if len(inp) > 48:
1428 t = inp[:48]
1429 inp = inp[48:]
1430 else:
1431 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001432 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 e = binascii.b2a_base64(t)
1434 if e:
1435 oup = oup + e[:-1]
1436 return oup
1437
1438 def decode(self, inp):
1439 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001440 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 return binascii.a2b_base64(inp)
1442
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001443Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1444Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001445
1446def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001447 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001448
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001449 Return corresponding local time. The return value is a
1450 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001451 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001452
Tim Peters07e99cb2001-01-14 23:47:14 +00001453 mo = InternalDate.match(resp)
1454 if not mo:
1455 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001456
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 mon = Mon2num[mo.group('mon')]
1458 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001459
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001460 day = int(mo.group('day'))
1461 year = int(mo.group('year'))
1462 hour = int(mo.group('hour'))
1463 min = int(mo.group('min'))
1464 sec = int(mo.group('sec'))
1465 zoneh = int(mo.group('zoneh'))
1466 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001467
Tim Peters07e99cb2001-01-14 23:47:14 +00001468 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001469
Tim Peters07e99cb2001-01-14 23:47:14 +00001470 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001471 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001472 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001473
Tim Peters07e99cb2001-01-14 23:47:14 +00001474 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001475 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001476
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001477 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001478
1479
1480
1481def Int2AP(num):
1482
Tim Peters07e99cb2001-01-14 23:47:14 +00001483 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001484
Christian Heimesfb5faf02008-11-05 19:39:50 +00001485 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001486 num = int(abs(num))
1487 while num:
1488 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001489 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001490 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001491
1492
1493
1494def ParseFlags(resp):
1495
Tim Peters07e99cb2001-01-14 23:47:14 +00001496 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001497
Tim Peters07e99cb2001-01-14 23:47:14 +00001498 mo = Flags.match(resp)
1499 if not mo:
1500 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001501
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001502 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001503
1504
1505def Time2Internaldate(date_time):
1506
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001507 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001508
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001509 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001510 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001511 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001512 representing local time, an instance of time.struct_time (as
1513 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001514 double-quoted string. In the last case, it is assumed to already
1515 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001516 """
Fred Drakedb519202002-01-05 17:17:09 +00001517 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001518 dt = datetime.fromtimestamp(date_time,
1519 timezone.utc).astimezone()
1520 elif isinstance(date_time, tuple):
1521 try:
1522 gmtoff = date_time.tm_gmtoff
1523 except AttributeError:
1524 if time.daylight:
1525 dst = date_time[8]
1526 if dst == -1:
1527 dst = time.localtime(time.mktime(date_time))[8]
1528 gmtoff = -(time.timezone, time.altzone)[dst]
1529 else:
1530 gmtoff = -time.timezone
1531 delta = timedelta(seconds=gmtoff)
1532 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1533 elif isinstance(date_time, datetime):
1534 if date_time.tzinfo is None:
1535 raise ValueError("date_time must be aware")
1536 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001537 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001538 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001539 else:
1540 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001541 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1542 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001543
1544
1545
Guido van Rossum8c062211999-12-13 23:27:45 +00001546if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001547
Piers Laudere0273de2002-11-22 05:53:04 +00001548 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1549 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1550 # to test the IMAP4_stream class
1551
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001552 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001553
Tim Peters07e99cb2001-01-14 23:47:14 +00001554 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001555 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001556 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001557 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001558
Piers Laudere0273de2002-11-22 05:53:04 +00001559 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001560 for opt,val in optlist:
1561 if opt == '-d':
1562 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001563 elif opt == '-s':
1564 stream_command = val
1565 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001566
Tim Peters07e99cb2001-01-14 23:47:14 +00001567 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001568
Tim Peters07e99cb2001-01-14 23:47:14 +00001569 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001570
Tim Peters07e99cb2001-01-14 23:47:14 +00001571 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001572 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001573
Piers Lauder47404ff2003-04-29 23:40:59 +00001574 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 +00001575 test_seq1 = (
1576 ('login', (USER, PASSWD)),
1577 ('create', ('/tmp/xxx 1',)),
1578 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1579 ('CREATE', ('/tmp/yyz 2',)),
1580 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1581 ('list', ('/tmp', 'yy*')),
1582 ('select', ('/tmp/yyz 2',)),
1583 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001584 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
R David Murray44b548d2016-09-08 13:59:53 -04001585 ('store', ('1', 'FLAGS', r'(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001586 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001587 ('expunge', ()),
1588 ('recent', ()),
1589 ('close', ()),
1590 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001591
Tim Peters07e99cb2001-01-14 23:47:14 +00001592 test_seq2 = (
1593 ('select', ()),
1594 ('response',('UIDVALIDITY',)),
1595 ('uid', ('SEARCH', 'ALL')),
1596 ('response', ('EXISTS',)),
1597 ('append', (None, None, None, test_mesg)),
1598 ('recent', ()),
1599 ('logout', ()),
1600 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001601
Tim Peters07e99cb2001-01-14 23:47:14 +00001602 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001603 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001604 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001605 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001606 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001607 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001608
Tim Peters07e99cb2001-01-14 23:47:14 +00001609 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001610 if stream_command:
1611 M = IMAP4_stream(stream_command)
1612 else:
1613 M = IMAP4(host)
1614 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001615 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001616 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001617 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001618
Tim Peters07e99cb2001-01-14 23:47:14 +00001619 for cmd,args in test_seq1:
1620 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001621
Tim Peters07e99cb2001-01-14 23:47:14 +00001622 for ml in run('list', ('/tmp/', 'yy%')):
1623 mo = re.match(r'.*"([^"]+)"$', ml)
1624 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001625 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001626 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001627
Tim Peters07e99cb2001-01-14 23:47:14 +00001628 for cmd,args in test_seq2:
1629 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001630
Tim Peters07e99cb2001-01-14 23:47:14 +00001631 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1632 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001633
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001634 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001635 if not uid: continue
1636 run('uid', ('FETCH', '%s' % uid[-1],
1637 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001638
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001639 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001640
Tim Peters07e99cb2001-01-14 23:47:14 +00001641 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001642 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001643
Tim Peters07e99cb2001-01-14 23:47:14 +00001644 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001645 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001646If you would like to see debugging output,
1647try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001648''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001649
Tim Peters07e99cb2001-01-14 23:47:14 +00001650 raise