blob: 220d6e1bc0a669f7caa796fce387259adc860ef2 [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'),
Piers Lauder15e5d532001-07-20 10:52:06 +000073 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000074 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000075 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000076 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000077 'RENAME': ('AUTH', 'SELECTED'),
78 'SEARCH': ('SELECTED',),
79 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000080 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000081 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000082 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000083 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000084 'STATUS': ('AUTH', 'SELECTED'),
85 'STORE': ('SELECTED',),
86 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000087 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000088 'UID': ('SELECTED',),
89 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
90 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000091
Tim Peters07e99cb2001-01-14 23:47:14 +000092# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000093
Guido van Rossumeda960a1998-06-18 14:24:28 +000094Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000095Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
96InternalDate = re.compile(r'.*INTERNALDATE "'
Piers Lauder8659d902005-03-02 09:13:45 +000097 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 +000098 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
99 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
100 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +0000101Literal = re.compile(r'.*{(?P<size>\d+)}$')
Piers Lauder533366b2003-04-29 23:58:08 +0000102MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000103Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000104Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000105Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
106
107
108
109class IMAP4:
110
Tim Peters07e99cb2001-01-14 23:47:14 +0000111 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000112
Tim Peters07e99cb2001-01-14 23:47:14 +0000113 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000114
Tim Peters07e99cb2001-01-14 23:47:14 +0000115 host - host's name (default: localhost);
116 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000117
Tim Peters07e99cb2001-01-14 23:47:14 +0000118 All IMAP4rev1 commands are supported by methods of the same
119 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000120
Tim Peters07e99cb2001-01-14 23:47:14 +0000121 All arguments to commands are converted to strings, except for
122 AUTHENTICATE, and the last argument to APPEND which is passed as
123 an IMAP4 literal. If necessary (the string contains any
124 non-printing characters or white-space and isn't enclosed with
125 either parentheses or double quotes) each string is quoted.
126 However, the 'password' argument to the LOGIN command is always
127 quoted. If you want to avoid having an argument string quoted
128 (eg: the 'flags' argument to STORE) then enclose the string in
129 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000130
Tim Peters07e99cb2001-01-14 23:47:14 +0000131 Each command returns a tuple: (type, [data, ...]) where 'type'
132 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000133 tagged response, or untagged results from command. Each 'data'
134 is either a string, or a tuple. If a tuple, then the first part
135 is the header of the response, and the second part contains
136 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000137
Tim Peters07e99cb2001-01-14 23:47:14 +0000138 Errors raise the exception class <instance>.error("<reason>").
139 IMAP4 server errors raise <instance>.abort("<reason>"),
140 which is a sub-class of 'error'. Mailbox status changes
141 from READ-WRITE to READ-ONLY raise the exception class
142 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000143
Tim Peters07e99cb2001-01-14 23:47:14 +0000144 "error" exceptions imply a program error.
145 "abort" exceptions imply the connection should be reset, and
146 the command re-tried.
147 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000148
Piers Lauderd80ef022005-06-01 23:50:52 +0000149 Note: to use this module, you must read the RFCs pertaining to the
150 IMAP4 protocol, as the semantics of the arguments to each IMAP4
151 command are left to the invoker, not to mention the results. Also,
152 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000154
Tim Peters07e99cb2001-01-14 23:47:14 +0000155 class error(Exception): pass # Logical errors - debug required
156 class abort(error): pass # Service errors - close and retry
157 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000158
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000160
Tim Peters07e99cb2001-01-14 23:47:14 +0000161 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 self.debug = Debug
163 self.state = 'LOGOUT'
164 self.literal = None # A literal argument to a command
165 self.tagged_commands = {} # Tagged commands awaiting response
166 self.untagged_responses = {} # {typ: [data, ...], ...}
167 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000168 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000170
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000172
Tim Peters07e99cb2001-01-14 23:47:14 +0000173 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000174
Tim Peters07e99cb2001-01-14 23:47:14 +0000175 # Create unique tag for this session,
176 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000177
Piers Lauder2dfc1682005-07-05 04:20:07 +0000178 self.tagpre = Int2AP(random.randint(4096, 65535))
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 self.tagre = re.compile(r'(?P<tag>'
180 + self.tagpre
181 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000182
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 # Get server welcome message,
184 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000185
Tim Peters07e99cb2001-01-14 23:47:14 +0000186 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000187 self._cmd_log_len = 10
188 self._cmd_log_idx = 0
189 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000190 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000191 self._mesg('imaplib version %s' % __version__)
192 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000193
Tim Peters07e99cb2001-01-14 23:47:14 +0000194 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000195 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000196 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000197 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 self.state = 'NONAUTH'
199 else:
200 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000201
Piers Lauderd80ef022005-06-01 23:50:52 +0000202 typ, dat = self.capability()
203 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000204 raise self.error('no CAPABILITY response from server')
Piers Lauderd80ef022005-06-01 23:50:52 +0000205 self.capabilities = tuple(dat[-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000206
Tim Peters07e99cb2001-01-14 23:47:14 +0000207 if __debug__:
208 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000209 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000210
Tim Peters07e99cb2001-01-14 23:47:14 +0000211 for version in AllowedVersions:
212 if not version in self.capabilities:
213 continue
214 self.PROTOCOL_VERSION = version
215 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000216
Tim Peters07e99cb2001-01-14 23:47:14 +0000217 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000218
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000219
Tim Peters07e99cb2001-01-14 23:47:14 +0000220 def __getattr__(self, attr):
221 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000222 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000223 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000224 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000225
226
227
Piers Lauder15e5d532001-07-20 10:52:06 +0000228 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000229
230
Piers Lauderf97b2d72002-06-05 22:31:57 +0000231 def open(self, host = '', port = IMAP4_PORT):
232 """Setup connection to remote server on "host:port"
233 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000234 This connection will be used by the routines:
235 read, readline, send, shutdown.
236 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000237 self.host = host
238 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +0000239 self.sock = socket.create_connection((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000240 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000241
242
Piers Lauder15e5d532001-07-20 10:52:06 +0000243 def read(self, size):
244 """Read 'size' bytes from remote."""
245 return self.file.read(size)
246
247
248 def readline(self):
249 """Read line from remote."""
R David Murray020d7c32014-01-03 13:59:22 -0500250 line = self.file.readline(_MAXLINE + 1)
251 if len(line) > _MAXLINE:
252 raise self.error("got more than %d bytes" % _MAXLINE)
253 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000254
255
256 def send(self, data):
257 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000258 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000259
Piers Lauderf2d7d152002-02-22 01:15:17 +0000260
Piers Lauder15e5d532001-07-20 10:52:06 +0000261 def shutdown(self):
262 """Close I/O established in "open"."""
263 self.file.close()
Antoine Pitrouae933892010-11-10 09:02:33 +0000264 try:
265 self.sock.shutdown(socket.SHUT_RDWR)
266 except socket.error as e:
Victor Stinner800e4b72017-05-16 17:38:30 -0700267 # The server might already have closed the connection.
268 # On Windows, this may result in WSAEINVAL (error 10022):
269 # An invalid operation was attempted.
270 if e.errno not in (errno.ENOTCONN, 10022):
Antoine Pitrouae933892010-11-10 09:02:33 +0000271 raise
272 finally:
273 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000274
275
276 def socket(self):
277 """Return socket instance used to connect to IMAP4 server.
278
279 socket = <instance>.socket()
280 """
281 return self.sock
282
283
284
285 # Utility methods
286
287
Tim Peters07e99cb2001-01-14 23:47:14 +0000288 def recent(self):
289 """Return most recent 'RECENT' responses if any exist,
290 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000291
Tim Peters07e99cb2001-01-14 23:47:14 +0000292 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000293
Tim Peters07e99cb2001-01-14 23:47:14 +0000294 'data' is None if no new messages,
295 else list of RECENT responses, most recent last.
296 """
297 name = 'RECENT'
298 typ, dat = self._untagged_response('OK', [None], name)
299 if dat[-1]:
300 return typ, dat
301 typ, dat = self.noop() # Prod server for response
302 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000303
304
Tim Peters07e99cb2001-01-14 23:47:14 +0000305 def response(self, code):
306 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000307
Tim Peters07e99cb2001-01-14 23:47:14 +0000308 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000309
Tim Peters07e99cb2001-01-14 23:47:14 +0000310 (code, [data]) = <instance>.response(code)
311 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000312 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000313
314
Guido van Rossum26367a01998-09-28 15:34:46 +0000315
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000317
318
Tim Peters07e99cb2001-01-14 23:47:14 +0000319 def append(self, mailbox, flags, date_time, message):
320 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000321
Tim Peters07e99cb2001-01-14 23:47:14 +0000322 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000323
Tim Peters07e99cb2001-01-14 23:47:14 +0000324 All args except `message' can be None.
325 """
326 name = 'APPEND'
327 if not mailbox:
328 mailbox = 'INBOX'
329 if flags:
330 if (flags[0],flags[-1]) != ('(',')'):
331 flags = '(%s)' % flags
332 else:
333 flags = None
334 if date_time:
335 date_time = Time2Internaldate(date_time)
336 else:
337 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000338 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000340
341
Tim Peters07e99cb2001-01-14 23:47:14 +0000342 def authenticate(self, mechanism, authobject):
343 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 'mechanism' specifies which authentication mechanism is to
346 be used - it must appear in <instance>.capabilities in the
347 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000348
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000350
Tim Peters07e99cb2001-01-14 23:47:14 +0000351 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000352
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 It will be called to process server continuation responses.
354 It should return data that will be encoded and sent to server.
355 It should return None if the client abort response '*' should
356 be sent instead.
357 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000358 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000359 # XXX: shouldn't this code be removed, not commented out?
360 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000361 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000362 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000363 self.literal = _Authenticator(authobject).process
364 typ, dat = self._simple_command('AUTHENTICATE', mech)
365 if typ != 'OK':
366 raise self.error(dat[-1])
367 self.state = 'AUTH'
368 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000369
370
Piers Lauderd80ef022005-06-01 23:50:52 +0000371 def capability(self):
372 """(typ, [data]) = <instance>.capability()
373 Fetch capabilities list from server."""
374
375 name = 'CAPABILITY'
376 typ, dat = self._simple_command(name)
377 return self._untagged_response(typ, dat, name)
378
379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 def check(self):
381 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 (typ, [data]) = <instance>.check()
384 """
385 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000386
387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 def close(self):
389 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000390
Tim Peters07e99cb2001-01-14 23:47:14 +0000391 Deleted messages are removed from writable mailbox.
392 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
Tim Peters07e99cb2001-01-14 23:47:14 +0000394 (typ, [data]) = <instance>.close()
395 """
396 try:
397 typ, dat = self._simple_command('CLOSE')
398 finally:
399 self.state = 'AUTH'
400 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000401
402
Tim Peters07e99cb2001-01-14 23:47:14 +0000403 def copy(self, message_set, new_mailbox):
404 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000405
Tim Peters07e99cb2001-01-14 23:47:14 +0000406 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
407 """
408 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000409
410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 def create(self, mailbox):
412 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000413
Tim Peters07e99cb2001-01-14 23:47:14 +0000414 (typ, [data]) = <instance>.create(mailbox)
415 """
416 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 def delete(self, mailbox):
420 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000421
Tim Peters07e99cb2001-01-14 23:47:14 +0000422 (typ, [data]) = <instance>.delete(mailbox)
423 """
424 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000425
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000426 def deleteacl(self, mailbox, who):
427 """Delete the ACLs (remove any rights) set for who on mailbox.
428
429 (typ, [data]) = <instance>.deleteacl(mailbox, who)
430 """
431 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000432
Tim Peters07e99cb2001-01-14 23:47:14 +0000433 def expunge(self):
434 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000435
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000437
Tim Peters07e99cb2001-01-14 23:47:14 +0000438 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000439
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 'data' is list of 'EXPUNGE'd message numbers in order received.
441 """
442 name = 'EXPUNGE'
443 typ, dat = self._simple_command(name)
444 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000445
446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 def fetch(self, message_set, message_parts):
448 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 'message_parts' should be a string of selected parts
453 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000454
Tim Peters07e99cb2001-01-14 23:47:14 +0000455 'data' are tuples of message part envelope and data.
456 """
457 name = 'FETCH'
458 typ, dat = self._simple_command(name, message_set, message_parts)
459 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000460
461
Piers Lauder15e5d532001-07-20 10:52:06 +0000462 def getacl(self, mailbox):
463 """Get the ACLs for a mailbox.
464
465 (typ, [data]) = <instance>.getacl(mailbox)
466 """
467 typ, dat = self._simple_command('GETACL', mailbox)
468 return self._untagged_response(typ, dat, 'ACL')
469
470
Piers Lauderd80ef022005-06-01 23:50:52 +0000471 def getannotation(self, mailbox, entry, attribute):
472 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
473 Retrieve ANNOTATIONs."""
474
475 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
476 return self._untagged_response(typ, dat, 'ANNOTATION')
477
478
Piers Lauder3fca2912002-06-17 07:07:20 +0000479 def getquota(self, root):
480 """Get the quota root's resource usage and limits.
481
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000482 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000483
484 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000485 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000486 typ, dat = self._simple_command('GETQUOTA', root)
487 return self._untagged_response(typ, dat, 'QUOTA')
488
489
490 def getquotaroot(self, mailbox):
491 """Get the list of quota roots for the named mailbox.
492
493 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000494 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000495 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000496 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
497 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000498 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000499
500
Tim Peters07e99cb2001-01-14 23:47:14 +0000501 def list(self, directory='""', pattern='*'):
502 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000503
Tim Peters07e99cb2001-01-14 23:47:14 +0000504 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
Tim Peters07e99cb2001-01-14 23:47:14 +0000506 'data' is list of LIST responses.
507 """
508 name = 'LIST'
509 typ, dat = self._simple_command(name, directory, pattern)
510 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000511
512
Tim Peters07e99cb2001-01-14 23:47:14 +0000513 def login(self, user, password):
514 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000515
Tim Peters07e99cb2001-01-14 23:47:14 +0000516 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000517
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 NB: 'password' will be quoted.
519 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000520 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
521 if typ != 'OK':
522 raise self.error(dat[-1])
523 self.state = 'AUTH'
524 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000525
526
Piers Laudere0273de2002-11-22 05:53:04 +0000527 def login_cram_md5(self, user, password):
528 """ Force use of CRAM-MD5 authentication.
529
530 (typ, [data]) = <instance>.login_cram_md5(user, password)
531 """
532 self.user, self.password = user, password
533 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
534
535
536 def _CRAM_MD5_AUTH(self, challenge):
537 """ Authobject to use with CRAM-MD5 authentication. """
538 import hmac
539 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
540
541
Tim Peters07e99cb2001-01-14 23:47:14 +0000542 def logout(self):
543 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000544
Tim Peters07e99cb2001-01-14 23:47:14 +0000545 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000546
Tim Peters07e99cb2001-01-14 23:47:14 +0000547 Returns server 'BYE' response.
548 """
549 self.state = 'LOGOUT'
550 try: typ, dat = self._simple_command('LOGOUT')
551 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000552 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000553 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000554 return 'BYE', self.untagged_responses['BYE']
555 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000556
557
Tim Peters07e99cb2001-01-14 23:47:14 +0000558 def lsub(self, directory='""', pattern='*'):
559 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000560
Tim Peters07e99cb2001-01-14 23:47:14 +0000561 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000562
Tim Peters07e99cb2001-01-14 23:47:14 +0000563 'data' are tuples of message part envelope and data.
564 """
565 name = 'LSUB'
566 typ, dat = self._simple_command(name, directory, pattern)
567 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000568
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000569 def myrights(self, mailbox):
570 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
571
572 (typ, [data]) = <instance>.myrights(mailbox)
573 """
574 typ,dat = self._simple_command('MYRIGHTS', mailbox)
575 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000576
Piers Lauder15e5d532001-07-20 10:52:06 +0000577 def namespace(self):
578 """ Returns IMAP namespaces ala rfc2342
579
580 (typ, [data, ...]) = <instance>.namespace()
581 """
582 name = 'NAMESPACE'
583 typ, dat = self._simple_command(name)
584 return self._untagged_response(typ, dat, name)
585
586
Tim Peters07e99cb2001-01-14 23:47:14 +0000587 def noop(self):
588 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000589
Piers Laudere0273de2002-11-22 05:53:04 +0000590 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 """
592 if __debug__:
593 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000594 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000596
597
Tim Peters07e99cb2001-01-14 23:47:14 +0000598 def partial(self, message_num, message_part, start, length):
599 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000600
Tim Peters07e99cb2001-01-14 23:47:14 +0000601 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000602
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 'data' is tuple of message part envelope and data.
604 """
605 name = 'PARTIAL'
606 typ, dat = self._simple_command(name, message_num, message_part, start, length)
607 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000608
609
Piers Laudere0273de2002-11-22 05:53:04 +0000610 def proxyauth(self, user):
611 """Assume authentication as "user".
612
613 Allows an authorised administrator to proxy into any user's
614 mailbox.
615
616 (typ, [data]) = <instance>.proxyauth(user)
617 """
618
619 name = 'PROXYAUTH'
620 return self._simple_command('PROXYAUTH', user)
621
622
Tim Peters07e99cb2001-01-14 23:47:14 +0000623 def rename(self, oldmailbox, newmailbox):
624 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000625
Piers Laudere0273de2002-11-22 05:53:04 +0000626 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000627 """
628 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000629
630
Tim Peters07e99cb2001-01-14 23:47:14 +0000631 def search(self, charset, *criteria):
632 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000633
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000634 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000635
Tim Peters07e99cb2001-01-14 23:47:14 +0000636 'data' is space separated list of matching message numbers.
637 """
638 name = 'SEARCH'
639 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000640 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000641 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000642 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000643 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000644
645
Piers Lauder14f39402005-08-31 10:46:29 +0000646 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000647 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000648
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000650
Piers Lauder14f39402005-08-31 10:46:29 +0000651 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000652
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000654
655 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
656 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000657 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000658 self.untagged_responses = {} # Flush old responses.
659 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000660 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 name = 'EXAMINE'
662 else:
663 name = 'SELECT'
664 typ, dat = self._simple_command(name, mailbox)
665 if typ != 'OK':
666 self.state = 'AUTH' # Might have been 'SELECTED'
667 return typ, dat
668 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000669 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000670 and not readonly:
671 if __debug__:
672 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000673 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000674 raise self.readonly('%s is not writable' % mailbox)
675 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000676
677
Piers Lauder15e5d532001-07-20 10:52:06 +0000678 def setacl(self, mailbox, who, what):
679 """Set a mailbox acl.
680
Piers Lauderf167dc32004-03-25 00:12:21 +0000681 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000682 """
683 return self._simple_command('SETACL', mailbox, who, what)
684
685
Piers Lauderd80ef022005-06-01 23:50:52 +0000686 def setannotation(self, *args):
687 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
688 Set ANNOTATIONs."""
689
690 typ, dat = self._simple_command('SETANNOTATION', *args)
691 return self._untagged_response(typ, dat, 'ANNOTATION')
692
693
Piers Lauder3fca2912002-06-17 07:07:20 +0000694 def setquota(self, root, limits):
695 """Set the quota root's resource limits.
696
697 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000698 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000699 typ, dat = self._simple_command('SETQUOTA', root, limits)
700 return self._untagged_response(typ, dat, 'QUOTA')
701
702
Piers Lauder15e5d532001-07-20 10:52:06 +0000703 def sort(self, sort_criteria, charset, *search_criteria):
704 """IMAP4rev1 extension SORT command.
705
706 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
707 """
708 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000709 #if not name in self.capabilities: # Let the server decide!
710 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000711 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000712 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000713 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000714 return self._untagged_response(typ, dat, name)
715
716
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 def status(self, mailbox, names):
718 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000719
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 (typ, [data]) = <instance>.status(mailbox, names)
721 """
722 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000723 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000724 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000725 typ, dat = self._simple_command(name, mailbox, names)
726 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
728
Tim Peters07e99cb2001-01-14 23:47:14 +0000729 def store(self, message_set, command, flags):
730 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000731
Tim Peters07e99cb2001-01-14 23:47:14 +0000732 (typ, [data]) = <instance>.store(message_set, command, flags)
733 """
734 if (flags[0],flags[-1]) != ('(',')'):
735 flags = '(%s)' % flags # Avoid quoting the flags
736 typ, dat = self._simple_command('STORE', message_set, command, flags)
737 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000738
739
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 def subscribe(self, mailbox):
741 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000742
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 (typ, [data]) = <instance>.subscribe(mailbox)
744 """
745 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000746
747
Martin v. Löwisd8921372003-11-10 06:44:44 +0000748 def thread(self, threading_algorithm, charset, *search_criteria):
749 """IMAPrev1 extension THREAD command.
750
Mark Dickinsondb69f012009-12-24 16:06:58 +0000751 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000752 """
753 name = 'THREAD'
754 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
755 return self._untagged_response(typ, dat, name)
756
757
Tim Peters07e99cb2001-01-14 23:47:14 +0000758 def uid(self, command, *args):
759 """Execute "command arg ..." with messages identified by UID,
760 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000761
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000763
Tim Peters07e99cb2001-01-14 23:47:14 +0000764 Returns response appropriate to 'command'.
765 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000766 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000767 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 raise self.error("Unknown IMAP4 UID command: %s" % command)
769 if self.state not in Commands[command]:
Georg Brandl6c104f62007-03-13 18:24:40 +0000770 raise self.error("command %s illegal in state %s, "
771 "only allowed in states %s" %
772 (command, self.state,
773 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000774 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000775 typ, dat = self._simple_command(name, command, *args)
Georg Brandl004c74d2010-08-01 19:06:51 +0000776 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000777 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000778 else:
779 name = 'FETCH'
780 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000781
782
Tim Peters07e99cb2001-01-14 23:47:14 +0000783 def unsubscribe(self, mailbox):
784 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000785
Tim Peters07e99cb2001-01-14 23:47:14 +0000786 (typ, [data]) = <instance>.unsubscribe(mailbox)
787 """
788 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000789
790
Tim Peters07e99cb2001-01-14 23:47:14 +0000791 def xatom(self, name, *args):
792 """Allow simple extension commands
793 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000794
Piers Lauder15e5d532001-07-20 10:52:06 +0000795 Assumes command is legal in current state.
796
Tim Peters07e99cb2001-01-14 23:47:14 +0000797 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000798
799 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000801 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000802 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000803 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000804 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000805 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000806 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000807
808
809
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000811
812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000814
Tim Peters07e99cb2001-01-14 23:47:14 +0000815 if dat is None: dat = ''
816 ur = self.untagged_responses
817 if __debug__:
818 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000819 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000821 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 ur[typ].append(dat)
823 else:
824 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000825
826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 def _check_bye(self):
828 bye = self.untagged_responses.get('BYE')
829 if bye:
830 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000831
832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 if self.state not in Commands[name]:
836 self.literal = None
Georg Brandl6c104f62007-03-13 18:24:40 +0000837 raise self.error("command %s illegal in state %s, "
838 "only allowed in states %s" %
839 (name, self.state,
840 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000841
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000843 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000845
Raymond Hettinger54f02222002-06-01 14:18:47 +0000846 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 and not self.is_readonly:
848 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 tag = self._new_tag()
851 data = '%s %s' % (tag, name)
852 for arg in args:
853 if arg is None: continue
854 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000855
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 literal = self.literal
857 if literal is not None:
858 self.literal = None
859 if type(literal) is type(self._command):
860 literator = literal
861 else:
862 literator = None
863 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000864
Tim Peters07e99cb2001-01-14 23:47:14 +0000865 if __debug__:
866 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000867 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000869 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000872 self.send('%s%s' % (data, CRLF))
873 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 if literal is None:
877 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 while 1:
880 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 while self._get_response():
883 if self.tagged_commands[tag]: # BAD/NO?
884 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000887
Tim Peters07e99cb2001-01-14 23:47:14 +0000888 if literator:
889 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000890
Tim Peters07e99cb2001-01-14 23:47:14 +0000891 if __debug__:
892 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000893 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000894
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000896 self.send(literal)
897 self.send(CRLF)
898 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 if not literator:
902 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000905
906
Tim Peters07e99cb2001-01-14 23:47:14 +0000907 def _command_complete(self, name, tag):
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000908 # BYE is expected after LOGOUT
909 if name != 'LOGOUT':
910 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000911 try:
912 typ, data = self._get_tagged_response(tag)
913 except self.abort, val:
914 raise self.abort('command: %s => %s' % (name, val))
915 except self.error, val:
916 raise self.error('command: %s => %s' % (name, val))
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000917 if name != 'LOGOUT':
918 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 if typ == 'BAD':
920 raise self.error('%s command error: %s %s' % (name, typ, data))
921 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000922
923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 # Read response and store.
927 #
928 # Returns None for continuation responses,
929 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 if self._match(self.tagre, resp):
936 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000937 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000939
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 typ = self.mo.group('type')
941 dat = self.mo.group('data')
942 self.tagged_commands[tag] = (typ, [dat])
943 else:
944 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000947
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 if not self._match(Untagged_response, resp):
949 if self._match(Untagged_status, resp):
950 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000951
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 if self.mo is None:
953 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 if self._match(Continuation, resp):
956 self.continuation_response = self.mo.group('data')
957 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000960
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 typ = self.mo.group('type')
962 dat = self.mo.group('data')
963 if dat is None: dat = '' # Null untagged response
964 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000965
Tim Peters07e99cb2001-01-14 23:47:14 +0000966 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000967
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000969
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000971
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000972 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 if __debug__:
974 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000975 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000976 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000983
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000985
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
991 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 if __debug__:
994 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000995 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001001
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 while 1:
1003 result = self.tagged_commands[tag]
1004 if result is not None:
1005 del self.tagged_commands[tag]
1006 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001007
R David Murrayca00c6e2014-02-07 13:51:22 -05001008 # If we've seen a BYE at this point, the socket will be
1009 # closed, so report the BYE now.
1010
1011 self._check_bye()
1012
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 # Some have reported "unexpected response" exceptions.
1014 # Note that ignoring them here causes loops.
1015 # Instead, send me details of the unexpected response and
1016 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 try:
1019 self._get_response()
1020 except self.abort, val:
1021 if __debug__:
1022 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001023 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
1026
Tim Peters07e99cb2001-01-14 23:47:14 +00001027 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001028
Piers Lauder15e5d532001-07-20 10:52:06 +00001029 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 if not line:
1031 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001032
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 # Protocol mandates all lines terminated by CRLF
R. David Murray93321f32009-12-09 15:15:31 +00001034 if not line.endswith('\r\n'):
1035 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001036
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 line = line[:-2]
1038 if __debug__:
1039 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001040 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001042 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
1045
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 # Run compiled regular expression match method on 's'.
1049 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001050
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 self.mo = cre.match(s)
1052 if __debug__:
1053 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001054 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001056
1057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 tag = '%s%s' % (self.tagpre, self.tagnum)
1061 self.tagnum = self.tagnum + 1
1062 self.tagged_commands[tag] = None
1063 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001064
1065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 # Must quote command args if non-alphanumeric chars present,
1069 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001070
Tim Peters07e99cb2001-01-14 23:47:14 +00001071 if type(arg) is not type(''):
1072 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001073 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001075 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001076 return arg
1077 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001078
1079
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001081
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001082 arg = arg.replace('\\', '\\\\')
1083 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001084
Tim Peters07e99cb2001-01-14 23:47:14 +00001085 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001086
1087
Tim Peters07e99cb2001-01-14 23:47:14 +00001088 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001089
Guido van Rossum68468eb2003-02-27 20:14:51 +00001090 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
1092
Tim Peters07e99cb2001-01-14 23:47:14 +00001093 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001094
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 if typ == 'NO':
1096 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001097 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001098 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001099 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001100 if __debug__:
1101 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001102 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001103 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001104
1105
Piers Lauderf2d7d152002-02-22 01:15:17 +00001106 if __debug__:
1107
1108 def _mesg(self, s, secs=None):
1109 if secs is None:
1110 secs = time.time()
1111 tm = time.strftime('%M:%S', time.localtime(secs))
1112 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1113 sys.stderr.flush()
1114
1115 def _dump_ur(self, dict):
1116 # Dump untagged responses (in `dict').
1117 l = dict.items()
1118 if not l: return
1119 t = '\n\t\t'
1120 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1121 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1122
1123 def _log(self, line):
1124 # Keep log of last `_cmd_log_len' interactions for debugging.
1125 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1126 self._cmd_log_idx += 1
1127 if self._cmd_log_idx >= self._cmd_log_len:
1128 self._cmd_log_idx = 0
1129
1130 def print_log(self):
1131 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1132 i, n = self._cmd_log_idx, self._cmd_log_len
1133 while n:
1134 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001135 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001136 except:
1137 pass
1138 i += 1
1139 if i >= self._cmd_log_len:
1140 i = 0
1141 n -= 1
1142
1143
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001144
Bill Janssen426ea0a2007-08-29 22:35:05 +00001145try:
1146 import ssl
1147except ImportError:
1148 pass
1149else:
1150 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001151
Bill Janssen426ea0a2007-08-29 22:35:05 +00001152 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001153
Bill Janssen426ea0a2007-08-29 22:35:05 +00001154 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001155
Bill Janssen426ea0a2007-08-29 22:35:05 +00001156 host - host's name (default: localhost);
1157 port - port number (default: standard IMAP4 SSL port).
1158 keyfile - PEM formatted file that contains your private key (default: None);
1159 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001160
Bill Janssen426ea0a2007-08-29 22:35:05 +00001161 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001162 """
Piers Laudera4f83132002-03-08 01:53:24 +00001163
1164
Bill Janssen426ea0a2007-08-29 22:35:05 +00001165 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1166 self.keyfile = keyfile
1167 self.certfile = certfile
1168 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001169
1170
Bill Janssen426ea0a2007-08-29 22:35:05 +00001171 def open(self, host = '', port = IMAP4_SSL_PORT):
1172 """Setup connection to remote server on "host:port".
1173 (default: localhost:standard IMAP4 SSL port).
1174 This connection will be used by the routines:
1175 read, readline, send, shutdown.
1176 """
1177 self.host = host
1178 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +00001179 self.sock = socket.create_connection((host, port))
Bill Janssen98d19da2007-09-10 21:51:02 +00001180 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001181 self.file = self.sslobj.makefile('rb')
Piers Laudera4f83132002-03-08 01:53:24 +00001182
1183
Bill Janssen426ea0a2007-08-29 22:35:05 +00001184 def read(self, size):
1185 """Read 'size' bytes from remote."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001186 return self.file.read(size)
Piers Laudera4f83132002-03-08 01:53:24 +00001187
1188
Bill Janssen426ea0a2007-08-29 22:35:05 +00001189 def readline(self):
1190 """Read line from remote."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001191 return self.file.readline()
Piers Laudera4f83132002-03-08 01:53:24 +00001192
1193
Bill Janssen426ea0a2007-08-29 22:35:05 +00001194 def send(self, data):
1195 """Send data to remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001196 bytes = len(data)
1197 while bytes > 0:
1198 sent = self.sslobj.write(data)
1199 if sent == bytes:
1200 break # avoid copy
1201 data = data[sent:]
1202 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001203
1204
Bill Janssen426ea0a2007-08-29 22:35:05 +00001205 def shutdown(self):
1206 """Close I/O established in "open"."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001207 self.file.close()
Bill Janssen426ea0a2007-08-29 22:35:05 +00001208 self.sock.close()
Piers Laudera4f83132002-03-08 01:53:24 +00001209
Bill Janssen426ea0a2007-08-29 22:35:05 +00001210
1211 def socket(self):
1212 """Return socket instance used to connect to IMAP4 server.
1213
1214 socket = <instance>.socket()
1215 """
1216 return self.sock
1217
1218
1219 def ssl(self):
1220 """Return SSLObject instance used to communicate with the IMAP4 server.
1221
Bill Janssen98d19da2007-09-10 21:51:02 +00001222 ssl = ssl.wrap_socket(<instance>.socket)
Bill Janssen426ea0a2007-08-29 22:35:05 +00001223 """
1224 return self.sslobj
Piers Laudera4f83132002-03-08 01:53:24 +00001225
Thomas Woutersa6900e82007-08-30 21:54:39 +00001226 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001227
1228
Piers Laudere0273de2002-11-22 05:53:04 +00001229class IMAP4_stream(IMAP4):
1230
1231 """IMAP4 client class over a stream
1232
1233 Instantiate with: IMAP4_stream(command)
1234
Georg Brandl36f42142010-01-02 12:35:01 +00001235 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001236
1237 for more documentation see the docstring of the parent class IMAP4.
1238 """
1239
1240
1241 def __init__(self, command):
1242 self.command = command
1243 IMAP4.__init__(self)
1244
1245
1246 def open(self, host = None, port = None):
1247 """Setup a stream connection.
1248 This connection will be used by the routines:
1249 read, readline, send, shutdown.
1250 """
1251 self.host = None # For compatibility with parent class
1252 self.port = None
1253 self.sock = None
1254 self.file = None
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001255 self.process = subprocess.Popen(self.command,
1256 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1257 shell=True, close_fds=True)
1258 self.writefile = self.process.stdin
1259 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001260
1261
1262 def read(self, size):
1263 """Read 'size' bytes from remote."""
1264 return self.readfile.read(size)
1265
1266
1267 def readline(self):
1268 """Read line from remote."""
1269 return self.readfile.readline()
1270
1271
1272 def send(self, data):
1273 """Send data to remote."""
1274 self.writefile.write(data)
1275 self.writefile.flush()
1276
1277
1278 def shutdown(self):
1279 """Close I/O established in "open"."""
1280 self.readfile.close()
1281 self.writefile.close()
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001282 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001283
1284
1285
Guido van Rossumeda960a1998-06-18 14:24:28 +00001286class _Authenticator:
1287
Tim Peters07e99cb2001-01-14 23:47:14 +00001288 """Private class to provide en/decoding
1289 for base64-based authentication conversation.
1290 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001291
Tim Peters07e99cb2001-01-14 23:47:14 +00001292 def __init__(self, mechinst):
1293 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001294
Tim Peters07e99cb2001-01-14 23:47:14 +00001295 def process(self, data):
1296 ret = self.mech(self.decode(data))
1297 if ret is None:
1298 return '*' # Abort conversation
1299 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001300
Tim Peters07e99cb2001-01-14 23:47:14 +00001301 def encode(self, inp):
1302 #
1303 # Invoke binascii.b2a_base64 iteratively with
1304 # short even length buffers, strip the trailing
1305 # line feed from the result and append. "Even"
1306 # means a number that factors to both 6 and 8,
1307 # so when it gets to the end of the 8-bit input
1308 # there's no partial 6-bit output.
1309 #
1310 oup = ''
1311 while inp:
1312 if len(inp) > 48:
1313 t = inp[:48]
1314 inp = inp[48:]
1315 else:
1316 t = inp
1317 inp = ''
1318 e = binascii.b2a_base64(t)
1319 if e:
1320 oup = oup + e[:-1]
1321 return oup
1322
1323 def decode(self, inp):
1324 if not inp:
1325 return ''
1326 return binascii.a2b_base64(inp)
1327
Guido van Rossumeda960a1998-06-18 14:24:28 +00001328
1329
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001330Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001331 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001332
1333def Internaldate2tuple(resp):
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001334 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001335
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001336 Return corresponding local time. The return value is a
1337 time.struct_time instance or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001338 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001339
Tim Peters07e99cb2001-01-14 23:47:14 +00001340 mo = InternalDate.match(resp)
1341 if not mo:
1342 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 mon = Mon2num[mo.group('mon')]
1345 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001346
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001347 day = int(mo.group('day'))
1348 year = int(mo.group('year'))
1349 hour = int(mo.group('hour'))
1350 min = int(mo.group('min'))
1351 sec = int(mo.group('sec'))
1352 zoneh = int(mo.group('zoneh'))
1353 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001354
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001356
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 zone = (zoneh*60 + zonem)*60
1358 if zonen == '-':
1359 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001362
Tim Peters07e99cb2001-01-14 23:47:14 +00001363 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001364
Tim Peters07e99cb2001-01-14 23:47:14 +00001365 # Following is necessary because the time module has no 'mkgmtime'.
1366 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001367
Tim Peters07e99cb2001-01-14 23:47:14 +00001368 lt = time.localtime(utc)
1369 if time.daylight and lt[-1]:
1370 zone = zone + time.altzone
1371 else:
1372 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001373
Tim Peters07e99cb2001-01-14 23:47:14 +00001374 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001375
1376
1377
1378def Int2AP(num):
1379
Tim Peters07e99cb2001-01-14 23:47:14 +00001380 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001381
Tim Peters07e99cb2001-01-14 23:47:14 +00001382 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1383 num = int(abs(num))
1384 while num:
1385 num, mod = divmod(num, 16)
1386 val = AP[mod] + val
1387 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001388
1389
1390
1391def ParseFlags(resp):
1392
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001394
Tim Peters07e99cb2001-01-14 23:47:14 +00001395 mo = Flags.match(resp)
1396 if not mo:
1397 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001398
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001399 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001400
1401
1402def Time2Internaldate(date_time):
1403
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001404 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001405
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001406 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Ezio Melottic2077b02011-03-16 12:34:31 +02001407 date_time argument can be a number (int or float) representing
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001408 seconds since epoch (as returned by time.time()), a 9-tuple
1409 representing local time (as returned by time.localtime()), or a
1410 double-quoted string. In the last case, it is assumed to already
1411 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001412 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001413
Serhiy Storchaka994f04d2016-12-27 15:09:36 +02001414 if isinstance(date_time, (int, long, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001415 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001416 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001418 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001419 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001420 else:
1421 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001422
Tim Peters07e99cb2001-01-14 23:47:14 +00001423 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1424 if dt[0] == '0':
1425 dt = ' ' + dt[1:]
1426 if time.daylight and tt[-1]:
1427 zone = -time.altzone
1428 else:
1429 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001430 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001431
1432
1433
Guido van Rossum8c062211999-12-13 23:27:45 +00001434if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001435
Piers Laudere0273de2002-11-22 05:53:04 +00001436 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1437 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1438 # to test the IMAP4_stream class
1439
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001440 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001441
Tim Peters07e99cb2001-01-14 23:47:14 +00001442 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001443 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001444 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001445 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001446
Piers Laudere0273de2002-11-22 05:53:04 +00001447 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001448 for opt,val in optlist:
1449 if opt == '-d':
1450 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001451 elif opt == '-s':
1452 stream_command = val
1453 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001454
Tim Peters07e99cb2001-01-14 23:47:14 +00001455 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001456
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001458
Tim Peters07e99cb2001-01-14 23:47:14 +00001459 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001460 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001461
Piers Lauder47404ff2003-04-29 23:40:59 +00001462 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 +00001463 test_seq1 = (
1464 ('login', (USER, PASSWD)),
1465 ('create', ('/tmp/xxx 1',)),
1466 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1467 ('CREATE', ('/tmp/yyz 2',)),
1468 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1469 ('list', ('/tmp', 'yy*')),
1470 ('select', ('/tmp/yyz 2',)),
1471 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001472 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001473 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001474 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001475 ('expunge', ()),
1476 ('recent', ()),
1477 ('close', ()),
1478 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001479
Tim Peters07e99cb2001-01-14 23:47:14 +00001480 test_seq2 = (
1481 ('select', ()),
1482 ('response',('UIDVALIDITY',)),
1483 ('uid', ('SEARCH', 'ALL')),
1484 ('response', ('EXISTS',)),
1485 ('append', (None, None, None, test_mesg)),
1486 ('recent', ()),
1487 ('logout', ()),
1488 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001489
Tim Peters07e99cb2001-01-14 23:47:14 +00001490 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001491 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001492 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001493 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001494 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001495 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001496
Tim Peters07e99cb2001-01-14 23:47:14 +00001497 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001498 if stream_command:
1499 M = IMAP4_stream(stream_command)
1500 else:
1501 M = IMAP4(host)
1502 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001503 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001504 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001505 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001506
Tim Peters07e99cb2001-01-14 23:47:14 +00001507 for cmd,args in test_seq1:
1508 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001509
Tim Peters07e99cb2001-01-14 23:47:14 +00001510 for ml in run('list', ('/tmp/', 'yy%')):
1511 mo = re.match(r'.*"([^"]+)"$', ml)
1512 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001513 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001514 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001515
Tim Peters07e99cb2001-01-14 23:47:14 +00001516 for cmd,args in test_seq2:
1517 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001518
Tim Peters07e99cb2001-01-14 23:47:14 +00001519 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1520 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001521
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001522 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001523 if not uid: continue
1524 run('uid', ('FETCH', '%s' % uid[-1],
1525 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001526
Tim Peters07e99cb2001-01-14 23:47:14 +00001527 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001528
Tim Peters07e99cb2001-01-14 23:47:14 +00001529 except:
1530 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001531
Tim Peters07e99cb2001-01-14 23:47:14 +00001532 if not Debug:
1533 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001534If you would like to see debugging output,
1535try: %s -d5
1536''' % sys.argv[0]
1537
Tim Peters07e99cb2001-01-14 23:47:14 +00001538 raise