blob: 0dfd8524a53a3542bd52b6019f7145650ec7353a [file] [log] [blame]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001"""IMAP4 client.
2
3Based on RFC 2060.
4
Tim Peters07e99cb2001-01-14 23:47:14 +00005Public class: IMAP4
6Public variable: Debug
7Public functions: Internaldate2tuple
8 Int2AP
9 ParseFlags
10 Time2Internaldate
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000011"""
Guido van Rossumb1f08121998-06-25 02:22:16 +000012
Guido van Rossum98d9fd32000-02-28 15:12:25 +000013# Author: Piers Lauder <piers@cs.su.oz.au> December 1997.
Tim Peters07e99cb2001-01-14 23:47:14 +000014#
Guido van Rossum98d9fd32000-02-28 15:12:25 +000015# Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998.
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +000016# String method conversion by ESR, February 2001.
Piers Lauder15e5d532001-07-20 10:52:06 +000017# GET/SETACL contributed by Anthony Baxter <anthony@interlink.com.au> April 2001.
Piers Laudera4f83132002-03-08 01:53:24 +000018# IMAP4_SSL contributed by Tino Lange <Tino.Lange@isg.de> March 2002.
Piers Lauder3fca2912002-06-17 07:07:20 +000019# GET/SETQUOTA contributed by Andreas Zeidler <az@kreativkombinat.de> June 2002.
Piers Laudere0273de2002-11-22 05:53:04 +000020# PROXYAUTH contributed by Rick Holbert <holbert.13@osu.edu> November 2002.
Piers Lauderd80ef022005-06-01 23:50:52 +000021# GET/SETANNOTATION contributed by Tomas Lindroos <skitta@abo.fi> June 2005.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000022
Piers Lauderbe5615e2005-08-31 10:50:03 +000023__version__ = "2.58"
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000024
Alexander Belopolsky2420d832012-04-29 15:56:49 -040025import binascii, errno, random, re, socket, subprocess, sys, time, calendar
Alexander Belopolsky8141cc72012-06-22 21:03:39 -040026from datetime import datetime, timezone, timedelta
R David Murrayfcb6d6a2013-03-19 13:52:33 -040027from io import DEFAULT_BUFFER_SIZE
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000028
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000029try:
30 import ssl
31 HAVE_SSL = True
Brett Cannoncd171c82013-07-04 17:43:24 -040032except ImportError:
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000033 HAVE_SSL = False
34
Thomas Wouters47b49bf2007-08-30 22:15:33 +000035__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000036 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000037
Tim Peters07e99cb2001-01-14 23:47:14 +000038# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000039
Christian Heimesfb5faf02008-11-05 19:39:50 +000040CRLF = b'\r\n'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000041Debug = 0
42IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000043IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000044AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000045
Georg Brandlca580f42013-10-27 06:52:14 +010046# Maximal line length when calling readline(). This is to prevent
47# reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1)
R David Murray936da2a2015-03-22 16:17:46 -040048# don't specify a line length. RFC 2683 suggests limiting client
49# command lines to 1000 octets and that servers should be prepared
50# to accept command lines up to 8000 octets, so we used to use 10K here.
51# In the modern world (eg: gmail) the response to, for example, a
52# search command can be quite large, so we now use 1M.
53_MAXLINE = 1000000
Georg Brandlca580f42013-10-27 06:52:14 +010054
55
Tim Peters07e99cb2001-01-14 23:47:14 +000056# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000057
58Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000059 # name valid states
60 'APPEND': ('AUTH', 'SELECTED'),
61 'AUTHENTICATE': ('NONAUTH',),
62 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
63 'CHECK': ('SELECTED',),
64 'CLOSE': ('SELECTED',),
65 'COPY': ('SELECTED',),
66 'CREATE': ('AUTH', 'SELECTED'),
67 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000068 'DELETEACL': ('AUTH', 'SELECTED'),
R David Murraya6429db2015-05-10 19:17:23 -040069 'ENABLE': ('AUTH', ),
Tim Peters07e99cb2001-01-14 23:47:14 +000070 'EXAMINE': ('AUTH', 'SELECTED'),
71 'EXPUNGE': ('SELECTED',),
72 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000073 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000074 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000075 'GETQUOTA': ('AUTH', 'SELECTED'),
76 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000077 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000078 'LIST': ('AUTH', 'SELECTED'),
79 'LOGIN': ('NONAUTH',),
80 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
81 'LSUB': ('AUTH', 'SELECTED'),
Matěj Ceplcaa331d2018-07-23 13:28:54 +020082 'MOVE': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000083 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000084 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000085 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000086 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000087 'RENAME': ('AUTH', 'SELECTED'),
88 'SEARCH': ('SELECTED',),
89 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000090 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000091 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000092 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000093 'SORT': ('SELECTED',),
Antoine Pitrouf3b001f2010-11-12 18:49:16 +000094 'STARTTLS': ('NONAUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000095 'STATUS': ('AUTH', 'SELECTED'),
96 'STORE': ('SELECTED',),
97 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000098 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000099 'UID': ('SELECTED',),
100 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
101 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102
Tim Peters07e99cb2001-01-14 23:47:14 +0000103# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104
Christian Heimesfb5faf02008-11-05 19:39:50 +0000105Continuation = re.compile(br'\+( (?P<data>.*))?')
106Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
107InternalDate = re.compile(br'.*INTERNALDATE "'
108 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
109 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
110 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
111 br'"')
R David Murraya6429db2015-05-10 19:17:23 -0400112# Literal is no longer used; kept for backward compatibility.
Christian Heimesfb5faf02008-11-05 19:39:50 +0000113Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
114MapCRLF = re.compile(br'\r\n|\r|\n')
R David Murray317f64f2016-01-02 17:18:34 -0500115# We no longer exclude the ']' character from the data portion of the response
116# code, even though it violates the RFC. Popular IMAP servers such as Gmail
117# allow flags with ']', and there are programs (including imaplib!) that can
118# produce them. The problem with this is if the 'text' portion of the response
119# includes a ']' we'll parse the response wrong (which is the point of the RFC
120# restriction). However, that seems less likely to be a problem in practice
121# than being unable to correctly parse flags that include ']' chars, which
122# was reported as a real-world problem in issue #21815.
123Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>.*))?\]')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000124Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
R David Murraya6429db2015-05-10 19:17:23 -0400125# Untagged_status is no longer used; kept for backward compatibility
Antoine Pitroufd036452008-08-19 17:56:33 +0000126Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +0000127 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
R David Murraya6429db2015-05-10 19:17:23 -0400128# We compile these in _mode_xxx.
129_Literal = br'.*{(?P<size>\d+)}$'
130_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000131
132
133
134class IMAP4:
135
R David Murray44b548d2016-09-08 13:59:53 -0400136 r"""IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000137
Tim Peters07e99cb2001-01-14 23:47:14 +0000138 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000139
Tim Peters07e99cb2001-01-14 23:47:14 +0000140 host - host's name (default: localhost);
141 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000142
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 All IMAP4rev1 commands are supported by methods of the same
144 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000145
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 All arguments to commands are converted to strings, except for
147 AUTHENTICATE, and the last argument to APPEND which is passed as
148 an IMAP4 literal. If necessary (the string contains any
149 non-printing characters or white-space and isn't enclosed with
150 either parentheses or double quotes) each string is quoted.
151 However, the 'password' argument to the LOGIN command is always
152 quoted. If you want to avoid having an argument string quoted
153 (eg: the 'flags' argument to STORE) then enclose the string in
154 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000155
Tim Peters07e99cb2001-01-14 23:47:14 +0000156 Each command returns a tuple: (type, [data, ...]) where 'type'
157 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000158 tagged response, or untagged results from command. Each 'data'
159 is either a string, or a tuple. If a tuple, then the first part
160 is the header of the response, and the second part contains
161 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000162
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 Errors raise the exception class <instance>.error("<reason>").
164 IMAP4 server errors raise <instance>.abort("<reason>"),
165 which is a sub-class of 'error'. Mailbox status changes
166 from READ-WRITE to READ-ONLY raise the exception class
167 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000168
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 "error" exceptions imply a program error.
170 "abort" exceptions imply the connection should be reset, and
171 the command re-tried.
172 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000173
Piers Lauderd80ef022005-06-01 23:50:52 +0000174 Note: to use this module, you must read the RFCs pertaining to the
175 IMAP4 protocol, as the semantics of the arguments to each IMAP4
176 command are left to the invoker, not to mention the results. Also,
177 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000178 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000179
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 class error(Exception): pass # Logical errors - debug required
181 class abort(error): pass # Service errors - close and retry
182 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
R David Murraya6429db2015-05-10 19:17:23 -0400184 def __init__(self, host='', port=IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 self.debug = Debug
186 self.state = 'LOGOUT'
187 self.literal = None # A literal argument to a command
188 self.tagged_commands = {} # Tagged commands awaiting response
189 self.untagged_responses = {} # {typ: [data, ...], ...}
190 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000191 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000192 self.tagnum = 0
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000193 self._tls_established = False
R David Murraya6429db2015-05-10 19:17:23 -0400194 self._mode_ascii()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000195
Tim Peters07e99cb2001-01-14 23:47:14 +0000196 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000197
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000199
Victor Stinner33e649c2011-01-05 23:01:37 +0000200 try:
201 self._connect()
202 except Exception:
203 try:
204 self.shutdown()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200205 except OSError:
Victor Stinner33e649c2011-01-05 23:01:37 +0000206 pass
207 raise
208
R David Murraya6429db2015-05-10 19:17:23 -0400209 def _mode_ascii(self):
210 self.utf8_enabled = False
211 self._encoding = 'ascii'
212 self.Literal = re.compile(_Literal, re.ASCII)
213 self.Untagged_status = re.compile(_Untagged_status, re.ASCII)
214
215
216 def _mode_utf8(self):
217 self.utf8_enabled = True
218 self._encoding = 'utf-8'
219 self.Literal = re.compile(_Literal)
220 self.Untagged_status = re.compile(_Untagged_status)
221
Victor Stinner33e649c2011-01-05 23:01:37 +0000222
223 def _connect(self):
Tim Peters07e99cb2001-01-14 23:47:14 +0000224 # Create unique tag for this session,
225 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000226
Piers Lauder2dfc1682005-07-05 04:20:07 +0000227 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000228 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000229 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000230 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000231
Tim Peters07e99cb2001-01-14 23:47:14 +0000232 # Get server welcome message,
233 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000234
Tim Peters07e99cb2001-01-14 23:47:14 +0000235 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000236 self._cmd_log_len = 10
237 self._cmd_log_idx = 0
238 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000239 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000240 self._mesg('imaplib version %s' % __version__)
241 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000242
Tim Peters07e99cb2001-01-14 23:47:14 +0000243 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000244 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000245 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000246 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000247 self.state = 'NONAUTH'
248 else:
249 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000250
Antoine Pitroudbe75192010-11-16 17:55:26 +0000251 self._get_capabilities()
Tim Peters07e99cb2001-01-14 23:47:14 +0000252 if __debug__:
253 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000254 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000255
Tim Peters07e99cb2001-01-14 23:47:14 +0000256 for version in AllowedVersions:
257 if not version in self.capabilities:
258 continue
259 self.PROTOCOL_VERSION = version
260 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000261
Tim Peters07e99cb2001-01-14 23:47:14 +0000262 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000263
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000264
Tim Peters07e99cb2001-01-14 23:47:14 +0000265 def __getattr__(self, attr):
266 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000267 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000268 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000269 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000270
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300271 def __enter__(self):
272 return self
273
274 def __exit__(self, *args):
275 try:
276 self.logout()
277 except OSError:
278 pass
Guido van Rossum26367a01998-09-28 15:34:46 +0000279
280
Piers Lauder15e5d532001-07-20 10:52:06 +0000281 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000282
283
Christian Heimesfb5faf02008-11-05 19:39:50 +0000284 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000285 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000286
Piers Lauderf97b2d72002-06-05 22:31:57 +0000287 def open(self, host = '', port = IMAP4_PORT):
288 """Setup connection to remote server on "host:port"
289 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000290 This connection will be used by the routines:
291 read, readline, send, shutdown.
292 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000293 self.host = host
294 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000295 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000296 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000297
298
Piers Lauder15e5d532001-07-20 10:52:06 +0000299 def read(self, size):
300 """Read 'size' bytes from remote."""
Charles-François Natali1f4560c2011-05-24 23:47:49 +0200301 return self.file.read(size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000302
303
304 def readline(self):
305 """Read line from remote."""
Georg Brandlca580f42013-10-27 06:52:14 +0100306 line = self.file.readline(_MAXLINE + 1)
307 if len(line) > _MAXLINE:
308 raise self.error("got more than %d bytes" % _MAXLINE)
309 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000310
311
312 def send(self, data):
313 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000314 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000315
Piers Lauderf2d7d152002-02-22 01:15:17 +0000316
Piers Lauder15e5d532001-07-20 10:52:06 +0000317 def shutdown(self):
318 """Close I/O established in "open"."""
319 self.file.close()
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000320 try:
321 self.sock.shutdown(socket.SHUT_RDWR)
Victor Stinner83a2c282017-05-15 17:33:45 +0200322 except OSError as exc:
323 # The server might already have closed the connection.
324 # On Windows, this may result in WSAEINVAL (error 10022):
325 # An invalid operation was attempted.
326 if (exc.errno != errno.ENOTCONN
327 and getattr(exc, 'winerror', 0) != 10022):
Antoine Pitrou81c87c52010-11-10 08:59:25 +0000328 raise
329 finally:
330 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000331
332
333 def socket(self):
334 """Return socket instance used to connect to IMAP4 server.
335
336 socket = <instance>.socket()
337 """
338 return self.sock
339
340
341
342 # Utility methods
343
344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 def recent(self):
346 """Return most recent 'RECENT' responses if any exist,
347 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000348
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000350
Tim Peters07e99cb2001-01-14 23:47:14 +0000351 'data' is None if no new messages,
352 else list of RECENT responses, most recent last.
353 """
354 name = 'RECENT'
355 typ, dat = self._untagged_response('OK', [None], name)
356 if dat[-1]:
357 return typ, dat
358 typ, dat = self.noop() # Prod server for response
359 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000360
361
Tim Peters07e99cb2001-01-14 23:47:14 +0000362 def response(self, code):
363 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000364
Tim Peters07e99cb2001-01-14 23:47:14 +0000365 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 (code, [data]) = <instance>.response(code)
368 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000369 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000370
371
Guido van Rossum26367a01998-09-28 15:34:46 +0000372
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000374
375
Tim Peters07e99cb2001-01-14 23:47:14 +0000376 def append(self, mailbox, flags, date_time, message):
377 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
Tim Peters07e99cb2001-01-14 23:47:14 +0000379 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 All args except `message' can be None.
382 """
383 name = 'APPEND'
384 if not mailbox:
385 mailbox = 'INBOX'
386 if flags:
387 if (flags[0],flags[-1]) != ('(',')'):
388 flags = '(%s)' % flags
389 else:
390 flags = None
391 if date_time:
392 date_time = Time2Internaldate(date_time)
393 else:
394 date_time = None
R David Murraya6429db2015-05-10 19:17:23 -0400395 literal = MapCRLF.sub(CRLF, message)
396 if self.utf8_enabled:
397 literal = b'UTF8 (' + literal + b')'
398 self.literal = literal
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000400
401
Tim Peters07e99cb2001-01-14 23:47:14 +0000402 def authenticate(self, mechanism, authobject):
403 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 'mechanism' specifies which authentication mechanism is to
406 be used - it must appear in <instance>.capabilities in the
407 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000408
Tim Peters07e99cb2001-01-14 23:47:14 +0000409 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000412
R David Murray774a39f2013-02-19 12:17:31 -0500413 It will be called to process server continuation responses; the
414 response argument it is passed will be a bytes. It should return bytes
415 data that will be base64 encoded and sent to the server. It should
416 return None if the client abort response '*' should be sent instead.
Tim Peters07e99cb2001-01-14 23:47:14 +0000417 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000418 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000419 # XXX: shouldn't this code be removed, not commented out?
420 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000421 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000422 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 self.literal = _Authenticator(authobject).process
424 typ, dat = self._simple_command('AUTHENTICATE', mech)
425 if typ != 'OK':
R David Murrayb079c072016-12-24 21:32:26 -0500426 raise self.error(dat[-1].decode('utf-8', 'replace'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000427 self.state = 'AUTH'
428 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
430
Piers Lauderd80ef022005-06-01 23:50:52 +0000431 def capability(self):
432 """(typ, [data]) = <instance>.capability()
433 Fetch capabilities list from server."""
434
435 name = 'CAPABILITY'
436 typ, dat = self._simple_command(name)
437 return self._untagged_response(typ, dat, name)
438
439
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 def check(self):
441 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
Tim Peters07e99cb2001-01-14 23:47:14 +0000443 (typ, [data]) = <instance>.check()
444 """
445 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000446
447
Tim Peters07e99cb2001-01-14 23:47:14 +0000448 def close(self):
449 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000450
Tim Peters07e99cb2001-01-14 23:47:14 +0000451 Deleted messages are removed from writable mailbox.
452 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000453
Tim Peters07e99cb2001-01-14 23:47:14 +0000454 (typ, [data]) = <instance>.close()
455 """
456 try:
457 typ, dat = self._simple_command('CLOSE')
458 finally:
459 self.state = 'AUTH'
460 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
462
Tim Peters07e99cb2001-01-14 23:47:14 +0000463 def copy(self, message_set, new_mailbox):
464 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000465
Tim Peters07e99cb2001-01-14 23:47:14 +0000466 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
467 """
468 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000469
470
Tim Peters07e99cb2001-01-14 23:47:14 +0000471 def create(self, mailbox):
472 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000473
Tim Peters07e99cb2001-01-14 23:47:14 +0000474 (typ, [data]) = <instance>.create(mailbox)
475 """
476 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000477
478
Tim Peters07e99cb2001-01-14 23:47:14 +0000479 def delete(self, mailbox):
480 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000481
Tim Peters07e99cb2001-01-14 23:47:14 +0000482 (typ, [data]) = <instance>.delete(mailbox)
483 """
484 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000485
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000486 def deleteacl(self, mailbox, who):
487 """Delete the ACLs (remove any rights) set for who on mailbox.
488
489 (typ, [data]) = <instance>.deleteacl(mailbox, who)
490 """
491 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000492
R David Murraya6429db2015-05-10 19:17:23 -0400493 def enable(self, capability):
494 """Send an RFC5161 enable string to the server.
495
496 (typ, [data]) = <intance>.enable(capability)
497 """
498 if 'ENABLE' not in self.capabilities:
499 raise IMAP4.error("Server does not support ENABLE")
500 typ, data = self._simple_command('ENABLE', capability)
501 if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper():
502 self._mode_utf8()
503 return typ, data
504
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 def expunge(self):
506 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 'data' is list of 'EXPUNGE'd message numbers in order received.
513 """
514 name = 'EXPUNGE'
515 typ, dat = self._simple_command(name)
516 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000517
518
Tim Peters07e99cb2001-01-14 23:47:14 +0000519 def fetch(self, message_set, message_parts):
520 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000521
Tim Peters07e99cb2001-01-14 23:47:14 +0000522 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000523
Tim Peters07e99cb2001-01-14 23:47:14 +0000524 'message_parts' should be a string of selected parts
525 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000526
Tim Peters07e99cb2001-01-14 23:47:14 +0000527 'data' are tuples of message part envelope and data.
528 """
529 name = 'FETCH'
530 typ, dat = self._simple_command(name, message_set, message_parts)
531 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000532
533
Piers Lauder15e5d532001-07-20 10:52:06 +0000534 def getacl(self, mailbox):
535 """Get the ACLs for a mailbox.
536
537 (typ, [data]) = <instance>.getacl(mailbox)
538 """
539 typ, dat = self._simple_command('GETACL', mailbox)
540 return self._untagged_response(typ, dat, 'ACL')
541
542
Piers Lauderd80ef022005-06-01 23:50:52 +0000543 def getannotation(self, mailbox, entry, attribute):
544 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
545 Retrieve ANNOTATIONs."""
546
547 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
548 return self._untagged_response(typ, dat, 'ANNOTATION')
549
550
Piers Lauder3fca2912002-06-17 07:07:20 +0000551 def getquota(self, root):
552 """Get the quota root's resource usage and limits.
553
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000554 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000555
556 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000557 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000558 typ, dat = self._simple_command('GETQUOTA', root)
559 return self._untagged_response(typ, dat, 'QUOTA')
560
561
562 def getquotaroot(self, mailbox):
563 """Get the list of quota roots for the named mailbox.
564
565 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000566 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000567 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000568 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
569 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000570 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000571
572
Tim Peters07e99cb2001-01-14 23:47:14 +0000573 def list(self, directory='""', pattern='*'):
574 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000575
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000577
Tim Peters07e99cb2001-01-14 23:47:14 +0000578 'data' is list of LIST responses.
579 """
580 name = 'LIST'
581 typ, dat = self._simple_command(name, directory, pattern)
582 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000583
584
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 def login(self, user, password):
586 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000587
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000589
Tim Peters07e99cb2001-01-14 23:47:14 +0000590 NB: 'password' will be quoted.
591 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
593 if typ != 'OK':
594 raise self.error(dat[-1])
595 self.state = 'AUTH'
596 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000597
598
Piers Laudere0273de2002-11-22 05:53:04 +0000599 def login_cram_md5(self, user, password):
600 """ Force use of CRAM-MD5 authentication.
601
602 (typ, [data]) = <instance>.login_cram_md5(user, password)
603 """
604 self.user, self.password = user, password
605 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
606
607
608 def _CRAM_MD5_AUTH(self, challenge):
609 """ Authobject to use with CRAM-MD5 authentication. """
610 import hmac
R David Murraya6429db2015-05-10 19:17:23 -0400611 pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
R David Murray774a39f2013-02-19 12:17:31 -0500612 else self.password)
Christian Heimes634919a2013-11-20 17:23:06 +0100613 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
Piers Laudere0273de2002-11-22 05:53:04 +0000614
615
Tim Peters07e99cb2001-01-14 23:47:14 +0000616 def logout(self):
617 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000618
Tim Peters07e99cb2001-01-14 23:47:14 +0000619 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000620
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 Returns server 'BYE' response.
622 """
623 self.state = 'LOGOUT'
624 try: typ, dat = self._simple_command('LOGOUT')
625 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000626 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000627 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 return 'BYE', self.untagged_responses['BYE']
629 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000630
631
Tim Peters07e99cb2001-01-14 23:47:14 +0000632 def lsub(self, directory='""', pattern='*'):
633 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000634
Tim Peters07e99cb2001-01-14 23:47:14 +0000635 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 'data' are tuples of message part envelope and data.
638 """
639 name = 'LSUB'
640 typ, dat = self._simple_command(name, directory, pattern)
641 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000643 def myrights(self, mailbox):
644 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
645
646 (typ, [data]) = <instance>.myrights(mailbox)
647 """
648 typ,dat = self._simple_command('MYRIGHTS', mailbox)
649 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000650
Piers Lauder15e5d532001-07-20 10:52:06 +0000651 def namespace(self):
652 """ Returns IMAP namespaces ala rfc2342
653
654 (typ, [data, ...]) = <instance>.namespace()
655 """
656 name = 'NAMESPACE'
657 typ, dat = self._simple_command(name)
658 return self._untagged_response(typ, dat, name)
659
660
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 def noop(self):
662 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000663
Piers Laudere0273de2002-11-22 05:53:04 +0000664 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000665 """
666 if __debug__:
667 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000668 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000669 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000670
671
Tim Peters07e99cb2001-01-14 23:47:14 +0000672 def partial(self, message_num, message_part, start, length):
673 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000674
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000676
Tim Peters07e99cb2001-01-14 23:47:14 +0000677 'data' is tuple of message part envelope and data.
678 """
679 name = 'PARTIAL'
680 typ, dat = self._simple_command(name, message_num, message_part, start, length)
681 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000682
683
Piers Laudere0273de2002-11-22 05:53:04 +0000684 def proxyauth(self, user):
685 """Assume authentication as "user".
686
687 Allows an authorised administrator to proxy into any user's
688 mailbox.
689
690 (typ, [data]) = <instance>.proxyauth(user)
691 """
692
693 name = 'PROXYAUTH'
694 return self._simple_command('PROXYAUTH', user)
695
696
Tim Peters07e99cb2001-01-14 23:47:14 +0000697 def rename(self, oldmailbox, newmailbox):
698 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000699
Piers Laudere0273de2002-11-22 05:53:04 +0000700 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000701 """
702 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000703
704
Tim Peters07e99cb2001-01-14 23:47:14 +0000705 def search(self, charset, *criteria):
706 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000707
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000708 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000709
Tim Peters07e99cb2001-01-14 23:47:14 +0000710 'data' is space separated list of matching message numbers.
R David Murraya6429db2015-05-10 19:17:23 -0400711 If UTF8 is enabled, charset MUST be None.
Tim Peters07e99cb2001-01-14 23:47:14 +0000712 """
713 name = 'SEARCH'
714 if charset:
R David Murraya6429db2015-05-10 19:17:23 -0400715 if self.utf8_enabled:
716 raise IMAP4.error("Non-None charset not valid in UTF8 mode")
Guido van Rossum68468eb2003-02-27 20:14:51 +0000717 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000718 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000719 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000721
722
Piers Lauder14f39402005-08-31 10:46:29 +0000723 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000724 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000725
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000727
Piers Lauder14f39402005-08-31 10:46:29 +0000728 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000731
732 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
733 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000735 self.untagged_responses = {} # Flush old responses.
736 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000737 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000738 name = 'EXAMINE'
739 else:
740 name = 'SELECT'
741 typ, dat = self._simple_command(name, mailbox)
742 if typ != 'OK':
743 self.state = 'AUTH' # Might have been 'SELECTED'
744 return typ, dat
745 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000746 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 and not readonly:
748 if __debug__:
749 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000750 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000751 raise self.readonly('%s is not writable' % mailbox)
752 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000753
754
Piers Lauder15e5d532001-07-20 10:52:06 +0000755 def setacl(self, mailbox, who, what):
756 """Set a mailbox acl.
757
Piers Lauderf167dc32004-03-25 00:12:21 +0000758 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000759 """
760 return self._simple_command('SETACL', mailbox, who, what)
761
762
Piers Lauderd80ef022005-06-01 23:50:52 +0000763 def setannotation(self, *args):
764 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
765 Set ANNOTATIONs."""
766
767 typ, dat = self._simple_command('SETANNOTATION', *args)
768 return self._untagged_response(typ, dat, 'ANNOTATION')
769
770
Piers Lauder3fca2912002-06-17 07:07:20 +0000771 def setquota(self, root, limits):
772 """Set the quota root's resource limits.
773
774 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000775 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000776 typ, dat = self._simple_command('SETQUOTA', root, limits)
777 return self._untagged_response(typ, dat, 'QUOTA')
778
779
Piers Lauder15e5d532001-07-20 10:52:06 +0000780 def sort(self, sort_criteria, charset, *search_criteria):
781 """IMAP4rev1 extension SORT command.
782
783 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
784 """
785 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000786 #if not name in self.capabilities: # Let the server decide!
787 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000788 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000789 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000790 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000791 return self._untagged_response(typ, dat, name)
792
793
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000794 def starttls(self, ssl_context=None):
795 name = 'STARTTLS'
796 if not HAVE_SSL:
797 raise self.error('SSL support missing')
798 if self._tls_established:
799 raise self.abort('TLS session already established')
800 if name not in self.capabilities:
801 raise self.abort('TLS not supported by server')
802 # Generate a default SSL context if none was passed.
803 if ssl_context is None:
Christian Heimes67986f92013-11-23 22:43:47 +0100804 ssl_context = ssl._create_stdlib_context()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000805 typ, dat = self._simple_command(name)
806 if typ == 'OK':
Christian Heimes48aae572013-12-02 20:01:29 +0100807 self.sock = ssl_context.wrap_socket(self.sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -0600808 server_hostname=self.host)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000809 self.file = self.sock.makefile('rb')
810 self._tls_established = True
Antoine Pitroudbe75192010-11-16 17:55:26 +0000811 self._get_capabilities()
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000812 else:
813 raise self.error("Couldn't establish TLS session")
814 return self._untagged_response(typ, dat, name)
815
816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 def status(self, mailbox, names):
818 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 (typ, [data]) = <instance>.status(mailbox, names)
821 """
822 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000823 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000824 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 typ, dat = self._simple_command(name, mailbox, names)
826 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000827
828
Tim Peters07e99cb2001-01-14 23:47:14 +0000829 def store(self, message_set, command, flags):
830 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 (typ, [data]) = <instance>.store(message_set, command, flags)
833 """
834 if (flags[0],flags[-1]) != ('(',')'):
835 flags = '(%s)' % flags # Avoid quoting the flags
836 typ, dat = self._simple_command('STORE', message_set, command, flags)
837 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
839
Tim Peters07e99cb2001-01-14 23:47:14 +0000840 def subscribe(self, mailbox):
841 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 (typ, [data]) = <instance>.subscribe(mailbox)
844 """
845 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000846
847
Martin v. Löwisd8921372003-11-10 06:44:44 +0000848 def thread(self, threading_algorithm, charset, *search_criteria):
849 """IMAPrev1 extension THREAD command.
850
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000851 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000852 """
853 name = 'THREAD'
854 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
855 return self._untagged_response(typ, dat, name)
856
857
Tim Peters07e99cb2001-01-14 23:47:14 +0000858 def uid(self, command, *args):
859 """Execute "command arg ..." with messages identified by UID,
860 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000861
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 Returns response appropriate to 'command'.
865 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000866 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000867 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 raise self.error("Unknown IMAP4 UID command: %s" % command)
869 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000870 raise self.error("command %s illegal in state %s, "
871 "only allowed in states %s" %
872 (command, self.state,
873 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000875 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000876 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000877 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 else:
879 name = 'FETCH'
880 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000881
882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 def unsubscribe(self, mailbox):
884 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 (typ, [data]) = <instance>.unsubscribe(mailbox)
887 """
888 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000889
890
Tim Peters07e99cb2001-01-14 23:47:14 +0000891 def xatom(self, name, *args):
892 """Allow simple extension commands
893 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000894
Piers Lauder15e5d532001-07-20 10:52:06 +0000895 Assumes command is legal in current state.
896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000898
899 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000901 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000902 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000903 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000904 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000905 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000906 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
908
909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000911
912
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000914 if dat is None:
915 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 ur = self.untagged_responses
917 if __debug__:
918 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000919 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000921 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000922 ur[typ].append(dat)
923 else:
924 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 def _check_bye(self):
928 bye = self.untagged_responses.get('BYE')
929 if bye:
R David Murraya6429db2015-05-10 19:17:23 -0400930 raise self.abort(bye[-1].decode(self._encoding, 'replace'))
Guido van Rossum8c062211999-12-13 23:27:45 +0000931
932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 if self.state not in Commands[name]:
936 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000937 raise self.error("command %s illegal in state %s, "
938 "only allowed in states %s" %
939 (name, self.state,
940 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000941
Tim Peters07e99cb2001-01-14 23:47:14 +0000942 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000943 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000945
Raymond Hettinger54f02222002-06-01 14:18:47 +0000946 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 and not self.is_readonly:
948 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000949
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 tag = self._new_tag()
R David Murraya6429db2015-05-10 19:17:23 -0400951 name = bytes(name, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000952 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 for arg in args:
954 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000955 if isinstance(arg, str):
R David Murraya6429db2015-05-10 19:17:23 -0400956 arg = bytes(arg, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +0000957 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 literal = self.literal
960 if literal is not None:
961 self.literal = None
962 if type(literal) is type(self._command):
963 literator = literal
964 else:
965 literator = None
R David Murraya6429db2015-05-10 19:17:23 -0400966 data = data + bytes(' {%s}' % len(literal), self._encoding)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000967
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 if __debug__:
969 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000970 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000971 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000972 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000973
Tim Peters07e99cb2001-01-14 23:47:14 +0000974 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000975 self.send(data + CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200976 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 if literal is None:
980 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 while 1:
983 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 while self._get_response():
986 if self.tagged_commands[tag]: # BAD/NO?
987 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 if literator:
992 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 if __debug__:
995 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000996 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000999 self.send(literal)
1000 self.send(CRLF)
Andrew Svetlov0832af62012-12-18 23:10:48 +02001001 except OSError as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001003
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 if not literator:
1005 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001006
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001008
1009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 def _command_complete(self, name, tag):
Antoine Pitroudac47912010-11-10 00:18:40 +00001011 # BYE is expected after LOGOUT
1012 if name != 'LOGOUT':
1013 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 try:
1015 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +00001016 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001017 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +00001018 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 raise self.error('command: %s => %s' % (name, val))
Antoine Pitroudac47912010-11-10 00:18:40 +00001020 if name != 'LOGOUT':
1021 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 if typ == 'BAD':
1023 raise self.error('%s command error: %s %s' % (name, typ, data))
1024 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
1026
Antoine Pitroudbe75192010-11-16 17:55:26 +00001027 def _get_capabilities(self):
1028 typ, dat = self.capability()
1029 if dat == [None]:
1030 raise self.error('no CAPABILITY response from server')
R David Murraya6429db2015-05-10 19:17:23 -04001031 dat = str(dat[-1], self._encoding)
Antoine Pitroudbe75192010-11-16 17:55:26 +00001032 dat = dat.upper()
1033 self.capabilities = tuple(dat.split())
1034
1035
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 # Read response and store.
1039 #
1040 # Returns None for continuation responses,
1041 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
Tim Peters07e99cb2001-01-14 23:47:14 +00001045 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001046
Tim Peters07e99cb2001-01-14 23:47:14 +00001047 if self._match(self.tagre, resp):
1048 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +00001049 if not tag in self.tagged_commands:
R David Murraya6429db2015-05-10 19:17:23 -04001050 raise self.abort('unexpected tagged response: %r' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001053 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 dat = self.mo.group('data')
1055 self.tagged_commands[tag] = (typ, [dat])
1056 else:
1057 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001058
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001060
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 if not self._match(Untagged_response, resp):
R David Murraya6429db2015-05-10 19:17:23 -04001062 if self._match(self.Untagged_status, resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001064
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 if self.mo is None:
1066 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 if self._match(Continuation, resp):
1069 self.continuation_response = self.mo.group('data')
1070 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001071
R David Murraya6429db2015-05-10 19:17:23 -04001072 raise self.abort("unexpected response: %r" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001075 typ = str(typ, self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001076 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +00001077 if dat is None: dat = b'' # Null untagged response
1078 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001081
R David Murraya6429db2015-05-10 19:17:23 -04001082 while self._match(self.Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001083
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001085
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001086 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +00001087 if __debug__:
1088 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001089 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +00001090 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001093
Tim Peters07e99cb2001-01-14 23:47:14 +00001094 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001095
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001097
Tim Peters07e99cb2001-01-14 23:47:14 +00001098 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001099
Tim Peters07e99cb2001-01-14 23:47:14 +00001100 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001101
Tim Peters07e99cb2001-01-14 23:47:14 +00001102 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001103
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001105 typ = self.mo.group('type')
R David Murraya6429db2015-05-10 19:17:23 -04001106 typ = str(typ, self._encoding)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001107 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001108
Tim Peters07e99cb2001-01-14 23:47:14 +00001109 if __debug__:
1110 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +00001111 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +00001112
Tim Peters07e99cb2001-01-14 23:47:14 +00001113 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001114
1115
Tim Peters07e99cb2001-01-14 23:47:14 +00001116 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001117
Tim Peters07e99cb2001-01-14 23:47:14 +00001118 while 1:
1119 result = self.tagged_commands[tag]
1120 if result is not None:
1121 del self.tagged_commands[tag]
1122 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001123
R David Murray95ff7232014-02-07 13:44:57 -05001124 # If we've seen a BYE at this point, the socket will be
1125 # closed, so report the BYE now.
1126
1127 self._check_bye()
1128
Tim Peters07e99cb2001-01-14 23:47:14 +00001129 # Some have reported "unexpected response" exceptions.
1130 # Note that ignoring them here causes loops.
1131 # Instead, send me details of the unexpected response and
1132 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001133
Tim Peters07e99cb2001-01-14 23:47:14 +00001134 try:
1135 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001136 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001137 if __debug__:
1138 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001139 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001140 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001141
1142
Tim Peters07e99cb2001-01-14 23:47:14 +00001143 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001144
Piers Lauder15e5d532001-07-20 10:52:06 +00001145 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001146 if not line:
1147 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001148
Tim Peters07e99cb2001-01-14 23:47:14 +00001149 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001150 if not line.endswith(b'\r\n'):
R David Murray3bca8ac2013-06-28 14:52:57 -04001151 raise self.abort('socket error: unterminated line: %r' % line)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001152
Tim Peters07e99cb2001-01-14 23:47:14 +00001153 line = line[:-2]
1154 if __debug__:
1155 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001156 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001157 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001158 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001159 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001160
1161
Tim Peters07e99cb2001-01-14 23:47:14 +00001162 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001163
Tim Peters07e99cb2001-01-14 23:47:14 +00001164 # Run compiled regular expression match method on 's'.
1165 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001166
Tim Peters07e99cb2001-01-14 23:47:14 +00001167 self.mo = cre.match(s)
1168 if __debug__:
1169 if self.mo is not None and self.debug >= 5:
Serhiy Storchakaa4a30202017-11-28 22:54:42 +02001170 self._mesg("\tmatched %r => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001171 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001172
1173
Tim Peters07e99cb2001-01-14 23:47:14 +00001174 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001175
R David Murraya6429db2015-05-10 19:17:23 -04001176 tag = self.tagpre + bytes(str(self.tagnum), self._encoding)
Tim Peters07e99cb2001-01-14 23:47:14 +00001177 self.tagnum = self.tagnum + 1
1178 self.tagged_commands[tag] = None
1179 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001180
1181
Tim Peters07e99cb2001-01-14 23:47:14 +00001182 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001183
Antoine Pitroub1436f12010-11-09 22:55:55 +00001184 arg = arg.replace('\\', '\\\\')
1185 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001186
Antoine Pitroub1436f12010-11-09 22:55:55 +00001187 return '"' + arg + '"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001188
1189
Tim Peters07e99cb2001-01-14 23:47:14 +00001190 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001191
Guido van Rossum68468eb2003-02-27 20:14:51 +00001192 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001193
1194
Tim Peters07e99cb2001-01-14 23:47:14 +00001195 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001196 if typ == 'NO':
1197 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001198 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001199 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001200 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001201 if __debug__:
1202 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001203 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001204 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001205
1206
Piers Lauderf2d7d152002-02-22 01:15:17 +00001207 if __debug__:
1208
1209 def _mesg(self, s, secs=None):
1210 if secs is None:
1211 secs = time.time()
1212 tm = time.strftime('%M:%S', time.localtime(secs))
1213 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1214 sys.stderr.flush()
1215
1216 def _dump_ur(self, dict):
1217 # Dump untagged responses (in `dict').
1218 l = dict.items()
1219 if not l: return
1220 t = '\n\t\t'
1221 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1222 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1223
1224 def _log(self, line):
1225 # Keep log of last `_cmd_log_len' interactions for debugging.
1226 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1227 self._cmd_log_idx += 1
1228 if self._cmd_log_idx >= self._cmd_log_len:
1229 self._cmd_log_idx = 0
1230
1231 def print_log(self):
1232 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1233 i, n = self._cmd_log_idx, self._cmd_log_len
1234 while n:
1235 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001236 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001237 except:
1238 pass
1239 i += 1
1240 if i >= self._cmd_log_len:
1241 i = 0
1242 n -= 1
1243
1244
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001245if HAVE_SSL:
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001246
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001247 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001248
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001249 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001250
Antoine Pitrou08728162011-05-06 18:49:52 +02001251 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001252
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001253 host - host's name (default: localhost);
Antoine Pitrou08728162011-05-06 18:49:52 +02001254 port - port number (default: standard IMAP4 SSL port);
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001255 keyfile - PEM formatted file that contains your private key (default: None);
1256 certfile - PEM formatted certificate chain file (default: None);
Antoine Pitrou08728162011-05-06 18:49:52 +02001257 ssl_context - a SSLContext object that contains your certificate chain
1258 and private key (default: None)
1259 Note: if ssl_context is provided, then parameters keyfile or
Andrew Svetlov5b898402012-12-18 21:26:36 +02001260 certfile should not be set otherwise ValueError is raised.
Piers Laudera4f83132002-03-08 01:53:24 +00001261
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001262 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001263 """
Piers Laudera4f83132002-03-08 01:53:24 +00001264
1265
R David Murraya6429db2015-05-10 19:17:23 -04001266 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
1267 certfile=None, ssl_context=None):
Antoine Pitrou08728162011-05-06 18:49:52 +02001268 if ssl_context is not None and keyfile is not None:
1269 raise ValueError("ssl_context and keyfile arguments are mutually "
1270 "exclusive")
1271 if ssl_context is not None and certfile is not None:
1272 raise ValueError("ssl_context and certfile arguments are mutually "
1273 "exclusive")
Christian Heimesd0486372016-09-10 23:23:33 +02001274 if keyfile is not None or certfile is not None:
1275 import warnings
1276 warnings.warn("keyfile and certfile are deprecated, use a"
1277 "custom ssl_context instead", DeprecationWarning, 2)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001278 self.keyfile = keyfile
1279 self.certfile = certfile
Christian Heimes67986f92013-11-23 22:43:47 +01001280 if ssl_context is None:
1281 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1282 keyfile=keyfile)
Antoine Pitrou08728162011-05-06 18:49:52 +02001283 self.ssl_context = ssl_context
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001284 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001285
Christian Heimesfb5faf02008-11-05 19:39:50 +00001286 def _create_socket(self):
1287 sock = IMAP4._create_socket(self)
Christian Heimes48aae572013-12-02 20:01:29 +01001288 return self.ssl_context.wrap_socket(sock,
Benjamin Peterson7243b572014-11-23 17:04:34 -06001289 server_hostname=self.host)
Piers Laudera4f83132002-03-08 01:53:24 +00001290
Christian Heimesfb5faf02008-11-05 19:39:50 +00001291 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001292 """Setup connection to remote server on "host:port".
1293 (default: localhost:standard IMAP4 SSL port).
1294 This connection will be used by the routines:
1295 read, readline, send, shutdown.
1296 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001297 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001298
1299 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001300
1301
Piers Laudere0273de2002-11-22 05:53:04 +00001302class IMAP4_stream(IMAP4):
1303
1304 """IMAP4 client class over a stream
1305
1306 Instantiate with: IMAP4_stream(command)
1307
R David Murraya6429db2015-05-10 19:17:23 -04001308 "command" - a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001309
1310 for more documentation see the docstring of the parent class IMAP4.
1311 """
1312
1313
1314 def __init__(self, command):
1315 self.command = command
1316 IMAP4.__init__(self)
1317
1318
1319 def open(self, host = None, port = None):
1320 """Setup a stream connection.
1321 This connection will be used by the routines:
1322 read, readline, send, shutdown.
1323 """
1324 self.host = None # For compatibility with parent class
1325 self.port = None
1326 self.sock = None
1327 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001328 self.process = subprocess.Popen(self.command,
R David Murrayfcb6d6a2013-03-19 13:52:33 -04001329 bufsize=DEFAULT_BUFFER_SIZE,
Christian Heimesfb5faf02008-11-05 19:39:50 +00001330 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1331 shell=True, close_fds=True)
1332 self.writefile = self.process.stdin
1333 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001334
1335 def read(self, size):
1336 """Read 'size' bytes from remote."""
1337 return self.readfile.read(size)
1338
1339
1340 def readline(self):
1341 """Read line from remote."""
1342 return self.readfile.readline()
1343
1344
1345 def send(self, data):
1346 """Send data to remote."""
1347 self.writefile.write(data)
1348 self.writefile.flush()
1349
1350
1351 def shutdown(self):
1352 """Close I/O established in "open"."""
1353 self.readfile.close()
1354 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001355 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001356
1357
1358
Guido van Rossumeda960a1998-06-18 14:24:28 +00001359class _Authenticator:
1360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 """Private class to provide en/decoding
1362 for base64-based authentication conversation.
1363 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001364
Tim Peters07e99cb2001-01-14 23:47:14 +00001365 def __init__(self, mechinst):
1366 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001367
Tim Peters07e99cb2001-01-14 23:47:14 +00001368 def process(self, data):
1369 ret = self.mech(self.decode(data))
1370 if ret is None:
Robert Collins5ccc18f2015-07-31 08:59:02 +12001371 return b'*' # Abort conversation
Tim Peters07e99cb2001-01-14 23:47:14 +00001372 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001373
Tim Peters07e99cb2001-01-14 23:47:14 +00001374 def encode(self, inp):
1375 #
1376 # Invoke binascii.b2a_base64 iteratively with
1377 # short even length buffers, strip the trailing
1378 # line feed from the result and append. "Even"
1379 # means a number that factors to both 6 and 8,
1380 # so when it gets to the end of the 8-bit input
1381 # there's no partial 6-bit output.
1382 #
R David Murray774a39f2013-02-19 12:17:31 -05001383 oup = b''
1384 if isinstance(inp, str):
R David Murraya6429db2015-05-10 19:17:23 -04001385 inp = inp.encode('utf-8')
Tim Peters07e99cb2001-01-14 23:47:14 +00001386 while inp:
1387 if len(inp) > 48:
1388 t = inp[:48]
1389 inp = inp[48:]
1390 else:
1391 t = inp
R David Murray774a39f2013-02-19 12:17:31 -05001392 inp = b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 e = binascii.b2a_base64(t)
1394 if e:
1395 oup = oup + e[:-1]
1396 return oup
1397
1398 def decode(self, inp):
1399 if not inp:
R David Murray774a39f2013-02-19 12:17:31 -05001400 return b''
Tim Peters07e99cb2001-01-14 23:47:14 +00001401 return binascii.a2b_base64(inp)
1402
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001403Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1404Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001405
1406def Internaldate2tuple(resp):
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001407 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001408
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001409 Return corresponding local time. The return value is a
1410 time.struct_time tuple or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001411 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 mo = InternalDate.match(resp)
1414 if not mo:
1415 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001416
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 mon = Mon2num[mo.group('mon')]
1418 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001419
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001420 day = int(mo.group('day'))
1421 year = int(mo.group('year'))
1422 hour = int(mo.group('hour'))
1423 min = int(mo.group('min'))
1424 sec = int(mo.group('sec'))
1425 zoneh = int(mo.group('zoneh'))
1426 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001427
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 zone = (zoneh*60 + zonem)*60
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +00001431 if zonen == b'-':
Tim Peters07e99cb2001-01-14 23:47:14 +00001432 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001433
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001435 utc = calendar.timegm(tt) - zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001436
Alexander Belopolsky2420d832012-04-29 15:56:49 -04001437 return time.localtime(utc)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001438
1439
1440
1441def Int2AP(num):
1442
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001444
Christian Heimesfb5faf02008-11-05 19:39:50 +00001445 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001446 num = int(abs(num))
1447 while num:
1448 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001449 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001450 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001451
1452
1453
1454def ParseFlags(resp):
1455
Tim Peters07e99cb2001-01-14 23:47:14 +00001456 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001457
Tim Peters07e99cb2001-01-14 23:47:14 +00001458 mo = Flags.match(resp)
1459 if not mo:
1460 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001461
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001462 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001463
1464
1465def Time2Internaldate(date_time):
1466
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001467 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001468
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001469 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Florent Xicluna992d9e02011-11-11 19:35:42 +01001470 date_time argument can be a number (int or float) representing
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001471 seconds since epoch (as returned by time.time()), a 9-tuple
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001472 representing local time, an instance of time.struct_time (as
1473 returned by time.localtime()), an aware datetime instance or a
Alexander Belopolsky41a99bc2011-01-19 19:53:30 +00001474 double-quoted string. In the last case, it is assumed to already
1475 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001476 """
Fred Drakedb519202002-01-05 17:17:09 +00001477 if isinstance(date_time, (int, float)):
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001478 dt = datetime.fromtimestamp(date_time,
1479 timezone.utc).astimezone()
1480 elif isinstance(date_time, tuple):
1481 try:
1482 gmtoff = date_time.tm_gmtoff
1483 except AttributeError:
1484 if time.daylight:
1485 dst = date_time[8]
1486 if dst == -1:
1487 dst = time.localtime(time.mktime(date_time))[8]
1488 gmtoff = -(time.timezone, time.altzone)[dst]
1489 else:
1490 gmtoff = -time.timezone
1491 delta = timedelta(seconds=gmtoff)
1492 dt = datetime(*date_time[:6], tzinfo=timezone(delta))
1493 elif isinstance(date_time, datetime):
1494 if date_time.tzinfo is None:
1495 raise ValueError("date_time must be aware")
1496 dt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001497 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001498 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001499 else:
1500 raise ValueError("date_time not of a known type")
Alexander Belopolsky8141cc72012-06-22 21:03:39 -04001501 fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month])
1502 return dt.strftime(fmt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001503
1504
1505
Guido van Rossum8c062211999-12-13 23:27:45 +00001506if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001507
Piers Laudere0273de2002-11-22 05:53:04 +00001508 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1509 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1510 # to test the IMAP4_stream class
1511
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001512 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001513
Tim Peters07e99cb2001-01-14 23:47:14 +00001514 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001515 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001516 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001517 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001518
Piers Laudere0273de2002-11-22 05:53:04 +00001519 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001520 for opt,val in optlist:
1521 if opt == '-d':
1522 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001523 elif opt == '-s':
1524 stream_command = val
1525 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001526
Tim Peters07e99cb2001-01-14 23:47:14 +00001527 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001528
Tim Peters07e99cb2001-01-14 23:47:14 +00001529 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001530
Tim Peters07e99cb2001-01-14 23:47:14 +00001531 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001532 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001533
Piers Lauder47404ff2003-04-29 23:40:59 +00001534 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 +00001535 test_seq1 = (
1536 ('login', (USER, PASSWD)),
1537 ('create', ('/tmp/xxx 1',)),
1538 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1539 ('CREATE', ('/tmp/yyz 2',)),
1540 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1541 ('list', ('/tmp', 'yy*')),
1542 ('select', ('/tmp/yyz 2',)),
1543 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001544 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
R David Murray44b548d2016-09-08 13:59:53 -04001545 ('store', ('1', 'FLAGS', r'(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001546 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001547 ('expunge', ()),
1548 ('recent', ()),
1549 ('close', ()),
1550 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001551
Tim Peters07e99cb2001-01-14 23:47:14 +00001552 test_seq2 = (
1553 ('select', ()),
1554 ('response',('UIDVALIDITY',)),
1555 ('uid', ('SEARCH', 'ALL')),
1556 ('response', ('EXISTS',)),
1557 ('append', (None, None, None, test_mesg)),
1558 ('recent', ()),
1559 ('logout', ()),
1560 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001561
Tim Peters07e99cb2001-01-14 23:47:14 +00001562 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001563 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001564 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001565 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001566 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001567 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001568
Tim Peters07e99cb2001-01-14 23:47:14 +00001569 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001570 if stream_command:
1571 M = IMAP4_stream(stream_command)
1572 else:
1573 M = IMAP4(host)
1574 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001575 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001576 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001577 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001578
Tim Peters07e99cb2001-01-14 23:47:14 +00001579 for cmd,args in test_seq1:
1580 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001581
Tim Peters07e99cb2001-01-14 23:47:14 +00001582 for ml in run('list', ('/tmp/', 'yy%')):
1583 mo = re.match(r'.*"([^"]+)"$', ml)
1584 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001585 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001586 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001587
Tim Peters07e99cb2001-01-14 23:47:14 +00001588 for cmd,args in test_seq2:
1589 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001590
Tim Peters07e99cb2001-01-14 23:47:14 +00001591 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1592 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001593
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001594 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001595 if not uid: continue
1596 run('uid', ('FETCH', '%s' % uid[-1],
1597 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001598
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001599 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001600
Tim Peters07e99cb2001-01-14 23:47:14 +00001601 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001602 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001603
Tim Peters07e99cb2001-01-14 23:47:14 +00001604 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001605 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001606If you would like to see debugging output,
1607try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001608''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001609
Tim Peters07e99cb2001-01-14 23:47:14 +00001610 raise