blob: 10ff340ef96f5d8d2bd44f692ad1be1d90548a82 [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)
40# don't specify a line length. RFC 2683 however suggests limiting client
41# command lines to 1000 octets and server command lines to 8000 octets.
42# We have selected 10000 for some extra margin and since that is supposedly
43# also what UW and Panda IMAP does.
44_MAXLINE = 10000
45
46
Tim Peters07e99cb2001-01-14 23:47:14 +000047# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000048
49Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000050 # name valid states
51 'APPEND': ('AUTH', 'SELECTED'),
52 'AUTHENTICATE': ('NONAUTH',),
53 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
54 'CHECK': ('SELECTED',),
55 'CLOSE': ('SELECTED',),
56 'COPY': ('SELECTED',),
57 'CREATE': ('AUTH', 'SELECTED'),
58 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000059 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000060 'EXAMINE': ('AUTH', 'SELECTED'),
61 'EXPUNGE': ('SELECTED',),
62 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000063 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000064 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000065 'GETQUOTA': ('AUTH', 'SELECTED'),
66 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000067 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000068 'LIST': ('AUTH', 'SELECTED'),
69 'LOGIN': ('NONAUTH',),
70 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
71 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000072 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000073 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000074 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000075 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000076 'RENAME': ('AUTH', 'SELECTED'),
77 'SEARCH': ('SELECTED',),
78 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000079 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000080 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000081 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000082 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000083 'STATUS': ('AUTH', 'SELECTED'),
84 'STORE': ('SELECTED',),
85 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000086 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000087 'UID': ('SELECTED',),
88 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
89 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000090
Tim Peters07e99cb2001-01-14 23:47:14 +000091# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000092
Guido van Rossumeda960a1998-06-18 14:24:28 +000093Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000094Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
95InternalDate = re.compile(r'.*INTERNALDATE "'
Piers Lauder8659d902005-03-02 09:13:45 +000096 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 +000097 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
98 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
99 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +0000100Literal = re.compile(r'.*{(?P<size>\d+)}$')
Piers Lauder533366b2003-04-29 23:58:08 +0000101MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000103Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
105
106
107
108class IMAP4:
109
Tim Peters07e99cb2001-01-14 23:47:14 +0000110 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000111
Tim Peters07e99cb2001-01-14 23:47:14 +0000112 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000113
Tim Peters07e99cb2001-01-14 23:47:14 +0000114 host - host's name (default: localhost);
115 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000116
Tim Peters07e99cb2001-01-14 23:47:14 +0000117 All IMAP4rev1 commands are supported by methods of the same
118 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000119
Tim Peters07e99cb2001-01-14 23:47:14 +0000120 All arguments to commands are converted to strings, except for
121 AUTHENTICATE, and the last argument to APPEND which is passed as
122 an IMAP4 literal. If necessary (the string contains any
123 non-printing characters or white-space and isn't enclosed with
124 either parentheses or double quotes) each string is quoted.
125 However, the 'password' argument to the LOGIN command is always
126 quoted. If you want to avoid having an argument string quoted
127 (eg: the 'flags' argument to STORE) then enclose the string in
128 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000129
Tim Peters07e99cb2001-01-14 23:47:14 +0000130 Each command returns a tuple: (type, [data, ...]) where 'type'
131 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000132 tagged response, or untagged results from command. Each 'data'
133 is either a string, or a tuple. If a tuple, then the first part
134 is the header of the response, and the second part contains
135 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000136
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 Errors raise the exception class <instance>.error("<reason>").
138 IMAP4 server errors raise <instance>.abort("<reason>"),
139 which is a sub-class of 'error'. Mailbox status changes
140 from READ-WRITE to READ-ONLY raise the exception class
141 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000142
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 "error" exceptions imply a program error.
144 "abort" exceptions imply the connection should be reset, and
145 the command re-tried.
146 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000147
Piers Lauderd80ef022005-06-01 23:50:52 +0000148 Note: to use this module, you must read the RFCs pertaining to the
149 IMAP4 protocol, as the semantics of the arguments to each IMAP4
150 command are left to the invoker, not to mention the results. Also,
151 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 class error(Exception): pass # Logical errors - debug required
155 class abort(error): pass # Service errors - close and retry
156 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000157
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000161 self.debug = Debug
162 self.state = 'LOGOUT'
163 self.literal = None # A literal argument to a command
164 self.tagged_commands = {} # Tagged commands awaiting response
165 self.untagged_responses = {} # {typ: [data, ...], ...}
166 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000167 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000169
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000173
Tim Peters07e99cb2001-01-14 23:47:14 +0000174 # Create unique tag for this session,
175 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000176
Piers Lauder2dfc1682005-07-05 04:20:07 +0000177 self.tagpre = Int2AP(random.randint(4096, 65535))
Tim Peters07e99cb2001-01-14 23:47:14 +0000178 self.tagre = re.compile(r'(?P<tag>'
179 + self.tagpre
180 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000181
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 # Get server welcome message,
183 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000184
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000186 self._cmd_log_len = 10
187 self._cmd_log_idx = 0
188 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000190 self._mesg('imaplib version %s' % __version__)
191 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000192
Tim Peters07e99cb2001-01-14 23:47:14 +0000193 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000194 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000196 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000197 self.state = 'NONAUTH'
198 else:
199 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000200
Piers Lauderd80ef022005-06-01 23:50:52 +0000201 typ, dat = self.capability()
202 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000203 raise self.error('no CAPABILITY response from server')
Piers Lauderd80ef022005-06-01 23:50:52 +0000204 self.capabilities = tuple(dat[-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000205
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 if __debug__:
207 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000208 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000209
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 for version in AllowedVersions:
211 if not version in self.capabilities:
212 continue
213 self.PROTOCOL_VERSION = version
214 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000215
Tim Peters07e99cb2001-01-14 23:47:14 +0000216 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000217
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000218
Tim Peters07e99cb2001-01-14 23:47:14 +0000219 def __getattr__(self, attr):
220 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000221 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000222 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000224
225
226
Piers Lauder15e5d532001-07-20 10:52:06 +0000227 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000228
229
Piers Lauderf97b2d72002-06-05 22:31:57 +0000230 def open(self, host = '', port = IMAP4_PORT):
231 """Setup connection to remote server on "host:port"
232 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000233 This connection will be used by the routines:
234 read, readline, send, shutdown.
235 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000236 self.host = host
237 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +0000238 self.sock = socket.create_connection((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000239 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000240
241
Piers Lauder15e5d532001-07-20 10:52:06 +0000242 def read(self, size):
243 """Read 'size' bytes from remote."""
244 return self.file.read(size)
245
246
247 def readline(self):
248 """Read line from remote."""
R David Murray020d7c32014-01-03 13:59:22 -0500249 line = self.file.readline(_MAXLINE + 1)
250 if len(line) > _MAXLINE:
251 raise self.error("got more than %d bytes" % _MAXLINE)
252 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000253
254
255 def send(self, data):
256 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000257 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000258
Piers Lauderf2d7d152002-02-22 01:15:17 +0000259
Piers Lauder15e5d532001-07-20 10:52:06 +0000260 def shutdown(self):
261 """Close I/O established in "open"."""
262 self.file.close()
Antoine Pitrouae933892010-11-10 09:02:33 +0000263 try:
264 self.sock.shutdown(socket.SHUT_RDWR)
265 except socket.error as e:
266 # The server might already have closed the connection
267 if e.errno != errno.ENOTCONN:
268 raise
269 finally:
270 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000271
272
273 def socket(self):
274 """Return socket instance used to connect to IMAP4 server.
275
276 socket = <instance>.socket()
277 """
278 return self.sock
279
280
281
282 # Utility methods
283
284
Tim Peters07e99cb2001-01-14 23:47:14 +0000285 def recent(self):
286 """Return most recent 'RECENT' responses if any exist,
287 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000288
Tim Peters07e99cb2001-01-14 23:47:14 +0000289 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000290
Tim Peters07e99cb2001-01-14 23:47:14 +0000291 'data' is None if no new messages,
292 else list of RECENT responses, most recent last.
293 """
294 name = 'RECENT'
295 typ, dat = self._untagged_response('OK', [None], name)
296 if dat[-1]:
297 return typ, dat
298 typ, dat = self.noop() # Prod server for response
299 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000300
301
Tim Peters07e99cb2001-01-14 23:47:14 +0000302 def response(self, code):
303 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000304
Tim Peters07e99cb2001-01-14 23:47:14 +0000305 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000306
Tim Peters07e99cb2001-01-14 23:47:14 +0000307 (code, [data]) = <instance>.response(code)
308 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000309 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000310
311
Guido van Rossum26367a01998-09-28 15:34:46 +0000312
Tim Peters07e99cb2001-01-14 23:47:14 +0000313 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000314
315
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 def append(self, mailbox, flags, date_time, message):
317 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000318
Tim Peters07e99cb2001-01-14 23:47:14 +0000319 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000320
Tim Peters07e99cb2001-01-14 23:47:14 +0000321 All args except `message' can be None.
322 """
323 name = 'APPEND'
324 if not mailbox:
325 mailbox = 'INBOX'
326 if flags:
327 if (flags[0],flags[-1]) != ('(',')'):
328 flags = '(%s)' % flags
329 else:
330 flags = None
331 if date_time:
332 date_time = Time2Internaldate(date_time)
333 else:
334 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000335 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000336 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000337
338
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 def authenticate(self, mechanism, authobject):
340 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000341
Tim Peters07e99cb2001-01-14 23:47:14 +0000342 'mechanism' specifies which authentication mechanism is to
343 be used - it must appear in <instance>.capabilities in the
344 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000345
Tim Peters07e99cb2001-01-14 23:47:14 +0000346 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000347
Tim Peters07e99cb2001-01-14 23:47:14 +0000348 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000349
Tim Peters07e99cb2001-01-14 23:47:14 +0000350 It will be called to process server continuation responses.
351 It should return data that will be encoded and sent to server.
352 It should return None if the client abort response '*' should
353 be sent instead.
354 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000355 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000356 # XXX: shouldn't this code be removed, not commented out?
357 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000358 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000359 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000360 self.literal = _Authenticator(authobject).process
361 typ, dat = self._simple_command('AUTHENTICATE', mech)
362 if typ != 'OK':
363 raise self.error(dat[-1])
364 self.state = 'AUTH'
365 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000366
367
Piers Lauderd80ef022005-06-01 23:50:52 +0000368 def capability(self):
369 """(typ, [data]) = <instance>.capability()
370 Fetch capabilities list from server."""
371
372 name = 'CAPABILITY'
373 typ, dat = self._simple_command(name)
374 return self._untagged_response(typ, dat, name)
375
376
Tim Peters07e99cb2001-01-14 23:47:14 +0000377 def check(self):
378 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 (typ, [data]) = <instance>.check()
381 """
382 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000383
384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 def close(self):
386 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 Deleted messages are removed from writable mailbox.
389 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000390
Tim Peters07e99cb2001-01-14 23:47:14 +0000391 (typ, [data]) = <instance>.close()
392 """
393 try:
394 typ, dat = self._simple_command('CLOSE')
395 finally:
396 self.state = 'AUTH'
397 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000398
399
Tim Peters07e99cb2001-01-14 23:47:14 +0000400 def copy(self, message_set, new_mailbox):
401 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000402
Tim Peters07e99cb2001-01-14 23:47:14 +0000403 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
404 """
405 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000406
407
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 def create(self, mailbox):
409 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 (typ, [data]) = <instance>.create(mailbox)
412 """
413 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000414
415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 def delete(self, mailbox):
417 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 (typ, [data]) = <instance>.delete(mailbox)
420 """
421 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000423 def deleteacl(self, mailbox, who):
424 """Delete the ACLs (remove any rights) set for who on mailbox.
425
426 (typ, [data]) = <instance>.deleteacl(mailbox, who)
427 """
428 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 def expunge(self):
431 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000432
Tim Peters07e99cb2001-01-14 23:47:14 +0000433 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000434
Tim Peters07e99cb2001-01-14 23:47:14 +0000435 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000436
Tim Peters07e99cb2001-01-14 23:47:14 +0000437 'data' is list of 'EXPUNGE'd message numbers in order received.
438 """
439 name = 'EXPUNGE'
440 typ, dat = self._simple_command(name)
441 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000442
443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 def fetch(self, message_set, message_parts):
445 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000448
Tim Peters07e99cb2001-01-14 23:47:14 +0000449 'message_parts' should be a string of selected parts
450 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000451
Tim Peters07e99cb2001-01-14 23:47:14 +0000452 'data' are tuples of message part envelope and data.
453 """
454 name = 'FETCH'
455 typ, dat = self._simple_command(name, message_set, message_parts)
456 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000457
458
Piers Lauder15e5d532001-07-20 10:52:06 +0000459 def getacl(self, mailbox):
460 """Get the ACLs for a mailbox.
461
462 (typ, [data]) = <instance>.getacl(mailbox)
463 """
464 typ, dat = self._simple_command('GETACL', mailbox)
465 return self._untagged_response(typ, dat, 'ACL')
466
467
Piers Lauderd80ef022005-06-01 23:50:52 +0000468 def getannotation(self, mailbox, entry, attribute):
469 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
470 Retrieve ANNOTATIONs."""
471
472 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
473 return self._untagged_response(typ, dat, 'ANNOTATION')
474
475
Piers Lauder3fca2912002-06-17 07:07:20 +0000476 def getquota(self, root):
477 """Get the quota root's resource usage and limits.
478
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000479 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000480
481 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000482 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000483 typ, dat = self._simple_command('GETQUOTA', root)
484 return self._untagged_response(typ, dat, 'QUOTA')
485
486
487 def getquotaroot(self, mailbox):
488 """Get the list of quota roots for the named mailbox.
489
490 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000491 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000492 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000493 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
494 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000495 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000496
497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 def list(self, directory='""', pattern='*'):
499 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000500
Tim Peters07e99cb2001-01-14 23:47:14 +0000501 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000502
Tim Peters07e99cb2001-01-14 23:47:14 +0000503 'data' is list of LIST responses.
504 """
505 name = 'LIST'
506 typ, dat = self._simple_command(name, directory, pattern)
507 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000508
509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 def login(self, user, password):
511 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000512
Tim Peters07e99cb2001-01-14 23:47:14 +0000513 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000514
Tim Peters07e99cb2001-01-14 23:47:14 +0000515 NB: 'password' will be quoted.
516 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000517 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
518 if typ != 'OK':
519 raise self.error(dat[-1])
520 self.state = 'AUTH'
521 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000522
523
Piers Laudere0273de2002-11-22 05:53:04 +0000524 def login_cram_md5(self, user, password):
525 """ Force use of CRAM-MD5 authentication.
526
527 (typ, [data]) = <instance>.login_cram_md5(user, password)
528 """
529 self.user, self.password = user, password
530 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
531
532
533 def _CRAM_MD5_AUTH(self, challenge):
534 """ Authobject to use with CRAM-MD5 authentication. """
535 import hmac
536 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
537
538
Tim Peters07e99cb2001-01-14 23:47:14 +0000539 def logout(self):
540 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000541
Tim Peters07e99cb2001-01-14 23:47:14 +0000542 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000543
Tim Peters07e99cb2001-01-14 23:47:14 +0000544 Returns server 'BYE' response.
545 """
546 self.state = 'LOGOUT'
547 try: typ, dat = self._simple_command('LOGOUT')
548 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000549 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000550 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000551 return 'BYE', self.untagged_responses['BYE']
552 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000553
554
Tim Peters07e99cb2001-01-14 23:47:14 +0000555 def lsub(self, directory='""', pattern='*'):
556 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000557
Tim Peters07e99cb2001-01-14 23:47:14 +0000558 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000559
Tim Peters07e99cb2001-01-14 23:47:14 +0000560 'data' are tuples of message part envelope and data.
561 """
562 name = 'LSUB'
563 typ, dat = self._simple_command(name, directory, pattern)
564 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000565
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000566 def myrights(self, mailbox):
567 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
568
569 (typ, [data]) = <instance>.myrights(mailbox)
570 """
571 typ,dat = self._simple_command('MYRIGHTS', mailbox)
572 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000573
Piers Lauder15e5d532001-07-20 10:52:06 +0000574 def namespace(self):
575 """ Returns IMAP namespaces ala rfc2342
576
577 (typ, [data, ...]) = <instance>.namespace()
578 """
579 name = 'NAMESPACE'
580 typ, dat = self._simple_command(name)
581 return self._untagged_response(typ, dat, name)
582
583
Tim Peters07e99cb2001-01-14 23:47:14 +0000584 def noop(self):
585 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000586
Piers Laudere0273de2002-11-22 05:53:04 +0000587 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 """
589 if __debug__:
590 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000591 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000593
594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 def partial(self, message_num, message_part, start, length):
596 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000597
Tim Peters07e99cb2001-01-14 23:47:14 +0000598 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000599
Tim Peters07e99cb2001-01-14 23:47:14 +0000600 'data' is tuple of message part envelope and data.
601 """
602 name = 'PARTIAL'
603 typ, dat = self._simple_command(name, message_num, message_part, start, length)
604 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000605
606
Piers Laudere0273de2002-11-22 05:53:04 +0000607 def proxyauth(self, user):
608 """Assume authentication as "user".
609
610 Allows an authorised administrator to proxy into any user's
611 mailbox.
612
613 (typ, [data]) = <instance>.proxyauth(user)
614 """
615
616 name = 'PROXYAUTH'
617 return self._simple_command('PROXYAUTH', user)
618
619
Tim Peters07e99cb2001-01-14 23:47:14 +0000620 def rename(self, oldmailbox, newmailbox):
621 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000622
Piers Laudere0273de2002-11-22 05:53:04 +0000623 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000624 """
625 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000626
627
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 def search(self, charset, *criteria):
629 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000630
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000631 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000632
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 'data' is space separated list of matching message numbers.
634 """
635 name = 'SEARCH'
636 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000637 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000638 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000639 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000640 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000641
642
Piers Lauder14f39402005-08-31 10:46:29 +0000643 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000644 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000645
Tim Peters07e99cb2001-01-14 23:47:14 +0000646 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000647
Piers Lauder14f39402005-08-31 10:46:29 +0000648 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000649
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000651
652 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
653 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 self.untagged_responses = {} # Flush old responses.
656 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000657 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000658 name = 'EXAMINE'
659 else:
660 name = 'SELECT'
661 typ, dat = self._simple_command(name, mailbox)
662 if typ != 'OK':
663 self.state = 'AUTH' # Might have been 'SELECTED'
664 return typ, dat
665 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000666 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000667 and not readonly:
668 if __debug__:
669 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000670 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000671 raise self.readonly('%s is not writable' % mailbox)
672 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000673
674
Piers Lauder15e5d532001-07-20 10:52:06 +0000675 def setacl(self, mailbox, who, what):
676 """Set a mailbox acl.
677
Piers Lauderf167dc32004-03-25 00:12:21 +0000678 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000679 """
680 return self._simple_command('SETACL', mailbox, who, what)
681
682
Piers Lauderd80ef022005-06-01 23:50:52 +0000683 def setannotation(self, *args):
684 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
685 Set ANNOTATIONs."""
686
687 typ, dat = self._simple_command('SETANNOTATION', *args)
688 return self._untagged_response(typ, dat, 'ANNOTATION')
689
690
Piers Lauder3fca2912002-06-17 07:07:20 +0000691 def setquota(self, root, limits):
692 """Set the quota root's resource limits.
693
694 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000695 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000696 typ, dat = self._simple_command('SETQUOTA', root, limits)
697 return self._untagged_response(typ, dat, 'QUOTA')
698
699
Piers Lauder15e5d532001-07-20 10:52:06 +0000700 def sort(self, sort_criteria, charset, *search_criteria):
701 """IMAP4rev1 extension SORT command.
702
703 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
704 """
705 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000706 #if not name in self.capabilities: # Let the server decide!
707 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000708 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000709 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000710 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000711 return self._untagged_response(typ, dat, name)
712
713
Tim Peters07e99cb2001-01-14 23:47:14 +0000714 def status(self, mailbox, names):
715 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000716
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 (typ, [data]) = <instance>.status(mailbox, names)
718 """
719 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000720 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000721 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 typ, dat = self._simple_command(name, mailbox, names)
723 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000724
725
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 def store(self, message_set, command, flags):
727 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000728
Tim Peters07e99cb2001-01-14 23:47:14 +0000729 (typ, [data]) = <instance>.store(message_set, command, flags)
730 """
731 if (flags[0],flags[-1]) != ('(',')'):
732 flags = '(%s)' % flags # Avoid quoting the flags
733 typ, dat = self._simple_command('STORE', message_set, command, flags)
734 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000735
736
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 def subscribe(self, mailbox):
738 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000739
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 (typ, [data]) = <instance>.subscribe(mailbox)
741 """
742 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000743
744
Martin v. Löwisd8921372003-11-10 06:44:44 +0000745 def thread(self, threading_algorithm, charset, *search_criteria):
746 """IMAPrev1 extension THREAD command.
747
Mark Dickinsondb69f012009-12-24 16:06:58 +0000748 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000749 """
750 name = 'THREAD'
751 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
752 return self._untagged_response(typ, dat, name)
753
754
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 def uid(self, command, *args):
756 """Execute "command arg ..." with messages identified by UID,
757 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000758
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000760
Tim Peters07e99cb2001-01-14 23:47:14 +0000761 Returns response appropriate to 'command'.
762 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000763 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000764 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000765 raise self.error("Unknown IMAP4 UID command: %s" % command)
766 if self.state not in Commands[command]:
Georg Brandl6c104f62007-03-13 18:24:40 +0000767 raise self.error("command %s illegal in state %s, "
768 "only allowed in states %s" %
769 (command, self.state,
770 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000771 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000772 typ, dat = self._simple_command(name, command, *args)
Georg Brandl004c74d2010-08-01 19:06:51 +0000773 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000774 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 else:
776 name = 'FETCH'
777 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000778
779
Tim Peters07e99cb2001-01-14 23:47:14 +0000780 def unsubscribe(self, mailbox):
781 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000782
Tim Peters07e99cb2001-01-14 23:47:14 +0000783 (typ, [data]) = <instance>.unsubscribe(mailbox)
784 """
785 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000786
787
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 def xatom(self, name, *args):
789 """Allow simple extension commands
790 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000791
Piers Lauder15e5d532001-07-20 10:52:06 +0000792 Assumes command is legal in current state.
793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000795
796 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000797 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000798 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000799 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000800 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000801 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000802 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000803 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000804
805
806
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000808
809
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000811
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 if dat is None: dat = ''
813 ur = self.untagged_responses
814 if __debug__:
815 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000816 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000818 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 ur[typ].append(dat)
820 else:
821 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000822
823
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 def _check_bye(self):
825 bye = self.untagged_responses.get('BYE')
826 if bye:
827 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000828
829
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 if self.state not in Commands[name]:
833 self.literal = None
Georg Brandl6c104f62007-03-13 18:24:40 +0000834 raise self.error("command %s illegal in state %s, "
835 "only allowed in states %s" %
836 (name, self.state,
837 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000840 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000841 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000842
Raymond Hettinger54f02222002-06-01 14:18:47 +0000843 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 and not self.is_readonly:
845 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000846
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 tag = self._new_tag()
848 data = '%s %s' % (tag, name)
849 for arg in args:
850 if arg is None: continue
851 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000852
Tim Peters07e99cb2001-01-14 23:47:14 +0000853 literal = self.literal
854 if literal is not None:
855 self.literal = None
856 if type(literal) is type(self._command):
857 literator = literal
858 else:
859 literator = None
860 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000861
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 if __debug__:
863 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000864 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000865 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000866 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000869 self.send('%s%s' % (data, CRLF))
870 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000872
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 if literal is None:
874 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 while 1:
877 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 while self._get_response():
880 if self.tagged_commands[tag]: # BAD/NO?
881 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 if literator:
886 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000887
Tim Peters07e99cb2001-01-14 23:47:14 +0000888 if __debug__:
889 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000890 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000891
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000893 self.send(literal)
894 self.send(CRLF)
895 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000897
Tim Peters07e99cb2001-01-14 23:47:14 +0000898 if not literator:
899 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000902
903
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 def _command_complete(self, name, tag):
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000905 # BYE is expected after LOGOUT
906 if name != 'LOGOUT':
907 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 try:
909 typ, data = self._get_tagged_response(tag)
910 except self.abort, val:
911 raise self.abort('command: %s => %s' % (name, val))
912 except self.error, val:
913 raise self.error('command: %s => %s' % (name, val))
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000914 if name != 'LOGOUT':
915 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 if typ == 'BAD':
917 raise self.error('%s command error: %s %s' % (name, typ, data))
918 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000919
920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 # Read response and store.
924 #
925 # Returns None for continuation responses,
926 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 if self._match(self.tagre, resp):
933 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000934 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 typ = self.mo.group('type')
938 dat = self.mo.group('data')
939 self.tagged_commands[tag] = (typ, [dat])
940 else:
941 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 if not self._match(Untagged_response, resp):
946 if self._match(Untagged_status, resp):
947 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 if self.mo is None:
950 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000951
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 if self._match(Continuation, resp):
953 self.continuation_response = self.mo.group('data')
954 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000957
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 typ = self.mo.group('type')
959 dat = self.mo.group('data')
960 if dat is None: dat = '' # Null untagged response
961 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000969 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 if __debug__:
971 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000972 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000973 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
988 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 if __debug__:
991 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000992 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000995
996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
Tim Peters07e99cb2001-01-14 23:47:14 +0000999 while 1:
1000 result = self.tagged_commands[tag]
1001 if result is not None:
1002 del self.tagged_commands[tag]
1003 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001004
R David Murrayca00c6e2014-02-07 13:51:22 -05001005 # If we've seen a BYE at this point, the socket will be
1006 # closed, so report the BYE now.
1007
1008 self._check_bye()
1009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 # Some have reported "unexpected response" exceptions.
1011 # Note that ignoring them here causes loops.
1012 # Instead, send me details of the unexpected response and
1013 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001014
Tim Peters07e99cb2001-01-14 23:47:14 +00001015 try:
1016 self._get_response()
1017 except self.abort, val:
1018 if __debug__:
1019 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001020 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001022
1023
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Piers Lauder15e5d532001-07-20 10:52:06 +00001026 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001027 if not line:
1028 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 # Protocol mandates all lines terminated by CRLF
R. David Murray93321f32009-12-09 15:15:31 +00001031 if not line.endswith('\r\n'):
1032 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 line = line[:-2]
1035 if __debug__:
1036 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001037 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001039 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001041
1042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
Tim Peters07e99cb2001-01-14 23:47:14 +00001045 # Run compiled regular expression match method on 's'.
1046 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 self.mo = cre.match(s)
1049 if __debug__:
1050 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001051 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001052 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001053
1054
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001056
Tim Peters07e99cb2001-01-14 23:47:14 +00001057 tag = '%s%s' % (self.tagpre, self.tagnum)
1058 self.tagnum = self.tagnum + 1
1059 self.tagged_commands[tag] = None
1060 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001061
1062
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001064
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 # Must quote command args if non-alphanumeric chars present,
1066 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 if type(arg) is not type(''):
1069 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001070 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001071 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001072 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001073 return arg
1074 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001075
1076
Tim Peters07e99cb2001-01-14 23:47:14 +00001077 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001078
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001079 arg = arg.replace('\\', '\\\\')
1080 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001081
Tim Peters07e99cb2001-01-14 23:47:14 +00001082 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001083
1084
Tim Peters07e99cb2001-01-14 23:47:14 +00001085 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001086
Guido van Rossum68468eb2003-02-27 20:14:51 +00001087 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001088
1089
Tim Peters07e99cb2001-01-14 23:47:14 +00001090 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 if typ == 'NO':
1093 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001094 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001095 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001096 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001097 if __debug__:
1098 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001099 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001100 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001101
1102
Piers Lauderf2d7d152002-02-22 01:15:17 +00001103 if __debug__:
1104
1105 def _mesg(self, s, secs=None):
1106 if secs is None:
1107 secs = time.time()
1108 tm = time.strftime('%M:%S', time.localtime(secs))
1109 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1110 sys.stderr.flush()
1111
1112 def _dump_ur(self, dict):
1113 # Dump untagged responses (in `dict').
1114 l = dict.items()
1115 if not l: return
1116 t = '\n\t\t'
1117 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1118 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1119
1120 def _log(self, line):
1121 # Keep log of last `_cmd_log_len' interactions for debugging.
1122 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1123 self._cmd_log_idx += 1
1124 if self._cmd_log_idx >= self._cmd_log_len:
1125 self._cmd_log_idx = 0
1126
1127 def print_log(self):
1128 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1129 i, n = self._cmd_log_idx, self._cmd_log_len
1130 while n:
1131 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001132 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001133 except:
1134 pass
1135 i += 1
1136 if i >= self._cmd_log_len:
1137 i = 0
1138 n -= 1
1139
1140
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001141
Bill Janssen426ea0a2007-08-29 22:35:05 +00001142try:
1143 import ssl
1144except ImportError:
1145 pass
1146else:
1147 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001148
Bill Janssen426ea0a2007-08-29 22:35:05 +00001149 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001150
Bill Janssen426ea0a2007-08-29 22:35:05 +00001151 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001152
Bill Janssen426ea0a2007-08-29 22:35:05 +00001153 host - host's name (default: localhost);
1154 port - port number (default: standard IMAP4 SSL port).
1155 keyfile - PEM formatted file that contains your private key (default: None);
1156 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001157
Bill Janssen426ea0a2007-08-29 22:35:05 +00001158 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001159 """
Piers Laudera4f83132002-03-08 01:53:24 +00001160
1161
Bill Janssen426ea0a2007-08-29 22:35:05 +00001162 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1163 self.keyfile = keyfile
1164 self.certfile = certfile
1165 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001166
1167
Bill Janssen426ea0a2007-08-29 22:35:05 +00001168 def open(self, host = '', port = IMAP4_SSL_PORT):
1169 """Setup connection to remote server on "host:port".
1170 (default: localhost:standard IMAP4 SSL port).
1171 This connection will be used by the routines:
1172 read, readline, send, shutdown.
1173 """
1174 self.host = host
1175 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +00001176 self.sock = socket.create_connection((host, port))
Bill Janssen98d19da2007-09-10 21:51:02 +00001177 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001178 self.file = self.sslobj.makefile('rb')
Piers Laudera4f83132002-03-08 01:53:24 +00001179
1180
Bill Janssen426ea0a2007-08-29 22:35:05 +00001181 def read(self, size):
1182 """Read 'size' bytes from remote."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001183 return self.file.read(size)
Piers Laudera4f83132002-03-08 01:53:24 +00001184
1185
Bill Janssen426ea0a2007-08-29 22:35:05 +00001186 def readline(self):
1187 """Read line from remote."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001188 return self.file.readline()
Piers Laudera4f83132002-03-08 01:53:24 +00001189
1190
Bill Janssen426ea0a2007-08-29 22:35:05 +00001191 def send(self, data):
1192 """Send data to remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001193 bytes = len(data)
1194 while bytes > 0:
1195 sent = self.sslobj.write(data)
1196 if sent == bytes:
1197 break # avoid copy
1198 data = data[sent:]
1199 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001200
1201
Bill Janssen426ea0a2007-08-29 22:35:05 +00001202 def shutdown(self):
1203 """Close I/O established in "open"."""
Charles-François Natalib94e0cd2011-05-24 23:10:54 +02001204 self.file.close()
Bill Janssen426ea0a2007-08-29 22:35:05 +00001205 self.sock.close()
Piers Laudera4f83132002-03-08 01:53:24 +00001206
Bill Janssen426ea0a2007-08-29 22:35:05 +00001207
1208 def socket(self):
1209 """Return socket instance used to connect to IMAP4 server.
1210
1211 socket = <instance>.socket()
1212 """
1213 return self.sock
1214
1215
1216 def ssl(self):
1217 """Return SSLObject instance used to communicate with the IMAP4 server.
1218
Bill Janssen98d19da2007-09-10 21:51:02 +00001219 ssl = ssl.wrap_socket(<instance>.socket)
Bill Janssen426ea0a2007-08-29 22:35:05 +00001220 """
1221 return self.sslobj
Piers Laudera4f83132002-03-08 01:53:24 +00001222
Thomas Woutersa6900e82007-08-30 21:54:39 +00001223 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001224
1225
Piers Laudere0273de2002-11-22 05:53:04 +00001226class IMAP4_stream(IMAP4):
1227
1228 """IMAP4 client class over a stream
1229
1230 Instantiate with: IMAP4_stream(command)
1231
Georg Brandl36f42142010-01-02 12:35:01 +00001232 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001233
1234 for more documentation see the docstring of the parent class IMAP4.
1235 """
1236
1237
1238 def __init__(self, command):
1239 self.command = command
1240 IMAP4.__init__(self)
1241
1242
1243 def open(self, host = None, port = None):
1244 """Setup a stream connection.
1245 This connection will be used by the routines:
1246 read, readline, send, shutdown.
1247 """
1248 self.host = None # For compatibility with parent class
1249 self.port = None
1250 self.sock = None
1251 self.file = None
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001252 self.process = subprocess.Popen(self.command,
1253 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1254 shell=True, close_fds=True)
1255 self.writefile = self.process.stdin
1256 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001257
1258
1259 def read(self, size):
1260 """Read 'size' bytes from remote."""
1261 return self.readfile.read(size)
1262
1263
1264 def readline(self):
1265 """Read line from remote."""
1266 return self.readfile.readline()
1267
1268
1269 def send(self, data):
1270 """Send data to remote."""
1271 self.writefile.write(data)
1272 self.writefile.flush()
1273
1274
1275 def shutdown(self):
1276 """Close I/O established in "open"."""
1277 self.readfile.close()
1278 self.writefile.close()
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001279 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001280
1281
1282
Guido van Rossumeda960a1998-06-18 14:24:28 +00001283class _Authenticator:
1284
Tim Peters07e99cb2001-01-14 23:47:14 +00001285 """Private class to provide en/decoding
1286 for base64-based authentication conversation.
1287 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001288
Tim Peters07e99cb2001-01-14 23:47:14 +00001289 def __init__(self, mechinst):
1290 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001291
Tim Peters07e99cb2001-01-14 23:47:14 +00001292 def process(self, data):
1293 ret = self.mech(self.decode(data))
1294 if ret is None:
1295 return '*' # Abort conversation
1296 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001297
Tim Peters07e99cb2001-01-14 23:47:14 +00001298 def encode(self, inp):
1299 #
1300 # Invoke binascii.b2a_base64 iteratively with
1301 # short even length buffers, strip the trailing
1302 # line feed from the result and append. "Even"
1303 # means a number that factors to both 6 and 8,
1304 # so when it gets to the end of the 8-bit input
1305 # there's no partial 6-bit output.
1306 #
1307 oup = ''
1308 while inp:
1309 if len(inp) > 48:
1310 t = inp[:48]
1311 inp = inp[48:]
1312 else:
1313 t = inp
1314 inp = ''
1315 e = binascii.b2a_base64(t)
1316 if e:
1317 oup = oup + e[:-1]
1318 return oup
1319
1320 def decode(self, inp):
1321 if not inp:
1322 return ''
1323 return binascii.a2b_base64(inp)
1324
Guido van Rossumeda960a1998-06-18 14:24:28 +00001325
1326
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001327Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001328 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001329
1330def Internaldate2tuple(resp):
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001331 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001332
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001333 Return corresponding local time. The return value is a
1334 time.struct_time instance or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001335 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001336
Tim Peters07e99cb2001-01-14 23:47:14 +00001337 mo = InternalDate.match(resp)
1338 if not mo:
1339 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001340
Tim Peters07e99cb2001-01-14 23:47:14 +00001341 mon = Mon2num[mo.group('mon')]
1342 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001344 day = int(mo.group('day'))
1345 year = int(mo.group('year'))
1346 hour = int(mo.group('hour'))
1347 min = int(mo.group('min'))
1348 sec = int(mo.group('sec'))
1349 zoneh = int(mo.group('zoneh'))
1350 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001351
Tim Peters07e99cb2001-01-14 23:47:14 +00001352 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001353
Tim Peters07e99cb2001-01-14 23:47:14 +00001354 zone = (zoneh*60 + zonem)*60
1355 if zonen == '-':
1356 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001357
Tim Peters07e99cb2001-01-14 23:47:14 +00001358 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001359
Tim Peters07e99cb2001-01-14 23:47:14 +00001360 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001361
Tim Peters07e99cb2001-01-14 23:47:14 +00001362 # Following is necessary because the time module has no 'mkgmtime'.
1363 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001364
Tim Peters07e99cb2001-01-14 23:47:14 +00001365 lt = time.localtime(utc)
1366 if time.daylight and lt[-1]:
1367 zone = zone + time.altzone
1368 else:
1369 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001370
Tim Peters07e99cb2001-01-14 23:47:14 +00001371 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001372
1373
1374
1375def Int2AP(num):
1376
Tim Peters07e99cb2001-01-14 23:47:14 +00001377 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001378
Tim Peters07e99cb2001-01-14 23:47:14 +00001379 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1380 num = int(abs(num))
1381 while num:
1382 num, mod = divmod(num, 16)
1383 val = AP[mod] + val
1384 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001385
1386
1387
1388def ParseFlags(resp):
1389
Tim Peters07e99cb2001-01-14 23:47:14 +00001390 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001391
Tim Peters07e99cb2001-01-14 23:47:14 +00001392 mo = Flags.match(resp)
1393 if not mo:
1394 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001395
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001396 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001397
1398
1399def Time2Internaldate(date_time):
1400
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001401 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001402
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001403 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Ezio Melottic2077b02011-03-16 12:34:31 +02001404 date_time argument can be a number (int or float) representing
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001405 seconds since epoch (as returned by time.time()), a 9-tuple
1406 representing local time (as returned by time.localtime()), or a
1407 double-quoted string. In the last case, it is assumed to already
1408 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001410
Fred Drakedb519202002-01-05 17:17:09 +00001411 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001412 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001413 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001414 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001415 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001416 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001417 else:
1418 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001419
Tim Peters07e99cb2001-01-14 23:47:14 +00001420 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1421 if dt[0] == '0':
1422 dt = ' ' + dt[1:]
1423 if time.daylight and tt[-1]:
1424 zone = -time.altzone
1425 else:
1426 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001427 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001428
1429
1430
Guido van Rossum8c062211999-12-13 23:27:45 +00001431if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
Piers Laudere0273de2002-11-22 05:53:04 +00001433 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1434 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1435 # to test the IMAP4_stream class
1436
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001437 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001438
Tim Peters07e99cb2001-01-14 23:47:14 +00001439 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001440 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001442 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001443
Piers Laudere0273de2002-11-22 05:53:04 +00001444 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 for opt,val in optlist:
1446 if opt == '-d':
1447 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001448 elif opt == '-s':
1449 stream_command = val
1450 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001451
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001453
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001455
Tim Peters07e99cb2001-01-14 23:47:14 +00001456 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001457 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001458
Piers Lauder47404ff2003-04-29 23:40:59 +00001459 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 +00001460 test_seq1 = (
1461 ('login', (USER, PASSWD)),
1462 ('create', ('/tmp/xxx 1',)),
1463 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1464 ('CREATE', ('/tmp/yyz 2',)),
1465 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1466 ('list', ('/tmp', 'yy*')),
1467 ('select', ('/tmp/yyz 2',)),
1468 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001469 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001470 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001471 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001472 ('expunge', ()),
1473 ('recent', ()),
1474 ('close', ()),
1475 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001476
Tim Peters07e99cb2001-01-14 23:47:14 +00001477 test_seq2 = (
1478 ('select', ()),
1479 ('response',('UIDVALIDITY',)),
1480 ('uid', ('SEARCH', 'ALL')),
1481 ('response', ('EXISTS',)),
1482 ('append', (None, None, None, test_mesg)),
1483 ('recent', ()),
1484 ('logout', ()),
1485 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001486
Tim Peters07e99cb2001-01-14 23:47:14 +00001487 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001488 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001489 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001490 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001491 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001492 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001493
Tim Peters07e99cb2001-01-14 23:47:14 +00001494 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001495 if stream_command:
1496 M = IMAP4_stream(stream_command)
1497 else:
1498 M = IMAP4(host)
1499 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001500 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001501 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001502 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001503
Tim Peters07e99cb2001-01-14 23:47:14 +00001504 for cmd,args in test_seq1:
1505 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001506
Tim Peters07e99cb2001-01-14 23:47:14 +00001507 for ml in run('list', ('/tmp/', 'yy%')):
1508 mo = re.match(r'.*"([^"]+)"$', ml)
1509 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001510 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001511 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001512
Tim Peters07e99cb2001-01-14 23:47:14 +00001513 for cmd,args in test_seq2:
1514 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001515
Tim Peters07e99cb2001-01-14 23:47:14 +00001516 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1517 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001518
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001519 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001520 if not uid: continue
1521 run('uid', ('FETCH', '%s' % uid[-1],
1522 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001523
Tim Peters07e99cb2001-01-14 23:47:14 +00001524 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001525
Tim Peters07e99cb2001-01-14 23:47:14 +00001526 except:
1527 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001528
Tim Peters07e99cb2001-01-14 23:47:14 +00001529 if not Debug:
1530 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001531If you would like to see debugging output,
1532try: %s -d5
1533''' % sys.argv[0]
1534
Tim Peters07e99cb2001-01-14 23:47:14 +00001535 raise