blob: 826eea25248a59b89f791134647fc1fd8d1d623a [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:
267 # The server might already have closed the connection
268 if e.errno != errno.ENOTCONN:
269 raise
270 finally:
271 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000272
273
274 def socket(self):
275 """Return socket instance used to connect to IMAP4 server.
276
277 socket = <instance>.socket()
278 """
279 return self.sock
280
281
282
283 # Utility methods
284
285
Tim Peters07e99cb2001-01-14 23:47:14 +0000286 def recent(self):
287 """Return most recent 'RECENT' responses if any exist,
288 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000289
Tim Peters07e99cb2001-01-14 23:47:14 +0000290 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000291
Tim Peters07e99cb2001-01-14 23:47:14 +0000292 'data' is None if no new messages,
293 else list of RECENT responses, most recent last.
294 """
295 name = 'RECENT'
296 typ, dat = self._untagged_response('OK', [None], name)
297 if dat[-1]:
298 return typ, dat
299 typ, dat = self.noop() # Prod server for response
300 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000301
302
Tim Peters07e99cb2001-01-14 23:47:14 +0000303 def response(self, code):
304 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000305
Tim Peters07e99cb2001-01-14 23:47:14 +0000306 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000307
Tim Peters07e99cb2001-01-14 23:47:14 +0000308 (code, [data]) = <instance>.response(code)
309 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000310 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000311
312
Guido van Rossum26367a01998-09-28 15:34:46 +0000313
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000315
316
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 def append(self, mailbox, flags, date_time, message):
318 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000319
Tim Peters07e99cb2001-01-14 23:47:14 +0000320 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000321
Tim Peters07e99cb2001-01-14 23:47:14 +0000322 All args except `message' can be None.
323 """
324 name = 'APPEND'
325 if not mailbox:
326 mailbox = 'INBOX'
327 if flags:
328 if (flags[0],flags[-1]) != ('(',')'):
329 flags = '(%s)' % flags
330 else:
331 flags = None
332 if date_time:
333 date_time = Time2Internaldate(date_time)
334 else:
335 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000336 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000337 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000338
339
Tim Peters07e99cb2001-01-14 23:47:14 +0000340 def authenticate(self, mechanism, authobject):
341 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000342
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 'mechanism' specifies which authentication mechanism is to
344 be used - it must appear in <instance>.capabilities in the
345 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000346
Tim Peters07e99cb2001-01-14 23:47:14 +0000347 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000348
Tim Peters07e99cb2001-01-14 23:47:14 +0000349 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000350
Tim Peters07e99cb2001-01-14 23:47:14 +0000351 It will be called to process server continuation responses.
352 It should return data that will be encoded and sent to server.
353 It should return None if the client abort response '*' should
354 be sent instead.
355 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000356 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000357 # XXX: shouldn't this code be removed, not commented out?
358 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000359 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000360 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 self.literal = _Authenticator(authobject).process
362 typ, dat = self._simple_command('AUTHENTICATE', mech)
363 if typ != 'OK':
364 raise self.error(dat[-1])
365 self.state = 'AUTH'
366 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000367
368
Piers Lauderd80ef022005-06-01 23:50:52 +0000369 def capability(self):
370 """(typ, [data]) = <instance>.capability()
371 Fetch capabilities list from server."""
372
373 name = 'CAPABILITY'
374 typ, dat = self._simple_command(name)
375 return self._untagged_response(typ, dat, name)
376
377
Tim Peters07e99cb2001-01-14 23:47:14 +0000378 def check(self):
379 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 (typ, [data]) = <instance>.check()
382 """
383 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000384
385
Tim Peters07e99cb2001-01-14 23:47:14 +0000386 def close(self):
387 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000388
Tim Peters07e99cb2001-01-14 23:47:14 +0000389 Deleted messages are removed from writable mailbox.
390 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000391
Tim Peters07e99cb2001-01-14 23:47:14 +0000392 (typ, [data]) = <instance>.close()
393 """
394 try:
395 typ, dat = self._simple_command('CLOSE')
396 finally:
397 self.state = 'AUTH'
398 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
400
Tim Peters07e99cb2001-01-14 23:47:14 +0000401 def copy(self, message_set, new_mailbox):
402 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
405 """
406 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
408
Tim Peters07e99cb2001-01-14 23:47:14 +0000409 def create(self, mailbox):
410 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 (typ, [data]) = <instance>.create(mailbox)
413 """
414 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000415
416
Tim Peters07e99cb2001-01-14 23:47:14 +0000417 def delete(self, mailbox):
418 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000419
Tim Peters07e99cb2001-01-14 23:47:14 +0000420 (typ, [data]) = <instance>.delete(mailbox)
421 """
422 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000423
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000424 def deleteacl(self, mailbox, who):
425 """Delete the ACLs (remove any rights) set for who on mailbox.
426
427 (typ, [data]) = <instance>.deleteacl(mailbox, who)
428 """
429 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 def expunge(self):
432 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000433
Tim Peters07e99cb2001-01-14 23:47:14 +0000434 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000435
Tim Peters07e99cb2001-01-14 23:47:14 +0000436 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000437
Tim Peters07e99cb2001-01-14 23:47:14 +0000438 'data' is list of 'EXPUNGE'd message numbers in order received.
439 """
440 name = 'EXPUNGE'
441 typ, dat = self._simple_command(name)
442 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000443
444
Tim Peters07e99cb2001-01-14 23:47:14 +0000445 def fetch(self, message_set, message_parts):
446 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000447
Tim Peters07e99cb2001-01-14 23:47:14 +0000448 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 'message_parts' should be a string of selected parts
451 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000452
Tim Peters07e99cb2001-01-14 23:47:14 +0000453 'data' are tuples of message part envelope and data.
454 """
455 name = 'FETCH'
456 typ, dat = self._simple_command(name, message_set, message_parts)
457 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000458
459
Piers Lauder15e5d532001-07-20 10:52:06 +0000460 def getacl(self, mailbox):
461 """Get the ACLs for a mailbox.
462
463 (typ, [data]) = <instance>.getacl(mailbox)
464 """
465 typ, dat = self._simple_command('GETACL', mailbox)
466 return self._untagged_response(typ, dat, 'ACL')
467
468
Piers Lauderd80ef022005-06-01 23:50:52 +0000469 def getannotation(self, mailbox, entry, attribute):
470 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
471 Retrieve ANNOTATIONs."""
472
473 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
474 return self._untagged_response(typ, dat, 'ANNOTATION')
475
476
Piers Lauder3fca2912002-06-17 07:07:20 +0000477 def getquota(self, root):
478 """Get the quota root's resource usage and limits.
479
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000480 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000481
482 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000483 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000484 typ, dat = self._simple_command('GETQUOTA', root)
485 return self._untagged_response(typ, dat, 'QUOTA')
486
487
488 def getquotaroot(self, mailbox):
489 """Get the list of quota roots for the named mailbox.
490
491 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000492 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000493 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000494 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
495 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000496 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000497
498
Tim Peters07e99cb2001-01-14 23:47:14 +0000499 def list(self, directory='""', pattern='*'):
500 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000501
Tim Peters07e99cb2001-01-14 23:47:14 +0000502 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000503
Tim Peters07e99cb2001-01-14 23:47:14 +0000504 'data' is list of LIST responses.
505 """
506 name = 'LIST'
507 typ, dat = self._simple_command(name, directory, pattern)
508 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000509
510
Tim Peters07e99cb2001-01-14 23:47:14 +0000511 def login(self, user, password):
512 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000513
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000515
Tim Peters07e99cb2001-01-14 23:47:14 +0000516 NB: 'password' will be quoted.
517 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000518 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
519 if typ != 'OK':
520 raise self.error(dat[-1])
521 self.state = 'AUTH'
522 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000523
524
Piers Laudere0273de2002-11-22 05:53:04 +0000525 def login_cram_md5(self, user, password):
526 """ Force use of CRAM-MD5 authentication.
527
528 (typ, [data]) = <instance>.login_cram_md5(user, password)
529 """
530 self.user, self.password = user, password
531 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
532
533
534 def _CRAM_MD5_AUTH(self, challenge):
535 """ Authobject to use with CRAM-MD5 authentication. """
536 import hmac
537 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
538
539
Tim Peters07e99cb2001-01-14 23:47:14 +0000540 def logout(self):
541 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000542
Tim Peters07e99cb2001-01-14 23:47:14 +0000543 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000544
Tim Peters07e99cb2001-01-14 23:47:14 +0000545 Returns server 'BYE' response.
546 """
547 self.state = 'LOGOUT'
548 try: typ, dat = self._simple_command('LOGOUT')
549 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000550 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000551 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000552 return 'BYE', self.untagged_responses['BYE']
553 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000554
555
Tim Peters07e99cb2001-01-14 23:47:14 +0000556 def lsub(self, directory='""', pattern='*'):
557 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000558
Tim Peters07e99cb2001-01-14 23:47:14 +0000559 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000560
Tim Peters07e99cb2001-01-14 23:47:14 +0000561 'data' are tuples of message part envelope and data.
562 """
563 name = 'LSUB'
564 typ, dat = self._simple_command(name, directory, pattern)
565 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000566
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000567 def myrights(self, mailbox):
568 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
569
570 (typ, [data]) = <instance>.myrights(mailbox)
571 """
572 typ,dat = self._simple_command('MYRIGHTS', mailbox)
573 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000574
Piers Lauder15e5d532001-07-20 10:52:06 +0000575 def namespace(self):
576 """ Returns IMAP namespaces ala rfc2342
577
578 (typ, [data, ...]) = <instance>.namespace()
579 """
580 name = 'NAMESPACE'
581 typ, dat = self._simple_command(name)
582 return self._untagged_response(typ, dat, name)
583
584
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 def noop(self):
586 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000587
Piers Laudere0273de2002-11-22 05:53:04 +0000588 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000589 """
590 if __debug__:
591 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000592 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000594
595
Tim Peters07e99cb2001-01-14 23:47:14 +0000596 def partial(self, message_num, message_part, start, length):
597 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000598
Tim Peters07e99cb2001-01-14 23:47:14 +0000599 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000600
Tim Peters07e99cb2001-01-14 23:47:14 +0000601 'data' is tuple of message part envelope and data.
602 """
603 name = 'PARTIAL'
604 typ, dat = self._simple_command(name, message_num, message_part, start, length)
605 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000606
607
Piers Laudere0273de2002-11-22 05:53:04 +0000608 def proxyauth(self, user):
609 """Assume authentication as "user".
610
611 Allows an authorised administrator to proxy into any user's
612 mailbox.
613
614 (typ, [data]) = <instance>.proxyauth(user)
615 """
616
617 name = 'PROXYAUTH'
618 return self._simple_command('PROXYAUTH', user)
619
620
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 def rename(self, oldmailbox, newmailbox):
622 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000623
Piers Laudere0273de2002-11-22 05:53:04 +0000624 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 """
626 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
628
Tim Peters07e99cb2001-01-14 23:47:14 +0000629 def search(self, charset, *criteria):
630 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000631
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000632 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000633
Tim Peters07e99cb2001-01-14 23:47:14 +0000634 'data' is space separated list of matching message numbers.
635 """
636 name = 'SEARCH'
637 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000638 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000639 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000640 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
643
Piers Lauder14f39402005-08-31 10:46:29 +0000644 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000645 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000646
Tim Peters07e99cb2001-01-14 23:47:14 +0000647 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000648
Piers Lauder14f39402005-08-31 10:46:29 +0000649 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000650
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000652
653 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
654 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000656 self.untagged_responses = {} # Flush old responses.
657 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000658 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 name = 'EXAMINE'
660 else:
661 name = 'SELECT'
662 typ, dat = self._simple_command(name, mailbox)
663 if typ != 'OK':
664 self.state = 'AUTH' # Might have been 'SELECTED'
665 return typ, dat
666 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000667 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000668 and not readonly:
669 if __debug__:
670 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000671 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000672 raise self.readonly('%s is not writable' % mailbox)
673 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000674
675
Piers Lauder15e5d532001-07-20 10:52:06 +0000676 def setacl(self, mailbox, who, what):
677 """Set a mailbox acl.
678
Piers Lauderf167dc32004-03-25 00:12:21 +0000679 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000680 """
681 return self._simple_command('SETACL', mailbox, who, what)
682
683
Piers Lauderd80ef022005-06-01 23:50:52 +0000684 def setannotation(self, *args):
685 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
686 Set ANNOTATIONs."""
687
688 typ, dat = self._simple_command('SETANNOTATION', *args)
689 return self._untagged_response(typ, dat, 'ANNOTATION')
690
691
Piers Lauder3fca2912002-06-17 07:07:20 +0000692 def setquota(self, root, limits):
693 """Set the quota root's resource limits.
694
695 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000696 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000697 typ, dat = self._simple_command('SETQUOTA', root, limits)
698 return self._untagged_response(typ, dat, 'QUOTA')
699
700
Piers Lauder15e5d532001-07-20 10:52:06 +0000701 def sort(self, sort_criteria, charset, *search_criteria):
702 """IMAP4rev1 extension SORT command.
703
704 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
705 """
706 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000707 #if not name in self.capabilities: # Let the server decide!
708 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000709 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000710 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000711 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000712 return self._untagged_response(typ, dat, name)
713
714
Tim Peters07e99cb2001-01-14 23:47:14 +0000715 def status(self, mailbox, names):
716 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000717
Tim Peters07e99cb2001-01-14 23:47:14 +0000718 (typ, [data]) = <instance>.status(mailbox, names)
719 """
720 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000721 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000722 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 typ, dat = self._simple_command(name, mailbox, names)
724 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000725
726
Tim Peters07e99cb2001-01-14 23:47:14 +0000727 def store(self, message_set, command, flags):
728 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 (typ, [data]) = <instance>.store(message_set, command, flags)
731 """
732 if (flags[0],flags[-1]) != ('(',')'):
733 flags = '(%s)' % flags # Avoid quoting the flags
734 typ, dat = self._simple_command('STORE', message_set, command, flags)
735 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
737
Tim Peters07e99cb2001-01-14 23:47:14 +0000738 def subscribe(self, mailbox):
739 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000740
Tim Peters07e99cb2001-01-14 23:47:14 +0000741 (typ, [data]) = <instance>.subscribe(mailbox)
742 """
743 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000744
745
Martin v. Löwisd8921372003-11-10 06:44:44 +0000746 def thread(self, threading_algorithm, charset, *search_criteria):
747 """IMAPrev1 extension THREAD command.
748
Mark Dickinsondb69f012009-12-24 16:06:58 +0000749 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000750 """
751 name = 'THREAD'
752 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
753 return self._untagged_response(typ, dat, name)
754
755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 def uid(self, command, *args):
757 """Execute "command arg ..." with messages identified by UID,
758 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000759
Tim Peters07e99cb2001-01-14 23:47:14 +0000760 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000761
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 Returns response appropriate to 'command'.
763 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000764 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000765 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 raise self.error("Unknown IMAP4 UID command: %s" % command)
767 if self.state not in Commands[command]:
Georg Brandl6c104f62007-03-13 18:24:40 +0000768 raise self.error("command %s illegal in state %s, "
769 "only allowed in states %s" %
770 (command, self.state,
771 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000772 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000773 typ, dat = self._simple_command(name, command, *args)
Georg Brandl004c74d2010-08-01 19:06:51 +0000774 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000775 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000776 else:
777 name = 'FETCH'
778 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000779
780
Tim Peters07e99cb2001-01-14 23:47:14 +0000781 def unsubscribe(self, mailbox):
782 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000783
Tim Peters07e99cb2001-01-14 23:47:14 +0000784 (typ, [data]) = <instance>.unsubscribe(mailbox)
785 """
786 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000787
788
Tim Peters07e99cb2001-01-14 23:47:14 +0000789 def xatom(self, name, *args):
790 """Allow simple extension commands
791 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000792
Piers Lauder15e5d532001-07-20 10:52:06 +0000793 Assumes command is legal in current state.
794
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000796
797 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000799 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000800 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000801 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000802 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000803 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000804 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000805
806
807
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000809
810
Tim Peters07e99cb2001-01-14 23:47:14 +0000811 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 if dat is None: dat = ''
814 ur = self.untagged_responses
815 if __debug__:
816 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000817 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000818 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000819 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 ur[typ].append(dat)
821 else:
822 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 def _check_bye(self):
826 bye = self.untagged_responses.get('BYE')
827 if bye:
828 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000829
830
Tim Peters07e99cb2001-01-14 23:47:14 +0000831 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000832
Tim Peters07e99cb2001-01-14 23:47:14 +0000833 if self.state not in Commands[name]:
834 self.literal = None
Georg Brandl6c104f62007-03-13 18:24:40 +0000835 raise self.error("command %s illegal in state %s, "
836 "only allowed in states %s" %
837 (name, self.state,
838 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000839
Tim Peters07e99cb2001-01-14 23:47:14 +0000840 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000841 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000843
Raymond Hettinger54f02222002-06-01 14:18:47 +0000844 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000845 and not self.is_readonly:
846 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000847
Tim Peters07e99cb2001-01-14 23:47:14 +0000848 tag = self._new_tag()
849 data = '%s %s' % (tag, name)
850 for arg in args:
851 if arg is None: continue
852 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000853
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 literal = self.literal
855 if literal is not None:
856 self.literal = None
857 if type(literal) is type(self._command):
858 literator = literal
859 else:
860 literator = None
861 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 if __debug__:
864 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000865 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000867 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000870 self.send('%s%s' % (data, CRLF))
871 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000873
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 if literal is None:
875 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 while 1:
878 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 while self._get_response():
881 if self.tagged_commands[tag]: # BAD/NO?
882 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 if literator:
887 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 if __debug__:
890 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000891 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000894 self.send(literal)
895 self.send(CRLF)
896 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000898
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 if not literator:
900 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
Tim Peters07e99cb2001-01-14 23:47:14 +0000902 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 def _command_complete(self, name, tag):
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000906 # BYE is expected after LOGOUT
907 if name != 'LOGOUT':
908 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 try:
910 typ, data = self._get_tagged_response(tag)
911 except self.abort, val:
912 raise self.abort('command: %s => %s' % (name, val))
913 except self.error, val:
914 raise self.error('command: %s => %s' % (name, val))
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000915 if name != 'LOGOUT':
916 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 if typ == 'BAD':
918 raise self.error('%s command error: %s %s' % (name, typ, data))
919 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000920
921
Tim Peters07e99cb2001-01-14 23:47:14 +0000922 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 # Read response and store.
925 #
926 # Returns None for continuation responses,
927 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 if self._match(self.tagre, resp):
934 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000935 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000937
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 typ = self.mo.group('type')
939 dat = self.mo.group('data')
940 self.tagged_commands[tag] = (typ, [dat])
941 else:
942 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000943
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 if not self._match(Untagged_response, resp):
947 if self._match(Untagged_status, resp):
948 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000949
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 if self.mo is None:
951 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 if self._match(Continuation, resp):
954 self.continuation_response = self.mo.group('data')
955 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 typ = self.mo.group('type')
960 dat = self.mo.group('data')
961 if dat is None: dat = '' # Null untagged response
962 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000963
Tim Peters07e99cb2001-01-14 23:47:14 +0000964 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000965
Tim Peters07e99cb2001-01-14 23:47:14 +0000966 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000967
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000969
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000970 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000971 if __debug__:
972 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000973 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000974 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000975
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000983
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000985
Tim Peters07e99cb2001-01-14 23:47:14 +0000986 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
989 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 if __debug__:
992 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000993 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000996
997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 while 1:
1001 result = self.tagged_commands[tag]
1002 if result is not None:
1003 del self.tagged_commands[tag]
1004 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001005
R David Murrayca00c6e2014-02-07 13:51:22 -05001006 # If we've seen a BYE at this point, the socket will be
1007 # closed, so report the BYE now.
1008
1009 self._check_bye()
1010
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 # Some have reported "unexpected response" exceptions.
1012 # Note that ignoring them here causes loops.
1013 # Instead, send me details of the unexpected response and
1014 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 try:
1017 self._get_response()
1018 except self.abort, val:
1019 if __debug__:
1020 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001021 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001023
1024
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001026
Piers Lauder15e5d532001-07-20 10:52:06 +00001027 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 if not line:
1029 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001030
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 # Protocol mandates all lines terminated by CRLF
R. David Murray93321f32009-12-09 15:15:31 +00001032 if not line.endswith('\r\n'):
1033 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001034
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 line = line[:-2]
1036 if __debug__:
1037 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001038 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001039 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001040 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001042
1043
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001045
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 # Run compiled regular expression match method on 's'.
1047 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001048
Tim Peters07e99cb2001-01-14 23:47:14 +00001049 self.mo = cre.match(s)
1050 if __debug__:
1051 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001052 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001054
1055
Tim Peters07e99cb2001-01-14 23:47:14 +00001056 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 tag = '%s%s' % (self.tagpre, self.tagnum)
1059 self.tagnum = self.tagnum + 1
1060 self.tagged_commands[tag] = None
1061 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001062
1063
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001065
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 # Must quote command args if non-alphanumeric chars present,
1067 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001068
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 if type(arg) is not type(''):
1070 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001071 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001073 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 return arg
1075 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001076
1077
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001079
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001080 arg = arg.replace('\\', '\\\\')
1081 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001082
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001084
1085
Tim Peters07e99cb2001-01-14 23:47:14 +00001086 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001087
Guido van Rossum68468eb2003-02-27 20:14:51 +00001088 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001089
1090
Tim Peters07e99cb2001-01-14 23:47:14 +00001091 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001092
Tim Peters07e99cb2001-01-14 23:47:14 +00001093 if typ == 'NO':
1094 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001095 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001096 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001097 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001098 if __debug__:
1099 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001100 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001101 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001102
1103
Piers Lauderf2d7d152002-02-22 01:15:17 +00001104 if __debug__:
1105
1106 def _mesg(self, s, secs=None):
1107 if secs is None:
1108 secs = time.time()
1109 tm = time.strftime('%M:%S', time.localtime(secs))
1110 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1111 sys.stderr.flush()
1112
1113 def _dump_ur(self, dict):
1114 # Dump untagged responses (in `dict').
1115 l = dict.items()
1116 if not l: return
1117 t = '\n\t\t'
1118 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1119 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1120
1121 def _log(self, line):
1122 # Keep log of last `_cmd_log_len' interactions for debugging.
1123 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1124 self._cmd_log_idx += 1
1125 if self._cmd_log_idx >= self._cmd_log_len:
1126 self._cmd_log_idx = 0
1127
1128 def print_log(self):
1129 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1130 i, n = self._cmd_log_idx, self._cmd_log_len
1131 while n:
1132 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001133 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001134 except:
1135 pass
1136 i += 1
1137 if i >= self._cmd_log_len:
1138 i = 0
1139 n -= 1
1140
1141
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001142
Bill Janssen426ea0a2007-08-29 22:35:05 +00001143try:
1144 import ssl
1145except ImportError:
1146 pass
1147else:
1148 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001149
Bill Janssen426ea0a2007-08-29 22:35:05 +00001150 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001151
Bill Janssen426ea0a2007-08-29 22:35:05 +00001152 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001153
Bill Janssen426ea0a2007-08-29 22:35:05 +00001154 host - host's name (default: localhost);
1155 port - port number (default: standard IMAP4 SSL port).
1156 keyfile - PEM formatted file that contains your private key (default: None);
1157 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001158
Bill Janssen426ea0a2007-08-29 22:35:05 +00001159 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001160 """
Piers Laudera4f83132002-03-08 01:53:24 +00001161
1162
Bill Janssen426ea0a2007-08-29 22:35:05 +00001163 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1164 self.keyfile = keyfile
1165 self.certfile = certfile
1166 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001167
1168
Bill Janssen426ea0a2007-08-29 22:35:05 +00001169 def open(self, host = '', port = IMAP4_SSL_PORT):
1170 """Setup connection to remote server on "host:port".
1171 (default: localhost:standard IMAP4 SSL port).
1172 This connection will be used by the routines:
1173 read, readline, send, shutdown.
1174 """
1175 self.host = host
1176 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +00001177 self.sock = socket.create_connection((host, port))
Bill Janssen98d19da2007-09-10 21:51:02 +00001178 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001179 self.file = self.sslobj.makefile('rb')
Piers Laudera4f83132002-03-08 01:53:24 +00001180
1181
Bill Janssen426ea0a2007-08-29 22:35:05 +00001182 def read(self, size):
1183 """Read 'size' bytes from remote."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001184 return self.file.read(size)
Piers Laudera4f83132002-03-08 01:53:24 +00001185
1186
Bill Janssen426ea0a2007-08-29 22:35:05 +00001187 def readline(self):
1188 """Read line from remote."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001189 return self.file.readline()
Piers Laudera4f83132002-03-08 01:53:24 +00001190
1191
Bill Janssen426ea0a2007-08-29 22:35:05 +00001192 def send(self, data):
1193 """Send data to remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001194 bytes = len(data)
1195 while bytes > 0:
1196 sent = self.sslobj.write(data)
1197 if sent == bytes:
1198 break # avoid copy
1199 data = data[sent:]
1200 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001201
1202
Bill Janssen426ea0a2007-08-29 22:35:05 +00001203 def shutdown(self):
1204 """Close I/O established in "open"."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001205 self.file.close()
Bill Janssen426ea0a2007-08-29 22:35:05 +00001206 self.sock.close()
Piers Laudera4f83132002-03-08 01:53:24 +00001207
Bill Janssen426ea0a2007-08-29 22:35:05 +00001208
1209 def socket(self):
1210 """Return socket instance used to connect to IMAP4 server.
1211
1212 socket = <instance>.socket()
1213 """
1214 return self.sock
1215
1216
1217 def ssl(self):
1218 """Return SSLObject instance used to communicate with the IMAP4 server.
1219
Bill Janssen98d19da2007-09-10 21:51:02 +00001220 ssl = ssl.wrap_socket(<instance>.socket)
Bill Janssen426ea0a2007-08-29 22:35:05 +00001221 """
1222 return self.sslobj
Piers Laudera4f83132002-03-08 01:53:24 +00001223
Thomas Woutersa6900e82007-08-30 21:54:39 +00001224 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001225
1226
Piers Laudere0273de2002-11-22 05:53:04 +00001227class IMAP4_stream(IMAP4):
1228
1229 """IMAP4 client class over a stream
1230
1231 Instantiate with: IMAP4_stream(command)
1232
Georg Brandl36f42142010-01-02 12:35:01 +00001233 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001234
1235 for more documentation see the docstring of the parent class IMAP4.
1236 """
1237
1238
1239 def __init__(self, command):
1240 self.command = command
1241 IMAP4.__init__(self)
1242
1243
1244 def open(self, host = None, port = None):
1245 """Setup a stream connection.
1246 This connection will be used by the routines:
1247 read, readline, send, shutdown.
1248 """
1249 self.host = None # For compatibility with parent class
1250 self.port = None
1251 self.sock = None
1252 self.file = None
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001253 self.process = subprocess.Popen(self.command,
1254 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1255 shell=True, close_fds=True)
1256 self.writefile = self.process.stdin
1257 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001258
1259
1260 def read(self, size):
1261 """Read 'size' bytes from remote."""
1262 return self.readfile.read(size)
1263
1264
1265 def readline(self):
1266 """Read line from remote."""
1267 return self.readfile.readline()
1268
1269
1270 def send(self, data):
1271 """Send data to remote."""
1272 self.writefile.write(data)
1273 self.writefile.flush()
1274
1275
1276 def shutdown(self):
1277 """Close I/O established in "open"."""
1278 self.readfile.close()
1279 self.writefile.close()
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001280 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001281
1282
1283
Guido van Rossumeda960a1998-06-18 14:24:28 +00001284class _Authenticator:
1285
Tim Peters07e99cb2001-01-14 23:47:14 +00001286 """Private class to provide en/decoding
1287 for base64-based authentication conversation.
1288 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001289
Tim Peters07e99cb2001-01-14 23:47:14 +00001290 def __init__(self, mechinst):
1291 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001292
Tim Peters07e99cb2001-01-14 23:47:14 +00001293 def process(self, data):
1294 ret = self.mech(self.decode(data))
1295 if ret is None:
1296 return '*' # Abort conversation
1297 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001298
Tim Peters07e99cb2001-01-14 23:47:14 +00001299 def encode(self, inp):
1300 #
1301 # Invoke binascii.b2a_base64 iteratively with
1302 # short even length buffers, strip the trailing
1303 # line feed from the result and append. "Even"
1304 # means a number that factors to both 6 and 8,
1305 # so when it gets to the end of the 8-bit input
1306 # there's no partial 6-bit output.
1307 #
1308 oup = ''
1309 while inp:
1310 if len(inp) > 48:
1311 t = inp[:48]
1312 inp = inp[48:]
1313 else:
1314 t = inp
1315 inp = ''
1316 e = binascii.b2a_base64(t)
1317 if e:
1318 oup = oup + e[:-1]
1319 return oup
1320
1321 def decode(self, inp):
1322 if not inp:
1323 return ''
1324 return binascii.a2b_base64(inp)
1325
Guido van Rossumeda960a1998-06-18 14:24:28 +00001326
1327
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001328Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001329 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001330
1331def Internaldate2tuple(resp):
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001332 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001333
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001334 Return corresponding local time. The return value is a
1335 time.struct_time instance or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001336 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Tim Peters07e99cb2001-01-14 23:47:14 +00001338 mo = InternalDate.match(resp)
1339 if not mo:
1340 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001341
Tim Peters07e99cb2001-01-14 23:47:14 +00001342 mon = Mon2num[mo.group('mon')]
1343 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001344
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001345 day = int(mo.group('day'))
1346 year = int(mo.group('year'))
1347 hour = int(mo.group('hour'))
1348 min = int(mo.group('min'))
1349 sec = int(mo.group('sec'))
1350 zoneh = int(mo.group('zoneh'))
1351 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001352
Tim Peters07e99cb2001-01-14 23:47:14 +00001353 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001354
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 zone = (zoneh*60 + zonem)*60
1356 if zonen == '-':
1357 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
Tim Peters07e99cb2001-01-14 23:47:14 +00001359 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001362
Tim Peters07e99cb2001-01-14 23:47:14 +00001363 # Following is necessary because the time module has no 'mkgmtime'.
1364 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001365
Tim Peters07e99cb2001-01-14 23:47:14 +00001366 lt = time.localtime(utc)
1367 if time.daylight and lt[-1]:
1368 zone = zone + time.altzone
1369 else:
1370 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001371
Tim Peters07e99cb2001-01-14 23:47:14 +00001372 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001373
1374
1375
1376def Int2AP(num):
1377
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001379
Tim Peters07e99cb2001-01-14 23:47:14 +00001380 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1381 num = int(abs(num))
1382 while num:
1383 num, mod = divmod(num, 16)
1384 val = AP[mod] + val
1385 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001386
1387
1388
1389def ParseFlags(resp):
1390
Tim Peters07e99cb2001-01-14 23:47:14 +00001391 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001392
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 mo = Flags.match(resp)
1394 if not mo:
1395 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001396
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001397 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001398
1399
1400def Time2Internaldate(date_time):
1401
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001402 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001403
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001404 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Ezio Melottic2077b02011-03-16 12:34:31 +02001405 date_time argument can be a number (int or float) representing
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001406 seconds since epoch (as returned by time.time()), a 9-tuple
1407 representing local time (as returned by time.localtime()), or a
1408 double-quoted string. In the last case, it is assumed to already
1409 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001410 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001411
Fred Drakedb519202002-01-05 17:17:09 +00001412 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001414 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001415 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001416 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001418 else:
1419 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001420
Tim Peters07e99cb2001-01-14 23:47:14 +00001421 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1422 if dt[0] == '0':
1423 dt = ' ' + dt[1:]
1424 if time.daylight and tt[-1]:
1425 zone = -time.altzone
1426 else:
1427 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001428 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001429
1430
1431
Guido van Rossum8c062211999-12-13 23:27:45 +00001432if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001433
Piers Laudere0273de2002-11-22 05:53:04 +00001434 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1435 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1436 # to test the IMAP4_stream class
1437
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001438 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001439
Tim Peters07e99cb2001-01-14 23:47:14 +00001440 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001441 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001442 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001443 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001444
Piers Laudere0273de2002-11-22 05:53:04 +00001445 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001446 for opt,val in optlist:
1447 if opt == '-d':
1448 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001449 elif opt == '-s':
1450 stream_command = val
1451 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001452
Tim Peters07e99cb2001-01-14 23:47:14 +00001453 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001454
Tim Peters07e99cb2001-01-14 23:47:14 +00001455 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001456
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001458 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001459
Piers Lauder47404ff2003-04-29 23:40:59 +00001460 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 +00001461 test_seq1 = (
1462 ('login', (USER, PASSWD)),
1463 ('create', ('/tmp/xxx 1',)),
1464 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1465 ('CREATE', ('/tmp/yyz 2',)),
1466 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1467 ('list', ('/tmp', 'yy*')),
1468 ('select', ('/tmp/yyz 2',)),
1469 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001470 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001471 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001472 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001473 ('expunge', ()),
1474 ('recent', ()),
1475 ('close', ()),
1476 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001477
Tim Peters07e99cb2001-01-14 23:47:14 +00001478 test_seq2 = (
1479 ('select', ()),
1480 ('response',('UIDVALIDITY',)),
1481 ('uid', ('SEARCH', 'ALL')),
1482 ('response', ('EXISTS',)),
1483 ('append', (None, None, None, test_mesg)),
1484 ('recent', ()),
1485 ('logout', ()),
1486 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001487
Tim Peters07e99cb2001-01-14 23:47:14 +00001488 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001489 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001490 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001491 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001492 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001493 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001494
Tim Peters07e99cb2001-01-14 23:47:14 +00001495 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001496 if stream_command:
1497 M = IMAP4_stream(stream_command)
1498 else:
1499 M = IMAP4(host)
1500 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001501 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001502 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001503 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001504
Tim Peters07e99cb2001-01-14 23:47:14 +00001505 for cmd,args in test_seq1:
1506 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001507
Tim Peters07e99cb2001-01-14 23:47:14 +00001508 for ml in run('list', ('/tmp/', 'yy%')):
1509 mo = re.match(r'.*"([^"]+)"$', ml)
1510 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001511 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001512 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001513
Tim Peters07e99cb2001-01-14 23:47:14 +00001514 for cmd,args in test_seq2:
1515 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001516
Tim Peters07e99cb2001-01-14 23:47:14 +00001517 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1518 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001519
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001520 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001521 if not uid: continue
1522 run('uid', ('FETCH', '%s' % uid[-1],
1523 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001524
Tim Peters07e99cb2001-01-14 23:47:14 +00001525 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001526
Tim Peters07e99cb2001-01-14 23:47:14 +00001527 except:
1528 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001529
Tim Peters07e99cb2001-01-14 23:47:14 +00001530 if not Debug:
1531 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001532If you would like to see debugging output,
1533try: %s -d5
1534''' % sys.argv[0]
1535
Tim Peters07e99cb2001-01-14 23:47:14 +00001536 raise