blob: 2e5511e02416834e67dd4e39789cde051d4acd34 [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
Antoine Pitrouae933892010-11-10 09:02:33 +000025import binascii, errno, random, re, socket, subprocess, sys, time
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000026
Thomas Woutersa6900e82007-08-30 21:54:39 +000027__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000028 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000029
Tim Peters07e99cb2001-01-14 23:47:14 +000030# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000031
32CRLF = '\r\n'
33Debug = 0
34IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000035IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000036AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000037
R David Murray020d7c32014-01-03 13:59:22 -050038# Maximal line length when calling readline(). This is to prevent
39# reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1)
R David Murray836c8242015-03-22 16:17:11 -040040# don't specify a line length. RFC 2683 suggests limiting client
41# command lines to 1000 octets and that servers should be prepared
42# to accept command lines up to 8000 octets, so we used to use 10K here.
43# In the modern world (eg: gmail) the response to, for example, a
44# search command can be quite large, so we now use 1M.
45_MAXLINE = 1000000
R David Murray020d7c32014-01-03 13:59:22 -050046
47
Tim Peters07e99cb2001-01-14 23:47:14 +000048# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000049
50Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000051 # name valid states
52 'APPEND': ('AUTH', 'SELECTED'),
53 'AUTHENTICATE': ('NONAUTH',),
54 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
55 'CHECK': ('SELECTED',),
56 'CLOSE': ('SELECTED',),
57 'COPY': ('SELECTED',),
58 'CREATE': ('AUTH', 'SELECTED'),
59 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000060 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000061 'EXAMINE': ('AUTH', 'SELECTED'),
62 'EXPUNGE': ('SELECTED',),
63 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000064 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000065 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000066 'GETQUOTA': ('AUTH', 'SELECTED'),
67 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000068 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000069 'LIST': ('AUTH', 'SELECTED'),
70 'LOGIN': ('NONAUTH',),
71 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
72 'LSUB': ('AUTH', 'SELECTED'),
Miss Islington (bot)78684262018-07-23 07:07:03 -070073 'MOVE': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000074 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000075 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000076 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000077 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000078 'RENAME': ('AUTH', 'SELECTED'),
79 'SEARCH': ('SELECTED',),
80 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000081 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000082 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000083 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000084 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000085 'STATUS': ('AUTH', 'SELECTED'),
86 'STORE': ('SELECTED',),
87 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000088 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000089 'UID': ('SELECTED',),
90 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
91 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000092
Tim Peters07e99cb2001-01-14 23:47:14 +000093# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000094
Guido van Rossumeda960a1998-06-18 14:24:28 +000095Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000096Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
97InternalDate = re.compile(r'.*INTERNALDATE "'
Piers Lauder8659d902005-03-02 09:13:45 +000098 r'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
Tim Peters07e99cb2001-01-14 23:47:14 +000099 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
100 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
101 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +0000102Literal = re.compile(r'.*{(?P<size>\d+)}$')
Piers Lauder533366b2003-04-29 23:58:08 +0000103MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000105Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000106Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
107
108
109
110class IMAP4:
111
Tim Peters07e99cb2001-01-14 23:47:14 +0000112 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000113
Tim Peters07e99cb2001-01-14 23:47:14 +0000114 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000115
Tim Peters07e99cb2001-01-14 23:47:14 +0000116 host - host's name (default: localhost);
117 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000118
Tim Peters07e99cb2001-01-14 23:47:14 +0000119 All IMAP4rev1 commands are supported by methods of the same
120 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000121
Tim Peters07e99cb2001-01-14 23:47:14 +0000122 All arguments to commands are converted to strings, except for
123 AUTHENTICATE, and the last argument to APPEND which is passed as
124 an IMAP4 literal. If necessary (the string contains any
125 non-printing characters or white-space and isn't enclosed with
126 either parentheses or double quotes) each string is quoted.
127 However, the 'password' argument to the LOGIN command is always
128 quoted. If you want to avoid having an argument string quoted
129 (eg: the 'flags' argument to STORE) then enclose the string in
130 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000131
Tim Peters07e99cb2001-01-14 23:47:14 +0000132 Each command returns a tuple: (type, [data, ...]) where 'type'
133 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000134 tagged response, or untagged results from command. Each 'data'
135 is either a string, or a tuple. If a tuple, then the first part
136 is the header of the response, and the second part contains
137 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000138
Tim Peters07e99cb2001-01-14 23:47:14 +0000139 Errors raise the exception class <instance>.error("<reason>").
140 IMAP4 server errors raise <instance>.abort("<reason>"),
141 which is a sub-class of 'error'. Mailbox status changes
142 from READ-WRITE to READ-ONLY raise the exception class
143 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000144
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 "error" exceptions imply a program error.
146 "abort" exceptions imply the connection should be reset, and
147 the command re-tried.
148 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000149
Piers Lauderd80ef022005-06-01 23:50:52 +0000150 Note: to use this module, you must read the RFCs pertaining to the
151 IMAP4 protocol, as the semantics of the arguments to each IMAP4
152 command are left to the invoker, not to mention the results. Also,
153 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000155
Tim Peters07e99cb2001-01-14 23:47:14 +0000156 class error(Exception): pass # Logical errors - debug required
157 class abort(error): pass # Service errors - close and retry
158 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 self.debug = Debug
164 self.state = 'LOGOUT'
165 self.literal = None # A literal argument to a command
166 self.tagged_commands = {} # Tagged commands awaiting response
167 self.untagged_responses = {} # {typ: [data, ...], ...}
168 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000169 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000173
Tim Peters07e99cb2001-01-14 23:47:14 +0000174 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000175
Tim Peters07e99cb2001-01-14 23:47:14 +0000176 # Create unique tag for this session,
177 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000178
Piers Lauder2dfc1682005-07-05 04:20:07 +0000179 self.tagpre = Int2AP(random.randint(4096, 65535))
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 self.tagre = re.compile(r'(?P<tag>'
181 + self.tagpre
182 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
Tim Peters07e99cb2001-01-14 23:47:14 +0000184 # Get server welcome message,
185 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000186
Tim Peters07e99cb2001-01-14 23:47:14 +0000187 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000188 self._cmd_log_len = 10
189 self._cmd_log_idx = 0
190 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000191 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000192 self._mesg('imaplib version %s' % __version__)
193 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000194
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000196 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000197 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000198 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000199 self.state = 'NONAUTH'
200 else:
201 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000202
Piers Lauderd80ef022005-06-01 23:50:52 +0000203 typ, dat = self.capability()
204 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000205 raise self.error('no CAPABILITY response from server')
Piers Lauderd80ef022005-06-01 23:50:52 +0000206 self.capabilities = tuple(dat[-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000207
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 if __debug__:
209 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000210 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000211
Tim Peters07e99cb2001-01-14 23:47:14 +0000212 for version in AllowedVersions:
213 if not version in self.capabilities:
214 continue
215 self.PROTOCOL_VERSION = version
216 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000217
Tim Peters07e99cb2001-01-14 23:47:14 +0000218 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000219
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000220
Tim Peters07e99cb2001-01-14 23:47:14 +0000221 def __getattr__(self, attr):
222 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000223 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000224 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000225 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000226
227
228
Piers Lauder15e5d532001-07-20 10:52:06 +0000229 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000230
231
Piers Lauderf97b2d72002-06-05 22:31:57 +0000232 def open(self, host = '', port = IMAP4_PORT):
233 """Setup connection to remote server on "host:port"
234 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000235 This connection will be used by the routines:
236 read, readline, send, shutdown.
237 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000238 self.host = host
239 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +0000240 self.sock = socket.create_connection((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000241 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000242
243
Piers Lauder15e5d532001-07-20 10:52:06 +0000244 def read(self, size):
245 """Read 'size' bytes from remote."""
246 return self.file.read(size)
247
248
249 def readline(self):
250 """Read line from remote."""
R David Murray020d7c32014-01-03 13:59:22 -0500251 line = self.file.readline(_MAXLINE + 1)
252 if len(line) > _MAXLINE:
253 raise self.error("got more than %d bytes" % _MAXLINE)
254 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000255
256
257 def send(self, data):
258 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000259 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000260
Piers Lauderf2d7d152002-02-22 01:15:17 +0000261
Piers Lauder15e5d532001-07-20 10:52:06 +0000262 def shutdown(self):
263 """Close I/O established in "open"."""
264 self.file.close()
Antoine Pitrouae933892010-11-10 09:02:33 +0000265 try:
266 self.sock.shutdown(socket.SHUT_RDWR)
267 except socket.error as e:
Victor Stinner800e4b72017-05-16 17:38:30 -0700268 # The server might already have closed the connection.
269 # On Windows, this may result in WSAEINVAL (error 10022):
270 # An invalid operation was attempted.
271 if e.errno not in (errno.ENOTCONN, 10022):
Antoine Pitrouae933892010-11-10 09:02:33 +0000272 raise
273 finally:
274 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000275
276
277 def socket(self):
278 """Return socket instance used to connect to IMAP4 server.
279
280 socket = <instance>.socket()
281 """
282 return self.sock
283
284
285
286 # Utility methods
287
288
Tim Peters07e99cb2001-01-14 23:47:14 +0000289 def recent(self):
290 """Return most recent 'RECENT' responses if any exist,
291 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000292
Tim Peters07e99cb2001-01-14 23:47:14 +0000293 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000294
Tim Peters07e99cb2001-01-14 23:47:14 +0000295 'data' is None if no new messages,
296 else list of RECENT responses, most recent last.
297 """
298 name = 'RECENT'
299 typ, dat = self._untagged_response('OK', [None], name)
300 if dat[-1]:
301 return typ, dat
302 typ, dat = self.noop() # Prod server for response
303 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000304
305
Tim Peters07e99cb2001-01-14 23:47:14 +0000306 def response(self, code):
307 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000310
Tim Peters07e99cb2001-01-14 23:47:14 +0000311 (code, [data]) = <instance>.response(code)
312 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000313 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000314
315
Guido van Rossum26367a01998-09-28 15:34:46 +0000316
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000318
319
Tim Peters07e99cb2001-01-14 23:47:14 +0000320 def append(self, mailbox, flags, date_time, message):
321 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000322
Tim Peters07e99cb2001-01-14 23:47:14 +0000323 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000324
Tim Peters07e99cb2001-01-14 23:47:14 +0000325 All args except `message' can be None.
326 """
327 name = 'APPEND'
328 if not mailbox:
329 mailbox = 'INBOX'
330 if flags:
331 if (flags[0],flags[-1]) != ('(',')'):
332 flags = '(%s)' % flags
333 else:
334 flags = None
335 if date_time:
336 date_time = Time2Internaldate(date_time)
337 else:
338 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000339 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000340 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000341
342
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 def authenticate(self, mechanism, authobject):
344 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000345
Tim Peters07e99cb2001-01-14 23:47:14 +0000346 'mechanism' specifies which authentication mechanism is to
347 be used - it must appear in <instance>.capabilities in the
348 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000349
Tim Peters07e99cb2001-01-14 23:47:14 +0000350 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000351
Tim Peters07e99cb2001-01-14 23:47:14 +0000352 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000353
Tim Peters07e99cb2001-01-14 23:47:14 +0000354 It will be called to process server continuation responses.
355 It should return data that will be encoded and sent to server.
356 It should return None if the client abort response '*' should
357 be sent instead.
358 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000359 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000360 # XXX: shouldn't this code be removed, not commented out?
361 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000362 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000363 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000364 self.literal = _Authenticator(authobject).process
365 typ, dat = self._simple_command('AUTHENTICATE', mech)
366 if typ != 'OK':
367 raise self.error(dat[-1])
368 self.state = 'AUTH'
369 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000370
371
Piers Lauderd80ef022005-06-01 23:50:52 +0000372 def capability(self):
373 """(typ, [data]) = <instance>.capability()
374 Fetch capabilities list from server."""
375
376 name = 'CAPABILITY'
377 typ, dat = self._simple_command(name)
378 return self._untagged_response(typ, dat, name)
379
380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 def check(self):
382 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 (typ, [data]) = <instance>.check()
385 """
386 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000387
388
Tim Peters07e99cb2001-01-14 23:47:14 +0000389 def close(self):
390 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000391
Tim Peters07e99cb2001-01-14 23:47:14 +0000392 Deleted messages are removed from writable mailbox.
393 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 (typ, [data]) = <instance>.close()
396 """
397 try:
398 typ, dat = self._simple_command('CLOSE')
399 finally:
400 self.state = 'AUTH'
401 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000402
403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 def copy(self, message_set, new_mailbox):
405 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000406
Tim Peters07e99cb2001-01-14 23:47:14 +0000407 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
408 """
409 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000410
411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 def create(self, mailbox):
413 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000414
Tim Peters07e99cb2001-01-14 23:47:14 +0000415 (typ, [data]) = <instance>.create(mailbox)
416 """
417 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000418
419
Tim Peters07e99cb2001-01-14 23:47:14 +0000420 def delete(self, mailbox):
421 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 (typ, [data]) = <instance>.delete(mailbox)
424 """
425 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000426
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000427 def deleteacl(self, mailbox, who):
428 """Delete the ACLs (remove any rights) set for who on mailbox.
429
430 (typ, [data]) = <instance>.deleteacl(mailbox, who)
431 """
432 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000433
Tim Peters07e99cb2001-01-14 23:47:14 +0000434 def expunge(self):
435 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000436
Tim Peters07e99cb2001-01-14 23:47:14 +0000437 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000438
Tim Peters07e99cb2001-01-14 23:47:14 +0000439 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000440
Tim Peters07e99cb2001-01-14 23:47:14 +0000441 'data' is list of 'EXPUNGE'd message numbers in order received.
442 """
443 name = 'EXPUNGE'
444 typ, dat = self._simple_command(name)
445 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000446
447
Tim Peters07e99cb2001-01-14 23:47:14 +0000448 def fetch(self, message_set, message_parts):
449 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000450
Tim Peters07e99cb2001-01-14 23:47:14 +0000451 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000452
Tim Peters07e99cb2001-01-14 23:47:14 +0000453 'message_parts' should be a string of selected parts
454 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000455
Tim Peters07e99cb2001-01-14 23:47:14 +0000456 'data' are tuples of message part envelope and data.
457 """
458 name = 'FETCH'
459 typ, dat = self._simple_command(name, message_set, message_parts)
460 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000461
462
Piers Lauder15e5d532001-07-20 10:52:06 +0000463 def getacl(self, mailbox):
464 """Get the ACLs for a mailbox.
465
466 (typ, [data]) = <instance>.getacl(mailbox)
467 """
468 typ, dat = self._simple_command('GETACL', mailbox)
469 return self._untagged_response(typ, dat, 'ACL')
470
471
Piers Lauderd80ef022005-06-01 23:50:52 +0000472 def getannotation(self, mailbox, entry, attribute):
473 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
474 Retrieve ANNOTATIONs."""
475
476 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
477 return self._untagged_response(typ, dat, 'ANNOTATION')
478
479
Piers Lauder3fca2912002-06-17 07:07:20 +0000480 def getquota(self, root):
481 """Get the quota root's resource usage and limits.
482
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000483 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000484
485 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000486 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000487 typ, dat = self._simple_command('GETQUOTA', root)
488 return self._untagged_response(typ, dat, 'QUOTA')
489
490
491 def getquotaroot(self, mailbox):
492 """Get the list of quota roots for the named mailbox.
493
494 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000495 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000496 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000497 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
498 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000499 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000500
501
Tim Peters07e99cb2001-01-14 23:47:14 +0000502 def list(self, directory='""', pattern='*'):
503 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000504
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000506
Tim Peters07e99cb2001-01-14 23:47:14 +0000507 'data' is list of LIST responses.
508 """
509 name = 'LIST'
510 typ, dat = self._simple_command(name, directory, pattern)
511 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000512
513
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 def login(self, user, password):
515 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000516
Tim Peters07e99cb2001-01-14 23:47:14 +0000517 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000518
Tim Peters07e99cb2001-01-14 23:47:14 +0000519 NB: 'password' will be quoted.
520 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
522 if typ != 'OK':
523 raise self.error(dat[-1])
524 self.state = 'AUTH'
525 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000526
527
Piers Laudere0273de2002-11-22 05:53:04 +0000528 def login_cram_md5(self, user, password):
529 """ Force use of CRAM-MD5 authentication.
530
531 (typ, [data]) = <instance>.login_cram_md5(user, password)
532 """
533 self.user, self.password = user, password
534 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
535
536
537 def _CRAM_MD5_AUTH(self, challenge):
538 """ Authobject to use with CRAM-MD5 authentication. """
539 import hmac
540 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
541
542
Tim Peters07e99cb2001-01-14 23:47:14 +0000543 def logout(self):
544 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000545
Tim Peters07e99cb2001-01-14 23:47:14 +0000546 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000547
Tim Peters07e99cb2001-01-14 23:47:14 +0000548 Returns server 'BYE' response.
549 """
550 self.state = 'LOGOUT'
551 try: typ, dat = self._simple_command('LOGOUT')
552 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000553 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000554 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000555 return 'BYE', self.untagged_responses['BYE']
556 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000557
558
Tim Peters07e99cb2001-01-14 23:47:14 +0000559 def lsub(self, directory='""', pattern='*'):
560 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000561
Tim Peters07e99cb2001-01-14 23:47:14 +0000562 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000563
Tim Peters07e99cb2001-01-14 23:47:14 +0000564 'data' are tuples of message part envelope and data.
565 """
566 name = 'LSUB'
567 typ, dat = self._simple_command(name, directory, pattern)
568 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000569
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000570 def myrights(self, mailbox):
571 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
572
573 (typ, [data]) = <instance>.myrights(mailbox)
574 """
575 typ,dat = self._simple_command('MYRIGHTS', mailbox)
576 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000577
Piers Lauder15e5d532001-07-20 10:52:06 +0000578 def namespace(self):
579 """ Returns IMAP namespaces ala rfc2342
580
581 (typ, [data, ...]) = <instance>.namespace()
582 """
583 name = 'NAMESPACE'
584 typ, dat = self._simple_command(name)
585 return self._untagged_response(typ, dat, name)
586
587
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 def noop(self):
589 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000590
Piers Laudere0273de2002-11-22 05:53:04 +0000591 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 """
593 if __debug__:
594 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000595 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000596 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000597
598
Tim Peters07e99cb2001-01-14 23:47:14 +0000599 def partial(self, message_num, message_part, start, length):
600 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000601
Tim Peters07e99cb2001-01-14 23:47:14 +0000602 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000603
Tim Peters07e99cb2001-01-14 23:47:14 +0000604 'data' is tuple of message part envelope and data.
605 """
606 name = 'PARTIAL'
607 typ, dat = self._simple_command(name, message_num, message_part, start, length)
608 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000609
610
Piers Laudere0273de2002-11-22 05:53:04 +0000611 def proxyauth(self, user):
612 """Assume authentication as "user".
613
614 Allows an authorised administrator to proxy into any user's
615 mailbox.
616
617 (typ, [data]) = <instance>.proxyauth(user)
618 """
619
620 name = 'PROXYAUTH'
621 return self._simple_command('PROXYAUTH', user)
622
623
Tim Peters07e99cb2001-01-14 23:47:14 +0000624 def rename(self, oldmailbox, newmailbox):
625 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000626
Piers Laudere0273de2002-11-22 05:53:04 +0000627 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 """
629 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000630
631
Tim Peters07e99cb2001-01-14 23:47:14 +0000632 def search(self, charset, *criteria):
633 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000634
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000635 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 'data' is space separated list of matching message numbers.
638 """
639 name = 'SEARCH'
640 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000641 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000642 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000643 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000644 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000645
646
Piers Lauder14f39402005-08-31 10:46:29 +0000647 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000648 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000649
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000651
Piers Lauder14f39402005-08-31 10:46:29 +0000652 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000653
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000655
656 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
657 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000658 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 self.untagged_responses = {} # Flush old responses.
660 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000661 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000662 name = 'EXAMINE'
663 else:
664 name = 'SELECT'
665 typ, dat = self._simple_command(name, mailbox)
666 if typ != 'OK':
667 self.state = 'AUTH' # Might have been 'SELECTED'
668 return typ, dat
669 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000670 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 and not readonly:
672 if __debug__:
673 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000674 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 raise self.readonly('%s is not writable' % mailbox)
676 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000677
678
Piers Lauder15e5d532001-07-20 10:52:06 +0000679 def setacl(self, mailbox, who, what):
680 """Set a mailbox acl.
681
Piers Lauderf167dc32004-03-25 00:12:21 +0000682 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000683 """
684 return self._simple_command('SETACL', mailbox, who, what)
685
686
Piers Lauderd80ef022005-06-01 23:50:52 +0000687 def setannotation(self, *args):
688 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
689 Set ANNOTATIONs."""
690
691 typ, dat = self._simple_command('SETANNOTATION', *args)
692 return self._untagged_response(typ, dat, 'ANNOTATION')
693
694
Piers Lauder3fca2912002-06-17 07:07:20 +0000695 def setquota(self, root, limits):
696 """Set the quota root's resource limits.
697
698 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000699 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000700 typ, dat = self._simple_command('SETQUOTA', root, limits)
701 return self._untagged_response(typ, dat, 'QUOTA')
702
703
Piers Lauder15e5d532001-07-20 10:52:06 +0000704 def sort(self, sort_criteria, charset, *search_criteria):
705 """IMAP4rev1 extension SORT command.
706
707 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
708 """
709 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000710 #if not name in self.capabilities: # Let the server decide!
711 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000712 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000713 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000714 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000715 return self._untagged_response(typ, dat, name)
716
717
Tim Peters07e99cb2001-01-14 23:47:14 +0000718 def status(self, mailbox, names):
719 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000720
Tim Peters07e99cb2001-01-14 23:47:14 +0000721 (typ, [data]) = <instance>.status(mailbox, names)
722 """
723 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000724 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000725 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 typ, dat = self._simple_command(name, mailbox, names)
727 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000728
729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 def store(self, message_set, command, flags):
731 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000732
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 (typ, [data]) = <instance>.store(message_set, command, flags)
734 """
735 if (flags[0],flags[-1]) != ('(',')'):
736 flags = '(%s)' % flags # Avoid quoting the flags
737 typ, dat = self._simple_command('STORE', message_set, command, flags)
738 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000739
740
Tim Peters07e99cb2001-01-14 23:47:14 +0000741 def subscribe(self, mailbox):
742 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000743
Tim Peters07e99cb2001-01-14 23:47:14 +0000744 (typ, [data]) = <instance>.subscribe(mailbox)
745 """
746 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000747
748
Martin v. Löwisd8921372003-11-10 06:44:44 +0000749 def thread(self, threading_algorithm, charset, *search_criteria):
750 """IMAPrev1 extension THREAD command.
751
Mark Dickinsondb69f012009-12-24 16:06:58 +0000752 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000753 """
754 name = 'THREAD'
755 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
756 return self._untagged_response(typ, dat, name)
757
758
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 def uid(self, command, *args):
760 """Execute "command arg ..." with messages identified by UID,
761 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000762
Tim Peters07e99cb2001-01-14 23:47:14 +0000763 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000764
Tim Peters07e99cb2001-01-14 23:47:14 +0000765 Returns response appropriate to 'command'.
766 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000767 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000768 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000769 raise self.error("Unknown IMAP4 UID command: %s" % command)
770 if self.state not in Commands[command]:
Georg Brandl6c104f62007-03-13 18:24:40 +0000771 raise self.error("command %s illegal in state %s, "
772 "only allowed in states %s" %
773 (command, self.state,
774 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000776 typ, dat = self._simple_command(name, command, *args)
Georg Brandl004c74d2010-08-01 19:06:51 +0000777 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000778 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000779 else:
780 name = 'FETCH'
781 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000782
783
Tim Peters07e99cb2001-01-14 23:47:14 +0000784 def unsubscribe(self, mailbox):
785 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000786
Tim Peters07e99cb2001-01-14 23:47:14 +0000787 (typ, [data]) = <instance>.unsubscribe(mailbox)
788 """
789 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000790
791
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 def xatom(self, name, *args):
793 """Allow simple extension commands
794 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000795
Piers Lauder15e5d532001-07-20 10:52:06 +0000796 Assumes command is legal in current state.
797
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000799
800 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000802 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000803 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000804 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000805 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000806 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000807 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000808
809
810
Tim Peters07e99cb2001-01-14 23:47:14 +0000811 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000812
813
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 if dat is None: dat = ''
817 ur = self.untagged_responses
818 if __debug__:
819 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000820 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000822 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 ur[typ].append(dat)
824 else:
825 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 def _check_bye(self):
829 bye = self.untagged_responses.get('BYE')
830 if bye:
831 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000832
833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 if self.state not in Commands[name]:
837 self.literal = None
Georg Brandl6c104f62007-03-13 18:24:40 +0000838 raise self.error("command %s illegal in state %s, "
839 "only allowed in states %s" %
840 (name, self.state,
841 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000844 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000845 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000846
Raymond Hettinger54f02222002-06-01 14:18:47 +0000847 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000848 and not self.is_readonly:
849 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000850
Tim Peters07e99cb2001-01-14 23:47:14 +0000851 tag = self._new_tag()
852 data = '%s %s' % (tag, name)
853 for arg in args:
854 if arg is None: continue
855 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000856
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 literal = self.literal
858 if literal is not None:
859 self.literal = None
860 if type(literal) is type(self._command):
861 literator = literal
862 else:
863 literator = None
864 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000865
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 if __debug__:
867 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000868 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000870 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000873 self.send('%s%s' % (data, CRLF))
874 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000875 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 if literal is None:
878 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 while 1:
881 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 while self._get_response():
884 if self.tagged_commands[tag]: # BAD/NO?
885 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 if literator:
890 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000891
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 if __debug__:
893 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000894 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000895
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000897 self.send(literal)
898 self.send(CRLF)
899 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000901
Tim Peters07e99cb2001-01-14 23:47:14 +0000902 if not literator:
903 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000906
907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 def _command_complete(self, name, tag):
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000909 # BYE is expected after LOGOUT
910 if name != 'LOGOUT':
911 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 try:
913 typ, data = self._get_tagged_response(tag)
914 except self.abort, val:
915 raise self.abort('command: %s => %s' % (name, val))
916 except self.error, val:
917 raise self.error('command: %s => %s' % (name, val))
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000918 if name != 'LOGOUT':
919 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 if typ == 'BAD':
921 raise self.error('%s command error: %s %s' % (name, typ, data))
922 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000923
924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 # Read response and store.
928 #
929 # Returns None for continuation responses,
930 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000935
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 if self._match(self.tagre, resp):
937 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000938 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000939 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 typ = self.mo.group('type')
942 dat = self.mo.group('data')
943 self.tagged_commands[tag] = (typ, [dat])
944 else:
945 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 if not self._match(Untagged_response, resp):
950 if self._match(Untagged_status, resp):
951 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 if self.mo is None:
954 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 if self._match(Continuation, resp):
957 self.continuation_response = self.mo.group('data')
958 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000959
Tim Peters07e99cb2001-01-14 23:47:14 +0000960 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000961
Tim Peters07e99cb2001-01-14 23:47:14 +0000962 typ = self.mo.group('type')
963 dat = self.mo.group('data')
964 if dat is None: dat = '' # Null untagged response
965 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000970
Tim Peters07e99cb2001-01-14 23:47:14 +0000971 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000973 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000974 if __debug__:
975 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000976 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000977 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
992 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 if __debug__:
995 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000996 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
1000
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 while 1:
1004 result = self.tagged_commands[tag]
1005 if result is not None:
1006 del self.tagged_commands[tag]
1007 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001008
R David Murrayca00c6e2014-02-07 13:51:22 -05001009 # If we've seen a BYE at this point, the socket will be
1010 # closed, so report the BYE now.
1011
1012 self._check_bye()
1013
Tim Peters07e99cb2001-01-14 23:47:14 +00001014 # Some have reported "unexpected response" exceptions.
1015 # Note that ignoring them here causes loops.
1016 # Instead, send me details of the unexpected response and
1017 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001018
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 try:
1020 self._get_response()
1021 except self.abort, val:
1022 if __debug__:
1023 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001024 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001026
1027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Piers Lauder15e5d532001-07-20 10:52:06 +00001030 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 if not line:
1032 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 # Protocol mandates all lines terminated by CRLF
R. David Murray93321f32009-12-09 15:15:31 +00001035 if not line.endswith('\r\n'):
1036 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 line = line[:-2]
1039 if __debug__:
1040 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001041 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001043 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001045
1046
Tim Peters07e99cb2001-01-14 23:47:14 +00001047 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001048
Tim Peters07e99cb2001-01-14 23:47:14 +00001049 # Run compiled regular expression match method on 's'.
1050 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 self.mo = cre.match(s)
1053 if __debug__:
1054 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001055 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001056 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001057
1058
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001060
Tim Peters07e99cb2001-01-14 23:47:14 +00001061 tag = '%s%s' % (self.tagpre, self.tagnum)
1062 self.tagnum = self.tagnum + 1
1063 self.tagged_commands[tag] = None
1064 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001065
1066
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001068
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 # Must quote command args if non-alphanumeric chars present,
1070 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001071
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 if type(arg) is not type(''):
1073 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001074 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001076 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001077 return arg
1078 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001079
1080
Tim Peters07e99cb2001-01-14 23:47:14 +00001081 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001082
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001083 arg = arg.replace('\\', '\\\\')
1084 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001085
Tim Peters07e99cb2001-01-14 23:47:14 +00001086 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001087
1088
Tim Peters07e99cb2001-01-14 23:47:14 +00001089 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001090
Guido van Rossum68468eb2003-02-27 20:14:51 +00001091 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001092
1093
Tim Peters07e99cb2001-01-14 23:47:14 +00001094 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001095
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 if typ == 'NO':
1097 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001098 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001099 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001100 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 if __debug__:
1102 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001103 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001104 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001105
1106
Piers Lauderf2d7d152002-02-22 01:15:17 +00001107 if __debug__:
1108
1109 def _mesg(self, s, secs=None):
1110 if secs is None:
1111 secs = time.time()
1112 tm = time.strftime('%M:%S', time.localtime(secs))
1113 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1114 sys.stderr.flush()
1115
1116 def _dump_ur(self, dict):
1117 # Dump untagged responses (in `dict').
1118 l = dict.items()
1119 if not l: return
1120 t = '\n\t\t'
1121 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1122 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1123
1124 def _log(self, line):
1125 # Keep log of last `_cmd_log_len' interactions for debugging.
1126 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1127 self._cmd_log_idx += 1
1128 if self._cmd_log_idx >= self._cmd_log_len:
1129 self._cmd_log_idx = 0
1130
1131 def print_log(self):
1132 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1133 i, n = self._cmd_log_idx, self._cmd_log_len
1134 while n:
1135 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001136 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001137 except:
1138 pass
1139 i += 1
1140 if i >= self._cmd_log_len:
1141 i = 0
1142 n -= 1
1143
1144
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001145
Bill Janssen426ea0a2007-08-29 22:35:05 +00001146try:
1147 import ssl
1148except ImportError:
1149 pass
1150else:
1151 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001152
Bill Janssen426ea0a2007-08-29 22:35:05 +00001153 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001154
Bill Janssen426ea0a2007-08-29 22:35:05 +00001155 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001156
Bill Janssen426ea0a2007-08-29 22:35:05 +00001157 host - host's name (default: localhost);
1158 port - port number (default: standard IMAP4 SSL port).
1159 keyfile - PEM formatted file that contains your private key (default: None);
1160 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001161
Bill Janssen426ea0a2007-08-29 22:35:05 +00001162 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001163 """
Piers Laudera4f83132002-03-08 01:53:24 +00001164
1165
Bill Janssen426ea0a2007-08-29 22:35:05 +00001166 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1167 self.keyfile = keyfile
1168 self.certfile = certfile
1169 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001170
1171
Bill Janssen426ea0a2007-08-29 22:35:05 +00001172 def open(self, host = '', port = IMAP4_SSL_PORT):
1173 """Setup connection to remote server on "host:port".
1174 (default: localhost:standard IMAP4 SSL port).
1175 This connection will be used by the routines:
1176 read, readline, send, shutdown.
1177 """
1178 self.host = host
1179 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +00001180 self.sock = socket.create_connection((host, port))
Bill Janssen98d19da2007-09-10 21:51:02 +00001181 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001182 self.file = self.sslobj.makefile('rb')
Piers Laudera4f83132002-03-08 01:53:24 +00001183
1184
Bill Janssen426ea0a2007-08-29 22:35:05 +00001185 def read(self, size):
1186 """Read 'size' bytes from remote."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001187 return self.file.read(size)
Piers Laudera4f83132002-03-08 01:53:24 +00001188
1189
Bill Janssen426ea0a2007-08-29 22:35:05 +00001190 def readline(self):
1191 """Read line from remote."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001192 return self.file.readline()
Piers Laudera4f83132002-03-08 01:53:24 +00001193
1194
Bill Janssen426ea0a2007-08-29 22:35:05 +00001195 def send(self, data):
1196 """Send data to remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001197 bytes = len(data)
1198 while bytes > 0:
1199 sent = self.sslobj.write(data)
1200 if sent == bytes:
1201 break # avoid copy
1202 data = data[sent:]
1203 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001204
1205
Bill Janssen426ea0a2007-08-29 22:35:05 +00001206 def shutdown(self):
1207 """Close I/O established in "open"."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001208 self.file.close()
Bill Janssen426ea0a2007-08-29 22:35:05 +00001209 self.sock.close()
Piers Laudera4f83132002-03-08 01:53:24 +00001210
Bill Janssen426ea0a2007-08-29 22:35:05 +00001211
1212 def socket(self):
1213 """Return socket instance used to connect to IMAP4 server.
1214
1215 socket = <instance>.socket()
1216 """
1217 return self.sock
1218
1219
1220 def ssl(self):
1221 """Return SSLObject instance used to communicate with the IMAP4 server.
1222
Bill Janssen98d19da2007-09-10 21:51:02 +00001223 ssl = ssl.wrap_socket(<instance>.socket)
Bill Janssen426ea0a2007-08-29 22:35:05 +00001224 """
1225 return self.sslobj
Piers Laudera4f83132002-03-08 01:53:24 +00001226
Thomas Woutersa6900e82007-08-30 21:54:39 +00001227 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001228
1229
Piers Laudere0273de2002-11-22 05:53:04 +00001230class IMAP4_stream(IMAP4):
1231
1232 """IMAP4 client class over a stream
1233
1234 Instantiate with: IMAP4_stream(command)
1235
Georg Brandl36f42142010-01-02 12:35:01 +00001236 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001237
1238 for more documentation see the docstring of the parent class IMAP4.
1239 """
1240
1241
1242 def __init__(self, command):
1243 self.command = command
1244 IMAP4.__init__(self)
1245
1246
1247 def open(self, host = None, port = None):
1248 """Setup a stream connection.
1249 This connection will be used by the routines:
1250 read, readline, send, shutdown.
1251 """
1252 self.host = None # For compatibility with parent class
1253 self.port = None
1254 self.sock = None
1255 self.file = None
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001256 self.process = subprocess.Popen(self.command,
1257 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1258 shell=True, close_fds=True)
1259 self.writefile = self.process.stdin
1260 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001261
1262
1263 def read(self, size):
1264 """Read 'size' bytes from remote."""
1265 return self.readfile.read(size)
1266
1267
1268 def readline(self):
1269 """Read line from remote."""
1270 return self.readfile.readline()
1271
1272
1273 def send(self, data):
1274 """Send data to remote."""
1275 self.writefile.write(data)
1276 self.writefile.flush()
1277
1278
1279 def shutdown(self):
1280 """Close I/O established in "open"."""
1281 self.readfile.close()
1282 self.writefile.close()
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001283 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001284
1285
1286
Guido van Rossumeda960a1998-06-18 14:24:28 +00001287class _Authenticator:
1288
Tim Peters07e99cb2001-01-14 23:47:14 +00001289 """Private class to provide en/decoding
1290 for base64-based authentication conversation.
1291 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001292
Tim Peters07e99cb2001-01-14 23:47:14 +00001293 def __init__(self, mechinst):
1294 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001295
Tim Peters07e99cb2001-01-14 23:47:14 +00001296 def process(self, data):
1297 ret = self.mech(self.decode(data))
1298 if ret is None:
1299 return '*' # Abort conversation
1300 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001301
Tim Peters07e99cb2001-01-14 23:47:14 +00001302 def encode(self, inp):
1303 #
1304 # Invoke binascii.b2a_base64 iteratively with
1305 # short even length buffers, strip the trailing
1306 # line feed from the result and append. "Even"
1307 # means a number that factors to both 6 and 8,
1308 # so when it gets to the end of the 8-bit input
1309 # there's no partial 6-bit output.
1310 #
1311 oup = ''
1312 while inp:
1313 if len(inp) > 48:
1314 t = inp[:48]
1315 inp = inp[48:]
1316 else:
1317 t = inp
1318 inp = ''
1319 e = binascii.b2a_base64(t)
1320 if e:
1321 oup = oup + e[:-1]
1322 return oup
1323
1324 def decode(self, inp):
1325 if not inp:
1326 return ''
1327 return binascii.a2b_base64(inp)
1328
Guido van Rossumeda960a1998-06-18 14:24:28 +00001329
1330
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001331Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001332 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001333
1334def Internaldate2tuple(resp):
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001335 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001336
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001337 Return corresponding local time. The return value is a
1338 time.struct_time instance or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001339 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001340
Tim Peters07e99cb2001-01-14 23:47:14 +00001341 mo = InternalDate.match(resp)
1342 if not mo:
1343 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001344
Tim Peters07e99cb2001-01-14 23:47:14 +00001345 mon = Mon2num[mo.group('mon')]
1346 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001347
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001348 day = int(mo.group('day'))
1349 year = int(mo.group('year'))
1350 hour = int(mo.group('hour'))
1351 min = int(mo.group('min'))
1352 sec = int(mo.group('sec'))
1353 zoneh = int(mo.group('zoneh'))
1354 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001355
Tim Peters07e99cb2001-01-14 23:47:14 +00001356 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001357
Tim Peters07e99cb2001-01-14 23:47:14 +00001358 zone = (zoneh*60 + zonem)*60
1359 if zonen == '-':
1360 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001361
Tim Peters07e99cb2001-01-14 23:47:14 +00001362 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001363
Tim Peters07e99cb2001-01-14 23:47:14 +00001364 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001365
Tim Peters07e99cb2001-01-14 23:47:14 +00001366 # Following is necessary because the time module has no 'mkgmtime'.
1367 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001368
Tim Peters07e99cb2001-01-14 23:47:14 +00001369 lt = time.localtime(utc)
1370 if time.daylight and lt[-1]:
1371 zone = zone + time.altzone
1372 else:
1373 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001374
Tim Peters07e99cb2001-01-14 23:47:14 +00001375 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001376
1377
1378
1379def Int2AP(num):
1380
Tim Peters07e99cb2001-01-14 23:47:14 +00001381 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001382
Tim Peters07e99cb2001-01-14 23:47:14 +00001383 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1384 num = int(abs(num))
1385 while num:
1386 num, mod = divmod(num, 16)
1387 val = AP[mod] + val
1388 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001389
1390
1391
1392def ParseFlags(resp):
1393
Tim Peters07e99cb2001-01-14 23:47:14 +00001394 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001395
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 mo = Flags.match(resp)
1397 if not mo:
1398 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001399
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001400 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001401
1402
1403def Time2Internaldate(date_time):
1404
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001405 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001406
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001407 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Ezio Melottic2077b02011-03-16 12:34:31 +02001408 date_time argument can be a number (int or float) representing
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001409 seconds since epoch (as returned by time.time()), a 9-tuple
1410 representing local time (as returned by time.localtime()), or a
1411 double-quoted string. In the last case, it is assumed to already
1412 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001414
Serhiy Storchaka994f04d2016-12-27 15:09:36 +02001415 if isinstance(date_time, (int, long, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001416 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001417 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001418 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001419 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001420 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001421 else:
1422 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001423
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1425 if dt[0] == '0':
1426 dt = ' ' + dt[1:]
1427 if time.daylight and tt[-1]:
1428 zone = -time.altzone
1429 else:
1430 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001431 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
1433
1434
Guido van Rossum8c062211999-12-13 23:27:45 +00001435if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001436
Piers Laudere0273de2002-11-22 05:53:04 +00001437 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1438 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1439 # to test the IMAP4_stream class
1440
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001441 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001442
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001444 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001446 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001447
Piers Laudere0273de2002-11-22 05:53:04 +00001448 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001449 for opt,val in optlist:
1450 if opt == '-d':
1451 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001452 elif opt == '-s':
1453 stream_command = val
1454 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001455
Tim Peters07e99cb2001-01-14 23:47:14 +00001456 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001457
Tim Peters07e99cb2001-01-14 23:47:14 +00001458 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001459
Tim Peters07e99cb2001-01-14 23:47:14 +00001460 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001461 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001462
Piers Lauder47404ff2003-04-29 23:40:59 +00001463 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 +00001464 test_seq1 = (
1465 ('login', (USER, PASSWD)),
1466 ('create', ('/tmp/xxx 1',)),
1467 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1468 ('CREATE', ('/tmp/yyz 2',)),
1469 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1470 ('list', ('/tmp', 'yy*')),
1471 ('select', ('/tmp/yyz 2',)),
1472 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001473 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001474 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001475 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001476 ('expunge', ()),
1477 ('recent', ()),
1478 ('close', ()),
1479 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001480
Tim Peters07e99cb2001-01-14 23:47:14 +00001481 test_seq2 = (
1482 ('select', ()),
1483 ('response',('UIDVALIDITY',)),
1484 ('uid', ('SEARCH', 'ALL')),
1485 ('response', ('EXISTS',)),
1486 ('append', (None, None, None, test_mesg)),
1487 ('recent', ()),
1488 ('logout', ()),
1489 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001490
Tim Peters07e99cb2001-01-14 23:47:14 +00001491 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001492 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001493 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001494 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001495 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001496 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001497
Tim Peters07e99cb2001-01-14 23:47:14 +00001498 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001499 if stream_command:
1500 M = IMAP4_stream(stream_command)
1501 else:
1502 M = IMAP4(host)
1503 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001504 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001505 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001506 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001507
Tim Peters07e99cb2001-01-14 23:47:14 +00001508 for cmd,args in test_seq1:
1509 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001510
Tim Peters07e99cb2001-01-14 23:47:14 +00001511 for ml in run('list', ('/tmp/', 'yy%')):
1512 mo = re.match(r'.*"([^"]+)"$', ml)
1513 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001514 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001515 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001516
Tim Peters07e99cb2001-01-14 23:47:14 +00001517 for cmd,args in test_seq2:
1518 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001519
Tim Peters07e99cb2001-01-14 23:47:14 +00001520 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1521 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001522
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001523 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001524 if not uid: continue
1525 run('uid', ('FETCH', '%s' % uid[-1],
1526 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001527
Tim Peters07e99cb2001-01-14 23:47:14 +00001528 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001529
Tim Peters07e99cb2001-01-14 23:47:14 +00001530 except:
1531 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001532
Tim Peters07e99cb2001-01-14 23:47:14 +00001533 if not Debug:
1534 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001535If you would like to see debugging output,
1536try: %s -d5
1537''' % sys.argv[0]
1538
Tim Peters07e99cb2001-01-14 23:47:14 +00001539 raise