blob: 56931911857646edde7a3dd7488ece36ba6e5c6c [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
Tim Peters07e99cb2001-01-14 23:47:14 +000038# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000039
40Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000041 # name valid states
42 'APPEND': ('AUTH', 'SELECTED'),
43 'AUTHENTICATE': ('NONAUTH',),
44 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
45 'CHECK': ('SELECTED',),
46 'CLOSE': ('SELECTED',),
47 'COPY': ('SELECTED',),
48 'CREATE': ('AUTH', 'SELECTED'),
49 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000050 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000051 'EXAMINE': ('AUTH', 'SELECTED'),
52 'EXPUNGE': ('SELECTED',),
53 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000054 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000055 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000056 'GETQUOTA': ('AUTH', 'SELECTED'),
57 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000058 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000059 'LIST': ('AUTH', 'SELECTED'),
60 'LOGIN': ('NONAUTH',),
61 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
62 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000063 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000064 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000065 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000066 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000067 'RENAME': ('AUTH', 'SELECTED'),
68 'SEARCH': ('SELECTED',),
69 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000070 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000071 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000072 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000073 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000074 'STATUS': ('AUTH', 'SELECTED'),
75 'STORE': ('SELECTED',),
76 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000077 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000078 'UID': ('SELECTED',),
79 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
80 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000081
Tim Peters07e99cb2001-01-14 23:47:14 +000082# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000083
Guido van Rossumeda960a1998-06-18 14:24:28 +000084Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000085Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
86InternalDate = re.compile(r'.*INTERNALDATE "'
Piers Lauder8659d902005-03-02 09:13:45 +000087 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 +000088 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
89 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
90 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000091Literal = re.compile(r'.*{(?P<size>\d+)}$')
Piers Lauder533366b2003-04-29 23:58:08 +000092MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000093Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000094Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000095Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
96
97
98
99class IMAP4:
100
Tim Peters07e99cb2001-01-14 23:47:14 +0000101 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102
Tim Peters07e99cb2001-01-14 23:47:14 +0000103 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104
Tim Peters07e99cb2001-01-14 23:47:14 +0000105 host - host's name (default: localhost);
106 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000107
Tim Peters07e99cb2001-01-14 23:47:14 +0000108 All IMAP4rev1 commands are supported by methods of the same
109 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000110
Tim Peters07e99cb2001-01-14 23:47:14 +0000111 All arguments to commands are converted to strings, except for
112 AUTHENTICATE, and the last argument to APPEND which is passed as
113 an IMAP4 literal. If necessary (the string contains any
114 non-printing characters or white-space and isn't enclosed with
115 either parentheses or double quotes) each string is quoted.
116 However, the 'password' argument to the LOGIN command is always
117 quoted. If you want to avoid having an argument string quoted
118 (eg: the 'flags' argument to STORE) then enclose the string in
119 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000120
Tim Peters07e99cb2001-01-14 23:47:14 +0000121 Each command returns a tuple: (type, [data, ...]) where 'type'
122 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000123 tagged response, or untagged results from command. Each 'data'
124 is either a string, or a tuple. If a tuple, then the first part
125 is the header of the response, and the second part contains
126 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000127
Tim Peters07e99cb2001-01-14 23:47:14 +0000128 Errors raise the exception class <instance>.error("<reason>").
129 IMAP4 server errors raise <instance>.abort("<reason>"),
130 which is a sub-class of 'error'. Mailbox status changes
131 from READ-WRITE to READ-ONLY raise the exception class
132 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000133
Tim Peters07e99cb2001-01-14 23:47:14 +0000134 "error" exceptions imply a program error.
135 "abort" exceptions imply the connection should be reset, and
136 the command re-tried.
137 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000138
Piers Lauderd80ef022005-06-01 23:50:52 +0000139 Note: to use this module, you must read the RFCs pertaining to the
140 IMAP4 protocol, as the semantics of the arguments to each IMAP4
141 command are left to the invoker, not to mention the results. Also,
142 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000144
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 class error(Exception): pass # Logical errors - debug required
146 class abort(error): pass # Service errors - close and retry
147 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000148
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000150
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 self.debug = Debug
153 self.state = 'LOGOUT'
154 self.literal = None # A literal argument to a command
155 self.tagged_commands = {} # Tagged commands awaiting response
156 self.untagged_responses = {} # {typ: [data, ...], ...}
157 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000158 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000160
Tim Peters07e99cb2001-01-14 23:47:14 +0000161 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000162
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000164
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 # Create unique tag for this session,
166 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000167
Piers Lauder2dfc1682005-07-05 04:20:07 +0000168 self.tagpre = Int2AP(random.randint(4096, 65535))
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 self.tagre = re.compile(r'(?P<tag>'
170 + self.tagpre
171 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000172
Tim Peters07e99cb2001-01-14 23:47:14 +0000173 # Get server welcome message,
174 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000175
Tim Peters07e99cb2001-01-14 23:47:14 +0000176 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000177 self._cmd_log_len = 10
178 self._cmd_log_idx = 0
179 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000181 self._mesg('imaplib version %s' % __version__)
182 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
Tim Peters07e99cb2001-01-14 23:47:14 +0000184 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000185 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000186 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000187 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000188 self.state = 'NONAUTH'
189 else:
190 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000191
Piers Lauderd80ef022005-06-01 23:50:52 +0000192 typ, dat = self.capability()
193 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000194 raise self.error('no CAPABILITY response from server')
Piers Lauderd80ef022005-06-01 23:50:52 +0000195 self.capabilities = tuple(dat[-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000196
Tim Peters07e99cb2001-01-14 23:47:14 +0000197 if __debug__:
198 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000199 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000200
Tim Peters07e99cb2001-01-14 23:47:14 +0000201 for version in AllowedVersions:
202 if not version in self.capabilities:
203 continue
204 self.PROTOCOL_VERSION = version
205 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000206
Tim Peters07e99cb2001-01-14 23:47:14 +0000207 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000208
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000209
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 def __getattr__(self, attr):
211 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000212 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000213 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000214 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000215
216
217
Piers Lauder15e5d532001-07-20 10:52:06 +0000218 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000219
220
Piers Lauderf97b2d72002-06-05 22:31:57 +0000221 def open(self, host = '', port = IMAP4_PORT):
222 """Setup connection to remote server on "host:port"
223 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000224 This connection will be used by the routines:
225 read, readline, send, shutdown.
226 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000227 self.host = host
228 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +0000229 self.sock = socket.create_connection((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000230 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000231
232
Piers Lauder15e5d532001-07-20 10:52:06 +0000233 def read(self, size):
234 """Read 'size' bytes from remote."""
235 return self.file.read(size)
236
237
238 def readline(self):
239 """Read line from remote."""
240 return self.file.readline()
241
242
243 def send(self, data):
244 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000245 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000246
Piers Lauderf2d7d152002-02-22 01:15:17 +0000247
Piers Lauder15e5d532001-07-20 10:52:06 +0000248 def shutdown(self):
249 """Close I/O established in "open"."""
250 self.file.close()
Antoine Pitrouae933892010-11-10 09:02:33 +0000251 try:
252 self.sock.shutdown(socket.SHUT_RDWR)
253 except socket.error as e:
254 # The server might already have closed the connection
255 if e.errno != errno.ENOTCONN:
256 raise
257 finally:
258 self.sock.close()
Piers Lauder15e5d532001-07-20 10:52:06 +0000259
260
261 def socket(self):
262 """Return socket instance used to connect to IMAP4 server.
263
264 socket = <instance>.socket()
265 """
266 return self.sock
267
268
269
270 # Utility methods
271
272
Tim Peters07e99cb2001-01-14 23:47:14 +0000273 def recent(self):
274 """Return most recent 'RECENT' responses if any exist,
275 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000276
Tim Peters07e99cb2001-01-14 23:47:14 +0000277 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000278
Tim Peters07e99cb2001-01-14 23:47:14 +0000279 'data' is None if no new messages,
280 else list of RECENT responses, most recent last.
281 """
282 name = 'RECENT'
283 typ, dat = self._untagged_response('OK', [None], name)
284 if dat[-1]:
285 return typ, dat
286 typ, dat = self.noop() # Prod server for response
287 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000288
289
Tim Peters07e99cb2001-01-14 23:47:14 +0000290 def response(self, code):
291 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000292
Tim Peters07e99cb2001-01-14 23:47:14 +0000293 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000294
Tim Peters07e99cb2001-01-14 23:47:14 +0000295 (code, [data]) = <instance>.response(code)
296 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000297 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000298
299
Guido van Rossum26367a01998-09-28 15:34:46 +0000300
Tim Peters07e99cb2001-01-14 23:47:14 +0000301 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000302
303
Tim Peters07e99cb2001-01-14 23:47:14 +0000304 def append(self, mailbox, flags, date_time, message):
305 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000306
Tim Peters07e99cb2001-01-14 23:47:14 +0000307 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 All args except `message' can be None.
310 """
311 name = 'APPEND'
312 if not mailbox:
313 mailbox = 'INBOX'
314 if flags:
315 if (flags[0],flags[-1]) != ('(',')'):
316 flags = '(%s)' % flags
317 else:
318 flags = None
319 if date_time:
320 date_time = Time2Internaldate(date_time)
321 else:
322 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000323 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000324 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000325
326
Tim Peters07e99cb2001-01-14 23:47:14 +0000327 def authenticate(self, mechanism, authobject):
328 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000329
Tim Peters07e99cb2001-01-14 23:47:14 +0000330 'mechanism' specifies which authentication mechanism is to
331 be used - it must appear in <instance>.capabilities in the
332 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000333
Tim Peters07e99cb2001-01-14 23:47:14 +0000334 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000335
Tim Peters07e99cb2001-01-14 23:47:14 +0000336 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000337
Tim Peters07e99cb2001-01-14 23:47:14 +0000338 It will be called to process server continuation responses.
339 It should return data that will be encoded and sent to server.
340 It should return None if the client abort response '*' should
341 be sent instead.
342 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000343 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000344 # XXX: shouldn't this code be removed, not commented out?
345 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000346 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000347 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000348 self.literal = _Authenticator(authobject).process
349 typ, dat = self._simple_command('AUTHENTICATE', mech)
350 if typ != 'OK':
351 raise self.error(dat[-1])
352 self.state = 'AUTH'
353 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000354
355
Piers Lauderd80ef022005-06-01 23:50:52 +0000356 def capability(self):
357 """(typ, [data]) = <instance>.capability()
358 Fetch capabilities list from server."""
359
360 name = 'CAPABILITY'
361 typ, dat = self._simple_command(name)
362 return self._untagged_response(typ, dat, name)
363
364
Tim Peters07e99cb2001-01-14 23:47:14 +0000365 def check(self):
366 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000367
Tim Peters07e99cb2001-01-14 23:47:14 +0000368 (typ, [data]) = <instance>.check()
369 """
370 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000371
372
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 def close(self):
374 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000375
Tim Peters07e99cb2001-01-14 23:47:14 +0000376 Deleted messages are removed from writable mailbox.
377 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
Tim Peters07e99cb2001-01-14 23:47:14 +0000379 (typ, [data]) = <instance>.close()
380 """
381 try:
382 typ, dat = self._simple_command('CLOSE')
383 finally:
384 self.state = 'AUTH'
385 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000386
387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 def copy(self, message_set, new_mailbox):
389 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000390
Tim Peters07e99cb2001-01-14 23:47:14 +0000391 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
392 """
393 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000394
395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 def create(self, mailbox):
397 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 (typ, [data]) = <instance>.create(mailbox)
400 """
401 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000402
403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 def delete(self, mailbox):
405 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000406
Tim Peters07e99cb2001-01-14 23:47:14 +0000407 (typ, [data]) = <instance>.delete(mailbox)
408 """
409 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000410
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000411 def deleteacl(self, mailbox, who):
412 """Delete the ACLs (remove any rights) set for who on mailbox.
413
414 (typ, [data]) = <instance>.deleteacl(mailbox, who)
415 """
416 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 def expunge(self):
419 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000420
Tim Peters07e99cb2001-01-14 23:47:14 +0000421 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000424
Tim Peters07e99cb2001-01-14 23:47:14 +0000425 'data' is list of 'EXPUNGE'd message numbers in order received.
426 """
427 name = 'EXPUNGE'
428 typ, dat = self._simple_command(name)
429 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000430
431
Tim Peters07e99cb2001-01-14 23:47:14 +0000432 def fetch(self, message_set, message_parts):
433 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000434
Tim Peters07e99cb2001-01-14 23:47:14 +0000435 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000436
Tim Peters07e99cb2001-01-14 23:47:14 +0000437 'message_parts' should be a string of selected parts
438 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000439
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 'data' are tuples of message part envelope and data.
441 """
442 name = 'FETCH'
443 typ, dat = self._simple_command(name, message_set, message_parts)
444 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000445
446
Piers Lauder15e5d532001-07-20 10:52:06 +0000447 def getacl(self, mailbox):
448 """Get the ACLs for a mailbox.
449
450 (typ, [data]) = <instance>.getacl(mailbox)
451 """
452 typ, dat = self._simple_command('GETACL', mailbox)
453 return self._untagged_response(typ, dat, 'ACL')
454
455
Piers Lauderd80ef022005-06-01 23:50:52 +0000456 def getannotation(self, mailbox, entry, attribute):
457 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
458 Retrieve ANNOTATIONs."""
459
460 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
461 return self._untagged_response(typ, dat, 'ANNOTATION')
462
463
Piers Lauder3fca2912002-06-17 07:07:20 +0000464 def getquota(self, root):
465 """Get the quota root's resource usage and limits.
466
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000467 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000468
469 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000470 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000471 typ, dat = self._simple_command('GETQUOTA', root)
472 return self._untagged_response(typ, dat, 'QUOTA')
473
474
475 def getquotaroot(self, mailbox):
476 """Get the list of quota roots for the named mailbox.
477
478 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000479 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000480 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000481 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
482 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000483 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000484
485
Tim Peters07e99cb2001-01-14 23:47:14 +0000486 def list(self, directory='""', pattern='*'):
487 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000488
Tim Peters07e99cb2001-01-14 23:47:14 +0000489 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000490
Tim Peters07e99cb2001-01-14 23:47:14 +0000491 'data' is list of LIST responses.
492 """
493 name = 'LIST'
494 typ, dat = self._simple_command(name, directory, pattern)
495 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000496
497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 def login(self, user, password):
499 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000500
Tim Peters07e99cb2001-01-14 23:47:14 +0000501 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000502
Tim Peters07e99cb2001-01-14 23:47:14 +0000503 NB: 'password' will be quoted.
504 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
506 if typ != 'OK':
507 raise self.error(dat[-1])
508 self.state = 'AUTH'
509 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000510
511
Piers Laudere0273de2002-11-22 05:53:04 +0000512 def login_cram_md5(self, user, password):
513 """ Force use of CRAM-MD5 authentication.
514
515 (typ, [data]) = <instance>.login_cram_md5(user, password)
516 """
517 self.user, self.password = user, password
518 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
519
520
521 def _CRAM_MD5_AUTH(self, challenge):
522 """ Authobject to use with CRAM-MD5 authentication. """
523 import hmac
524 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
525
526
Tim Peters07e99cb2001-01-14 23:47:14 +0000527 def logout(self):
528 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000529
Tim Peters07e99cb2001-01-14 23:47:14 +0000530 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000531
Tim Peters07e99cb2001-01-14 23:47:14 +0000532 Returns server 'BYE' response.
533 """
534 self.state = 'LOGOUT'
535 try: typ, dat = self._simple_command('LOGOUT')
536 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000537 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000538 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000539 return 'BYE', self.untagged_responses['BYE']
540 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000541
542
Tim Peters07e99cb2001-01-14 23:47:14 +0000543 def lsub(self, directory='""', pattern='*'):
544 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000545
Tim Peters07e99cb2001-01-14 23:47:14 +0000546 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000547
Tim Peters07e99cb2001-01-14 23:47:14 +0000548 'data' are tuples of message part envelope and data.
549 """
550 name = 'LSUB'
551 typ, dat = self._simple_command(name, directory, pattern)
552 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000553
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000554 def myrights(self, mailbox):
555 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
556
557 (typ, [data]) = <instance>.myrights(mailbox)
558 """
559 typ,dat = self._simple_command('MYRIGHTS', mailbox)
560 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000561
Piers Lauder15e5d532001-07-20 10:52:06 +0000562 def namespace(self):
563 """ Returns IMAP namespaces ala rfc2342
564
565 (typ, [data, ...]) = <instance>.namespace()
566 """
567 name = 'NAMESPACE'
568 typ, dat = self._simple_command(name)
569 return self._untagged_response(typ, dat, name)
570
571
Tim Peters07e99cb2001-01-14 23:47:14 +0000572 def noop(self):
573 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000574
Piers Laudere0273de2002-11-22 05:53:04 +0000575 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 """
577 if __debug__:
578 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000579 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000581
582
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 def partial(self, message_num, message_part, start, length):
584 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000585
Tim Peters07e99cb2001-01-14 23:47:14 +0000586 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000587
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 'data' is tuple of message part envelope and data.
589 """
590 name = 'PARTIAL'
591 typ, dat = self._simple_command(name, message_num, message_part, start, length)
592 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000593
594
Piers Laudere0273de2002-11-22 05:53:04 +0000595 def proxyauth(self, user):
596 """Assume authentication as "user".
597
598 Allows an authorised administrator to proxy into any user's
599 mailbox.
600
601 (typ, [data]) = <instance>.proxyauth(user)
602 """
603
604 name = 'PROXYAUTH'
605 return self._simple_command('PROXYAUTH', user)
606
607
Tim Peters07e99cb2001-01-14 23:47:14 +0000608 def rename(self, oldmailbox, newmailbox):
609 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000610
Piers Laudere0273de2002-11-22 05:53:04 +0000611 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000612 """
613 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000614
615
Tim Peters07e99cb2001-01-14 23:47:14 +0000616 def search(self, charset, *criteria):
617 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000618
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000619 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000620
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 'data' is space separated list of matching message numbers.
622 """
623 name = 'SEARCH'
624 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000625 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000626 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000627 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000629
630
Piers Lauder14f39402005-08-31 10:46:29 +0000631 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000632 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000633
Tim Peters07e99cb2001-01-14 23:47:14 +0000634 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000635
Piers Lauder14f39402005-08-31 10:46:29 +0000636 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000637
Tim Peters07e99cb2001-01-14 23:47:14 +0000638 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000639
640 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
641 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000642 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000643 self.untagged_responses = {} # Flush old responses.
644 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000645 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000646 name = 'EXAMINE'
647 else:
648 name = 'SELECT'
649 typ, dat = self._simple_command(name, mailbox)
650 if typ != 'OK':
651 self.state = 'AUTH' # Might have been 'SELECTED'
652 return typ, dat
653 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000654 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 and not readonly:
656 if __debug__:
657 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000658 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000659 raise self.readonly('%s is not writable' % mailbox)
660 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000661
662
Piers Lauder15e5d532001-07-20 10:52:06 +0000663 def setacl(self, mailbox, who, what):
664 """Set a mailbox acl.
665
Piers Lauderf167dc32004-03-25 00:12:21 +0000666 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000667 """
668 return self._simple_command('SETACL', mailbox, who, what)
669
670
Piers Lauderd80ef022005-06-01 23:50:52 +0000671 def setannotation(self, *args):
672 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
673 Set ANNOTATIONs."""
674
675 typ, dat = self._simple_command('SETANNOTATION', *args)
676 return self._untagged_response(typ, dat, 'ANNOTATION')
677
678
Piers Lauder3fca2912002-06-17 07:07:20 +0000679 def setquota(self, root, limits):
680 """Set the quota root's resource limits.
681
682 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000683 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000684 typ, dat = self._simple_command('SETQUOTA', root, limits)
685 return self._untagged_response(typ, dat, 'QUOTA')
686
687
Piers Lauder15e5d532001-07-20 10:52:06 +0000688 def sort(self, sort_criteria, charset, *search_criteria):
689 """IMAP4rev1 extension SORT command.
690
691 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
692 """
693 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000694 #if not name in self.capabilities: # Let the server decide!
695 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000696 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000697 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000698 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000699 return self._untagged_response(typ, dat, name)
700
701
Tim Peters07e99cb2001-01-14 23:47:14 +0000702 def status(self, mailbox, names):
703 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000704
Tim Peters07e99cb2001-01-14 23:47:14 +0000705 (typ, [data]) = <instance>.status(mailbox, names)
706 """
707 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000708 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000709 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000710 typ, dat = self._simple_command(name, mailbox, names)
711 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000712
713
Tim Peters07e99cb2001-01-14 23:47:14 +0000714 def store(self, message_set, command, flags):
715 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000716
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 (typ, [data]) = <instance>.store(message_set, command, flags)
718 """
719 if (flags[0],flags[-1]) != ('(',')'):
720 flags = '(%s)' % flags # Avoid quoting the flags
721 typ, dat = self._simple_command('STORE', message_set, command, flags)
722 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000723
724
Tim Peters07e99cb2001-01-14 23:47:14 +0000725 def subscribe(self, mailbox):
726 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
Tim Peters07e99cb2001-01-14 23:47:14 +0000728 (typ, [data]) = <instance>.subscribe(mailbox)
729 """
730 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000731
732
Martin v. Löwisd8921372003-11-10 06:44:44 +0000733 def thread(self, threading_algorithm, charset, *search_criteria):
734 """IMAPrev1 extension THREAD command.
735
Mark Dickinsondb69f012009-12-24 16:06:58 +0000736 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000737 """
738 name = 'THREAD'
739 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
740 return self._untagged_response(typ, dat, name)
741
742
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 def uid(self, command, *args):
744 """Execute "command arg ..." with messages identified by UID,
745 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000746
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000748
Tim Peters07e99cb2001-01-14 23:47:14 +0000749 Returns response appropriate to 'command'.
750 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000751 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000752 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 raise self.error("Unknown IMAP4 UID command: %s" % command)
754 if self.state not in Commands[command]:
Georg Brandl6c104f62007-03-13 18:24:40 +0000755 raise self.error("command %s illegal in state %s, "
756 "only allowed in states %s" %
757 (command, self.state,
758 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000759 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000760 typ, dat = self._simple_command(name, command, *args)
Georg Brandl004c74d2010-08-01 19:06:51 +0000761 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000762 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000763 else:
764 name = 'FETCH'
765 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000766
767
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 def unsubscribe(self, mailbox):
769 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000770
Tim Peters07e99cb2001-01-14 23:47:14 +0000771 (typ, [data]) = <instance>.unsubscribe(mailbox)
772 """
773 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000774
775
Tim Peters07e99cb2001-01-14 23:47:14 +0000776 def xatom(self, name, *args):
777 """Allow simple extension commands
778 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000779
Piers Lauder15e5d532001-07-20 10:52:06 +0000780 Assumes command is legal in current state.
781
Tim Peters07e99cb2001-01-14 23:47:14 +0000782 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000783
784 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000785 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000786 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000787 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000788 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000789 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000790 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000791 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000792
793
794
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000796
797
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000799
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 if dat is None: dat = ''
801 ur = self.untagged_responses
802 if __debug__:
803 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000804 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000806 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 ur[typ].append(dat)
808 else:
809 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000810
811
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 def _check_bye(self):
813 bye = self.untagged_responses.get('BYE')
814 if bye:
815 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000816
817
Tim Peters07e99cb2001-01-14 23:47:14 +0000818 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 if self.state not in Commands[name]:
821 self.literal = None
Georg Brandl6c104f62007-03-13 18:24:40 +0000822 raise self.error("command %s illegal in state %s, "
823 "only allowed in states %s" %
824 (name, self.state,
825 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000828 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000829 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000830
Raymond Hettinger54f02222002-06-01 14:18:47 +0000831 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 and not self.is_readonly:
833 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 tag = self._new_tag()
836 data = '%s %s' % (tag, name)
837 for arg in args:
838 if arg is None: continue
839 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000840
Tim Peters07e99cb2001-01-14 23:47:14 +0000841 literal = self.literal
842 if literal is not None:
843 self.literal = None
844 if type(literal) is type(self._command):
845 literator = literal
846 else:
847 literator = None
848 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 if __debug__:
851 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000852 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000853 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000854 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000855
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000857 self.send('%s%s' % (data, CRLF))
858 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000859 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 if literal is None:
862 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 while 1:
865 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000866
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 while self._get_response():
868 if self.tagged_commands[tag]: # BAD/NO?
869 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000872
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 if literator:
874 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 if __debug__:
877 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000878 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000881 self.send(literal)
882 self.send(CRLF)
883 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 if not literator:
887 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000890
891
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 def _command_complete(self, name, tag):
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000893 # BYE is expected after LOGOUT
894 if name != 'LOGOUT':
895 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000896 try:
897 typ, data = self._get_tagged_response(tag)
898 except self.abort, val:
899 raise self.abort('command: %s => %s' % (name, val))
900 except self.error, val:
901 raise self.error('command: %s => %s' % (name, val))
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000902 if name != 'LOGOUT':
903 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000904 if typ == 'BAD':
905 raise self.error('%s command error: %s %s' % (name, typ, data))
906 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
908
Tim Peters07e99cb2001-01-14 23:47:14 +0000909 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000910
Tim Peters07e99cb2001-01-14 23:47:14 +0000911 # Read response and store.
912 #
913 # Returns None for continuation responses,
914 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000917
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000919
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 if self._match(self.tagre, resp):
921 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000922 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 typ = self.mo.group('type')
926 dat = self.mo.group('data')
927 self.tagged_commands[tag] = (typ, [dat])
928 else:
929 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 if not self._match(Untagged_response, resp):
934 if self._match(Untagged_status, resp):
935 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 if self.mo is None:
938 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000939
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 if self._match(Continuation, resp):
941 self.continuation_response = self.mo.group('data')
942 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000943
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 typ = self.mo.group('type')
947 dat = self.mo.group('data')
948 if dat is None: dat = '' # Null untagged response
949 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000950
Tim Peters07e99cb2001-01-14 23:47:14 +0000951 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000957 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 if __debug__:
959 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000960 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000961 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000970
Tim Peters07e99cb2001-01-14 23:47:14 +0000971 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
976 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 if __debug__:
979 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000980 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000983
984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 while 1:
988 result = self.tagged_commands[tag]
989 if result is not None:
990 del self.tagged_commands[tag]
991 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 # Some have reported "unexpected response" exceptions.
994 # Note that ignoring them here causes loops.
995 # Instead, send me details of the unexpected response and
996 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 try:
999 self._get_response()
1000 except self.abort, val:
1001 if __debug__:
1002 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001003 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001005
1006
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001008
Piers Lauder15e5d532001-07-20 10:52:06 +00001009 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 if not line:
1011 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001012
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 # Protocol mandates all lines terminated by CRLF
R. David Murray93321f32009-12-09 15:15:31 +00001014 if not line.endswith('\r\n'):
1015 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001016
Tim Peters07e99cb2001-01-14 23:47:14 +00001017 line = line[:-2]
1018 if __debug__:
1019 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001020 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001022 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001023 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001024
1025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 # Run compiled regular expression match method on 's'.
1029 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001030
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 self.mo = cre.match(s)
1032 if __debug__:
1033 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001034 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001036
1037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 tag = '%s%s' % (self.tagpre, self.tagnum)
1041 self.tagnum = self.tagnum + 1
1042 self.tagged_commands[tag] = None
1043 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001044
1045
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 # Must quote command args if non-alphanumeric chars present,
1049 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001050
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 if type(arg) is not type(''):
1052 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001053 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001055 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001056 return arg
1057 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001058
1059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001061
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001062 arg = arg.replace('\\', '\\\\')
1063 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001064
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001066
1067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001069
Guido van Rossum68468eb2003-02-27 20:14:51 +00001070 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001071
1072
Tim Peters07e99cb2001-01-14 23:47:14 +00001073 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001074
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 if typ == 'NO':
1076 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001077 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001079 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 if __debug__:
1081 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001082 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001084
1085
Piers Lauderf2d7d152002-02-22 01:15:17 +00001086 if __debug__:
1087
1088 def _mesg(self, s, secs=None):
1089 if secs is None:
1090 secs = time.time()
1091 tm = time.strftime('%M:%S', time.localtime(secs))
1092 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1093 sys.stderr.flush()
1094
1095 def _dump_ur(self, dict):
1096 # Dump untagged responses (in `dict').
1097 l = dict.items()
1098 if not l: return
1099 t = '\n\t\t'
1100 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1101 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1102
1103 def _log(self, line):
1104 # Keep log of last `_cmd_log_len' interactions for debugging.
1105 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1106 self._cmd_log_idx += 1
1107 if self._cmd_log_idx >= self._cmd_log_len:
1108 self._cmd_log_idx = 0
1109
1110 def print_log(self):
1111 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1112 i, n = self._cmd_log_idx, self._cmd_log_len
1113 while n:
1114 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001115 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001116 except:
1117 pass
1118 i += 1
1119 if i >= self._cmd_log_len:
1120 i = 0
1121 n -= 1
1122
1123
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001124
Bill Janssen426ea0a2007-08-29 22:35:05 +00001125try:
1126 import ssl
1127except ImportError:
1128 pass
1129else:
1130 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001131
Bill Janssen426ea0a2007-08-29 22:35:05 +00001132 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001133
Bill Janssen426ea0a2007-08-29 22:35:05 +00001134 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001135
Bill Janssen426ea0a2007-08-29 22:35:05 +00001136 host - host's name (default: localhost);
1137 port - port number (default: standard IMAP4 SSL port).
1138 keyfile - PEM formatted file that contains your private key (default: None);
1139 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001140
Bill Janssen426ea0a2007-08-29 22:35:05 +00001141 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001142 """
Piers Laudera4f83132002-03-08 01:53:24 +00001143
1144
Bill Janssen426ea0a2007-08-29 22:35:05 +00001145 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1146 self.keyfile = keyfile
1147 self.certfile = certfile
1148 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001149
1150
Bill Janssen426ea0a2007-08-29 22:35:05 +00001151 def open(self, host = '', port = IMAP4_SSL_PORT):
1152 """Setup connection to remote server on "host:port".
1153 (default: localhost:standard IMAP4 SSL port).
1154 This connection will be used by the routines:
1155 read, readline, send, shutdown.
1156 """
1157 self.host = host
1158 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +00001159 self.sock = socket.create_connection((host, port))
Bill Janssen98d19da2007-09-10 21:51:02 +00001160 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001161
1162
Bill Janssen426ea0a2007-08-29 22:35:05 +00001163 def read(self, size):
1164 """Read 'size' bytes from remote."""
1165 # sslobj.read() sometimes returns < size bytes
1166 chunks = []
1167 read = 0
1168 while read < size:
Andrew M. Kuchling1219a802008-02-23 19:02:33 +00001169 data = self.sslobj.read(min(size-read, 16384))
Bill Janssen426ea0a2007-08-29 22:35:05 +00001170 read += len(data)
1171 chunks.append(data)
1172
1173 return ''.join(chunks)
Piers Laudera4f83132002-03-08 01:53:24 +00001174
1175
Bill Janssen426ea0a2007-08-29 22:35:05 +00001176 def readline(self):
1177 """Read line from remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001178 line = []
1179 while 1:
1180 char = self.sslobj.read(1)
1181 line.append(char)
R. David Murray93321f32009-12-09 15:15:31 +00001182 if char in ("\n", ""): return ''.join(line)
Piers Laudera4f83132002-03-08 01:53:24 +00001183
1184
Bill Janssen426ea0a2007-08-29 22:35:05 +00001185 def send(self, data):
1186 """Send data to remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001187 bytes = len(data)
1188 while bytes > 0:
1189 sent = self.sslobj.write(data)
1190 if sent == bytes:
1191 break # avoid copy
1192 data = data[sent:]
1193 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001194
1195
Bill Janssen426ea0a2007-08-29 22:35:05 +00001196 def shutdown(self):
1197 """Close I/O established in "open"."""
1198 self.sock.close()
Piers Laudera4f83132002-03-08 01:53:24 +00001199
Bill Janssen426ea0a2007-08-29 22:35:05 +00001200
1201 def socket(self):
1202 """Return socket instance used to connect to IMAP4 server.
1203
1204 socket = <instance>.socket()
1205 """
1206 return self.sock
1207
1208
1209 def ssl(self):
1210 """Return SSLObject instance used to communicate with the IMAP4 server.
1211
Bill Janssen98d19da2007-09-10 21:51:02 +00001212 ssl = ssl.wrap_socket(<instance>.socket)
Bill Janssen426ea0a2007-08-29 22:35:05 +00001213 """
1214 return self.sslobj
Piers Laudera4f83132002-03-08 01:53:24 +00001215
Thomas Woutersa6900e82007-08-30 21:54:39 +00001216 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001217
1218
Piers Laudere0273de2002-11-22 05:53:04 +00001219class IMAP4_stream(IMAP4):
1220
1221 """IMAP4 client class over a stream
1222
1223 Instantiate with: IMAP4_stream(command)
1224
Georg Brandl36f42142010-01-02 12:35:01 +00001225 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001226
1227 for more documentation see the docstring of the parent class IMAP4.
1228 """
1229
1230
1231 def __init__(self, command):
1232 self.command = command
1233 IMAP4.__init__(self)
1234
1235
1236 def open(self, host = None, port = None):
1237 """Setup a stream connection.
1238 This connection will be used by the routines:
1239 read, readline, send, shutdown.
1240 """
1241 self.host = None # For compatibility with parent class
1242 self.port = None
1243 self.sock = None
1244 self.file = None
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001245 self.process = subprocess.Popen(self.command,
1246 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1247 shell=True, close_fds=True)
1248 self.writefile = self.process.stdin
1249 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001250
1251
1252 def read(self, size):
1253 """Read 'size' bytes from remote."""
1254 return self.readfile.read(size)
1255
1256
1257 def readline(self):
1258 """Read line from remote."""
1259 return self.readfile.readline()
1260
1261
1262 def send(self, data):
1263 """Send data to remote."""
1264 self.writefile.write(data)
1265 self.writefile.flush()
1266
1267
1268 def shutdown(self):
1269 """Close I/O established in "open"."""
1270 self.readfile.close()
1271 self.writefile.close()
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001272 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001273
1274
1275
Guido van Rossumeda960a1998-06-18 14:24:28 +00001276class _Authenticator:
1277
Tim Peters07e99cb2001-01-14 23:47:14 +00001278 """Private class to provide en/decoding
1279 for base64-based authentication conversation.
1280 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001281
Tim Peters07e99cb2001-01-14 23:47:14 +00001282 def __init__(self, mechinst):
1283 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001284
Tim Peters07e99cb2001-01-14 23:47:14 +00001285 def process(self, data):
1286 ret = self.mech(self.decode(data))
1287 if ret is None:
1288 return '*' # Abort conversation
1289 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001290
Tim Peters07e99cb2001-01-14 23:47:14 +00001291 def encode(self, inp):
1292 #
1293 # Invoke binascii.b2a_base64 iteratively with
1294 # short even length buffers, strip the trailing
1295 # line feed from the result and append. "Even"
1296 # means a number that factors to both 6 and 8,
1297 # so when it gets to the end of the 8-bit input
1298 # there's no partial 6-bit output.
1299 #
1300 oup = ''
1301 while inp:
1302 if len(inp) > 48:
1303 t = inp[:48]
1304 inp = inp[48:]
1305 else:
1306 t = inp
1307 inp = ''
1308 e = binascii.b2a_base64(t)
1309 if e:
1310 oup = oup + e[:-1]
1311 return oup
1312
1313 def decode(self, inp):
1314 if not inp:
1315 return ''
1316 return binascii.a2b_base64(inp)
1317
Guido van Rossumeda960a1998-06-18 14:24:28 +00001318
1319
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001320Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001321 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322
1323def Internaldate2tuple(resp):
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001324 """Parse an IMAP4 INTERNALDATE string.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001325
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001326 Return corresponding local time. The return value is a
1327 time.struct_time instance or None if the string has wrong format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001328 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001329
Tim Peters07e99cb2001-01-14 23:47:14 +00001330 mo = InternalDate.match(resp)
1331 if not mo:
1332 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001333
Tim Peters07e99cb2001-01-14 23:47:14 +00001334 mon = Mon2num[mo.group('mon')]
1335 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001336
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001337 day = int(mo.group('day'))
1338 year = int(mo.group('year'))
1339 hour = int(mo.group('hour'))
1340 min = int(mo.group('min'))
1341 sec = int(mo.group('sec'))
1342 zoneh = int(mo.group('zoneh'))
1343 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001344
Tim Peters07e99cb2001-01-14 23:47:14 +00001345 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001346
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 zone = (zoneh*60 + zonem)*60
1348 if zonen == '-':
1349 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001350
Tim Peters07e99cb2001-01-14 23:47:14 +00001351 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001352
Tim Peters07e99cb2001-01-14 23:47:14 +00001353 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001354
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 # Following is necessary because the time module has no 'mkgmtime'.
1356 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001357
Tim Peters07e99cb2001-01-14 23:47:14 +00001358 lt = time.localtime(utc)
1359 if time.daylight and lt[-1]:
1360 zone = zone + time.altzone
1361 else:
1362 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001363
Tim Peters07e99cb2001-01-14 23:47:14 +00001364 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001365
1366
1367
1368def Int2AP(num):
1369
Tim Peters07e99cb2001-01-14 23:47:14 +00001370 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001371
Tim Peters07e99cb2001-01-14 23:47:14 +00001372 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1373 num = int(abs(num))
1374 while num:
1375 num, mod = divmod(num, 16)
1376 val = AP[mod] + val
1377 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001378
1379
1380
1381def ParseFlags(resp):
1382
Tim Peters07e99cb2001-01-14 23:47:14 +00001383 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001384
Tim Peters07e99cb2001-01-14 23:47:14 +00001385 mo = Flags.match(resp)
1386 if not mo:
1387 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001388
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001389 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001390
1391
1392def Time2Internaldate(date_time):
1393
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001394 """Convert date_time to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001395
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001396 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
Ezio Melottic2077b02011-03-16 12:34:31 +02001397 date_time argument can be a number (int or float) representing
Alexander Belopolsky7e8fbd22011-01-19 21:48:20 +00001398 seconds since epoch (as returned by time.time()), a 9-tuple
1399 representing local time (as returned by time.localtime()), or a
1400 double-quoted string. In the last case, it is assumed to already
1401 be in the correct format.
Tim Peters07e99cb2001-01-14 23:47:14 +00001402 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001403
Fred Drakedb519202002-01-05 17:17:09 +00001404 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001405 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001406 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001407 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001408 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001410 else:
1411 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1414 if dt[0] == '0':
1415 dt = ' ' + dt[1:]
1416 if time.daylight and tt[-1]:
1417 zone = -time.altzone
1418 else:
1419 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001420 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001421
1422
1423
Guido van Rossum8c062211999-12-13 23:27:45 +00001424if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001425
Piers Laudere0273de2002-11-22 05:53:04 +00001426 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1427 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1428 # to test the IMAP4_stream class
1429
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001430 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001431
Tim Peters07e99cb2001-01-14 23:47:14 +00001432 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001433 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001435 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001436
Piers Laudere0273de2002-11-22 05:53:04 +00001437 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001438 for opt,val in optlist:
1439 if opt == '-d':
1440 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001441 elif opt == '-s':
1442 stream_command = val
1443 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001444
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001446
Tim Peters07e99cb2001-01-14 23:47:14 +00001447 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001448
Tim Peters07e99cb2001-01-14 23:47:14 +00001449 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001450 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001451
Piers Lauder47404ff2003-04-29 23:40:59 +00001452 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 +00001453 test_seq1 = (
1454 ('login', (USER, PASSWD)),
1455 ('create', ('/tmp/xxx 1',)),
1456 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1457 ('CREATE', ('/tmp/yyz 2',)),
1458 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1459 ('list', ('/tmp', 'yy*')),
1460 ('select', ('/tmp/yyz 2',)),
1461 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001462 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001463 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001464 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001465 ('expunge', ()),
1466 ('recent', ()),
1467 ('close', ()),
1468 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001469
Tim Peters07e99cb2001-01-14 23:47:14 +00001470 test_seq2 = (
1471 ('select', ()),
1472 ('response',('UIDVALIDITY',)),
1473 ('uid', ('SEARCH', 'ALL')),
1474 ('response', ('EXISTS',)),
1475 ('append', (None, None, None, test_mesg)),
1476 ('recent', ()),
1477 ('logout', ()),
1478 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001479
Tim Peters07e99cb2001-01-14 23:47:14 +00001480 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001481 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001482 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001483 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001484 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001485 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001486
Tim Peters07e99cb2001-01-14 23:47:14 +00001487 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001488 if stream_command:
1489 M = IMAP4_stream(stream_command)
1490 else:
1491 M = IMAP4(host)
1492 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001493 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001494 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001495 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001496
Tim Peters07e99cb2001-01-14 23:47:14 +00001497 for cmd,args in test_seq1:
1498 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001499
Tim Peters07e99cb2001-01-14 23:47:14 +00001500 for ml in run('list', ('/tmp/', 'yy%')):
1501 mo = re.match(r'.*"([^"]+)"$', ml)
1502 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001503 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001504 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001505
Tim Peters07e99cb2001-01-14 23:47:14 +00001506 for cmd,args in test_seq2:
1507 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001508
Tim Peters07e99cb2001-01-14 23:47:14 +00001509 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1510 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001511
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001512 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001513 if not uid: continue
1514 run('uid', ('FETCH', '%s' % uid[-1],
1515 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001516
Tim Peters07e99cb2001-01-14 23:47:14 +00001517 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001518
Tim Peters07e99cb2001-01-14 23:47:14 +00001519 except:
1520 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001521
Tim Peters07e99cb2001-01-14 23:47:14 +00001522 if not Debug:
1523 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001524If you would like to see debugging output,
1525try: %s -d5
1526''' % sys.argv[0]
1527
Tim Peters07e99cb2001-01-14 23:47:14 +00001528 raise