blob: 9b0fd3b98f57775c74d27d176646fbfd77bd79b2 [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
Christian Heimesfb5faf02008-11-05 19:39:50 +000025import binascii, random, re, socket, subprocess, sys, time
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000026
Thomas Wouters47b49bf2007-08-30 22:15:33 +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
Christian Heimesfb5faf02008-11-05 19:39:50 +000032CRLF = b'\r\n'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000033Debug = 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
Christian Heimesfb5faf02008-11-05 19:39:50 +000084Continuation = re.compile(br'\+( (?P<data>.*))?')
85Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
86InternalDate = re.compile(br'.*INTERNALDATE "'
87 br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
88 br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
89 br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
90 br'"')
91Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
92MapCRLF = re.compile(br'\r\n|\r|\n')
93Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
94Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +000095Untagged_status = re.compile(
Christian Heimesfb5faf02008-11-05 19:39:50 +000096 br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000097
98
99
100class IMAP4:
101
Tim Peters07e99cb2001-01-14 23:47:14 +0000102 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000103
Tim Peters07e99cb2001-01-14 23:47:14 +0000104 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000105
Tim Peters07e99cb2001-01-14 23:47:14 +0000106 host - host's name (default: localhost);
107 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000108
Tim Peters07e99cb2001-01-14 23:47:14 +0000109 All IMAP4rev1 commands are supported by methods of the same
110 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000111
Tim Peters07e99cb2001-01-14 23:47:14 +0000112 All arguments to commands are converted to strings, except for
113 AUTHENTICATE, and the last argument to APPEND which is passed as
114 an IMAP4 literal. If necessary (the string contains any
115 non-printing characters or white-space and isn't enclosed with
116 either parentheses or double quotes) each string is quoted.
117 However, the 'password' argument to the LOGIN command is always
118 quoted. If you want to avoid having an argument string quoted
119 (eg: the 'flags' argument to STORE) then enclose the string in
120 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000121
Tim Peters07e99cb2001-01-14 23:47:14 +0000122 Each command returns a tuple: (type, [data, ...]) where 'type'
123 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000124 tagged response, or untagged results from command. Each 'data'
125 is either a string, or a tuple. If a tuple, then the first part
126 is the header of the response, and the second part contains
127 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 Errors raise the exception class <instance>.error("<reason>").
130 IMAP4 server errors raise <instance>.abort("<reason>"),
131 which is a sub-class of 'error'. Mailbox status changes
132 from READ-WRITE to READ-ONLY raise the exception class
133 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000134
Tim Peters07e99cb2001-01-14 23:47:14 +0000135 "error" exceptions imply a program error.
136 "abort" exceptions imply the connection should be reset, and
137 the command re-tried.
138 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000139
Piers Lauderd80ef022005-06-01 23:50:52 +0000140 Note: to use this module, you must read the RFCs pertaining to the
141 IMAP4 protocol, as the semantics of the arguments to each IMAP4
142 command are left to the invoker, not to mention the results. Also,
143 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000144 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000145
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 class error(Exception): pass # Logical errors - debug required
147 class abort(error): pass # Service errors - close and retry
148 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000149
Tim Peters07e99cb2001-01-14 23:47:14 +0000150 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 self.debug = Debug
152 self.state = 'LOGOUT'
153 self.literal = None # A literal argument to a command
154 self.tagged_commands = {} # Tagged commands awaiting response
155 self.untagged_responses = {} # {typ: [data, ...], ...}
156 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000157 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000163
Tim Peters07e99cb2001-01-14 23:47:14 +0000164 # Create unique tag for this session,
165 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000166
Piers Lauder2dfc1682005-07-05 04:20:07 +0000167 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000168 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000170 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 # Get server welcome message,
173 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000174
Tim Peters07e99cb2001-01-14 23:47:14 +0000175 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000176 self._cmd_log_len = 10
177 self._cmd_log_idx = 0
178 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000180 self._mesg('imaplib version %s' % __version__)
181 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000182
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000184 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000186 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000187 self.state = 'NONAUTH'
188 else:
189 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000190
Piers Lauderd80ef022005-06-01 23:50:52 +0000191 typ, dat = self.capability()
192 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000193 raise self.error('no CAPABILITY response from server')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000194 dat = str(dat[-1], "ASCII")
195 dat = dat.upper()
196 self.capabilities = tuple(dat.split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000197
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 if __debug__:
199 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000200 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000201
Tim Peters07e99cb2001-01-14 23:47:14 +0000202 for version in AllowedVersions:
203 if not version in self.capabilities:
204 continue
205 self.PROTOCOL_VERSION = version
206 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000207
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000209
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000210
Tim Peters07e99cb2001-01-14 23:47:14 +0000211 def __getattr__(self, attr):
212 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000213 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000214 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000216
217
218
Piers Lauder15e5d532001-07-20 10:52:06 +0000219 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000220
221
Christian Heimesfb5faf02008-11-05 19:39:50 +0000222 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000223 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000224
Piers Lauderf97b2d72002-06-05 22:31:57 +0000225 def open(self, host = '', port = IMAP4_PORT):
226 """Setup connection to remote server on "host:port"
227 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000228 This connection will be used by the routines:
229 read, readline, send, shutdown.
230 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000231 self.host = host
232 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000233 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000234 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000235
236
Piers Lauder15e5d532001-07-20 10:52:06 +0000237 def read(self, size):
238 """Read 'size' bytes from remote."""
Christian Heimesfb5faf02008-11-05 19:39:50 +0000239 chunks = []
240 read = 0
241 while read < size:
242 data = self.file.read(min(size-read, 4096))
243 if not data:
244 break
245 read += len(data)
246 chunks.append(data)
247 return b''.join(chunks)
Piers Lauder15e5d532001-07-20 10:52:06 +0000248
249
250 def readline(self):
251 """Read line from remote."""
252 return self.file.readline()
253
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()
263 self.sock.close()
264
265
266 def socket(self):
267 """Return socket instance used to connect to IMAP4 server.
268
269 socket = <instance>.socket()
270 """
271 return self.sock
272
273
274
275 # Utility methods
276
277
Tim Peters07e99cb2001-01-14 23:47:14 +0000278 def recent(self):
279 """Return most recent 'RECENT' responses if any exist,
280 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000281
Tim Peters07e99cb2001-01-14 23:47:14 +0000282 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000283
Tim Peters07e99cb2001-01-14 23:47:14 +0000284 'data' is None if no new messages,
285 else list of RECENT responses, most recent last.
286 """
287 name = 'RECENT'
288 typ, dat = self._untagged_response('OK', [None], name)
289 if dat[-1]:
290 return typ, dat
291 typ, dat = self.noop() # Prod server for response
292 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000293
294
Tim Peters07e99cb2001-01-14 23:47:14 +0000295 def response(self, code):
296 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000297
Tim Peters07e99cb2001-01-14 23:47:14 +0000298 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000299
Tim Peters07e99cb2001-01-14 23:47:14 +0000300 (code, [data]) = <instance>.response(code)
301 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000302 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000303
304
Guido van Rossum26367a01998-09-28 15:34:46 +0000305
Tim Peters07e99cb2001-01-14 23:47:14 +0000306 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000307
308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 def append(self, mailbox, flags, date_time, message):
310 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000311
Tim Peters07e99cb2001-01-14 23:47:14 +0000312 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000313
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 All args except `message' can be None.
315 """
316 name = 'APPEND'
317 if not mailbox:
318 mailbox = 'INBOX'
319 if flags:
320 if (flags[0],flags[-1]) != ('(',')'):
321 flags = '(%s)' % flags
322 else:
323 flags = None
324 if date_time:
325 date_time = Time2Internaldate(date_time)
326 else:
327 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000328 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000329 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000330
331
Tim Peters07e99cb2001-01-14 23:47:14 +0000332 def authenticate(self, mechanism, authobject):
333 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000334
Tim Peters07e99cb2001-01-14 23:47:14 +0000335 'mechanism' specifies which authentication mechanism is to
336 be used - it must appear in <instance>.capabilities in the
337 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000338
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000342
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 It will be called to process server continuation responses.
344 It should return data that will be encoded and sent to server.
345 It should return None if the client abort response '*' should
346 be sent instead.
347 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000348 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000349 # XXX: shouldn't this code be removed, not commented out?
350 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000351 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000352 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 self.literal = _Authenticator(authobject).process
354 typ, dat = self._simple_command('AUTHENTICATE', mech)
355 if typ != 'OK':
356 raise self.error(dat[-1])
357 self.state = 'AUTH'
358 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000359
360
Piers Lauderd80ef022005-06-01 23:50:52 +0000361 def capability(self):
362 """(typ, [data]) = <instance>.capability()
363 Fetch capabilities list from server."""
364
365 name = 'CAPABILITY'
366 typ, dat = self._simple_command(name)
367 return self._untagged_response(typ, dat, name)
368
369
Tim Peters07e99cb2001-01-14 23:47:14 +0000370 def check(self):
371 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000372
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 (typ, [data]) = <instance>.check()
374 """
375 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000376
377
Tim Peters07e99cb2001-01-14 23:47:14 +0000378 def close(self):
379 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 Deleted messages are removed from writable mailbox.
382 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 (typ, [data]) = <instance>.close()
385 """
386 try:
387 typ, dat = self._simple_command('CLOSE')
388 finally:
389 self.state = 'AUTH'
390 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000391
392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 def copy(self, message_set, new_mailbox):
394 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
397 """
398 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
400
Tim Peters07e99cb2001-01-14 23:47:14 +0000401 def create(self, mailbox):
402 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 (typ, [data]) = <instance>.create(mailbox)
405 """
406 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
408
Tim Peters07e99cb2001-01-14 23:47:14 +0000409 def delete(self, mailbox):
410 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 (typ, [data]) = <instance>.delete(mailbox)
413 """
414 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000415
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000416 def deleteacl(self, mailbox, who):
417 """Delete the ACLs (remove any rights) set for who on mailbox.
418
419 (typ, [data]) = <instance>.deleteacl(mailbox, who)
420 """
421 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 def expunge(self):
424 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000425
Tim Peters07e99cb2001-01-14 23:47:14 +0000426 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 'data' is list of 'EXPUNGE'd message numbers in order received.
431 """
432 name = 'EXPUNGE'
433 typ, dat = self._simple_command(name)
434 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000435
436
Tim Peters07e99cb2001-01-14 23:47:14 +0000437 def fetch(self, message_set, message_parts):
438 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000439
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Tim Peters07e99cb2001-01-14 23:47:14 +0000442 'message_parts' should be a string of selected parts
443 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000444
Tim Peters07e99cb2001-01-14 23:47:14 +0000445 'data' are tuples of message part envelope and data.
446 """
447 name = 'FETCH'
448 typ, dat = self._simple_command(name, message_set, message_parts)
449 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000450
451
Piers Lauder15e5d532001-07-20 10:52:06 +0000452 def getacl(self, mailbox):
453 """Get the ACLs for a mailbox.
454
455 (typ, [data]) = <instance>.getacl(mailbox)
456 """
457 typ, dat = self._simple_command('GETACL', mailbox)
458 return self._untagged_response(typ, dat, 'ACL')
459
460
Piers Lauderd80ef022005-06-01 23:50:52 +0000461 def getannotation(self, mailbox, entry, attribute):
462 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
463 Retrieve ANNOTATIONs."""
464
465 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
466 return self._untagged_response(typ, dat, 'ANNOTATION')
467
468
Piers Lauder3fca2912002-06-17 07:07:20 +0000469 def getquota(self, root):
470 """Get the quota root's resource usage and limits.
471
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000472 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000473
474 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000475 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000476 typ, dat = self._simple_command('GETQUOTA', root)
477 return self._untagged_response(typ, dat, 'QUOTA')
478
479
480 def getquotaroot(self, mailbox):
481 """Get the list of quota roots for the named mailbox.
482
483 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000484 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000485 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000486 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
487 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000488 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000489
490
Tim Peters07e99cb2001-01-14 23:47:14 +0000491 def list(self, directory='""', pattern='*'):
492 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000493
Tim Peters07e99cb2001-01-14 23:47:14 +0000494 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 'data' is list of LIST responses.
497 """
498 name = 'LIST'
499 typ, dat = self._simple_command(name, directory, pattern)
500 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000501
502
Tim Peters07e99cb2001-01-14 23:47:14 +0000503 def login(self, user, password):
504 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
Tim Peters07e99cb2001-01-14 23:47:14 +0000506 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 NB: 'password' will be quoted.
509 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
511 if typ != 'OK':
512 raise self.error(dat[-1])
513 self.state = 'AUTH'
514 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000515
516
Piers Laudere0273de2002-11-22 05:53:04 +0000517 def login_cram_md5(self, user, password):
518 """ Force use of CRAM-MD5 authentication.
519
520 (typ, [data]) = <instance>.login_cram_md5(user, password)
521 """
522 self.user, self.password = user, password
523 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
524
525
526 def _CRAM_MD5_AUTH(self, challenge):
527 """ Authobject to use with CRAM-MD5 authentication. """
528 import hmac
529 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
530
531
Tim Peters07e99cb2001-01-14 23:47:14 +0000532 def logout(self):
533 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000534
Tim Peters07e99cb2001-01-14 23:47:14 +0000535 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 Returns server 'BYE' response.
538 """
539 self.state = 'LOGOUT'
540 try: typ, dat = self._simple_command('LOGOUT')
541 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000542 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000543 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000544 return 'BYE', self.untagged_responses['BYE']
545 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000546
547
Tim Peters07e99cb2001-01-14 23:47:14 +0000548 def lsub(self, directory='""', pattern='*'):
549 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000550
Tim Peters07e99cb2001-01-14 23:47:14 +0000551 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000552
Tim Peters07e99cb2001-01-14 23:47:14 +0000553 'data' are tuples of message part envelope and data.
554 """
555 name = 'LSUB'
556 typ, dat = self._simple_command(name, directory, pattern)
557 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000558
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000559 def myrights(self, mailbox):
560 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
561
562 (typ, [data]) = <instance>.myrights(mailbox)
563 """
564 typ,dat = self._simple_command('MYRIGHTS', mailbox)
565 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000566
Piers Lauder15e5d532001-07-20 10:52:06 +0000567 def namespace(self):
568 """ Returns IMAP namespaces ala rfc2342
569
570 (typ, [data, ...]) = <instance>.namespace()
571 """
572 name = 'NAMESPACE'
573 typ, dat = self._simple_command(name)
574 return self._untagged_response(typ, dat, name)
575
576
Tim Peters07e99cb2001-01-14 23:47:14 +0000577 def noop(self):
578 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000579
Piers Laudere0273de2002-11-22 05:53:04 +0000580 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 """
582 if __debug__:
583 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000584 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000586
587
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 def partial(self, message_num, message_part, start, length):
589 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000590
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000592
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 'data' is tuple of message part envelope and data.
594 """
595 name = 'PARTIAL'
596 typ, dat = self._simple_command(name, message_num, message_part, start, length)
597 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000598
599
Piers Laudere0273de2002-11-22 05:53:04 +0000600 def proxyauth(self, user):
601 """Assume authentication as "user".
602
603 Allows an authorised administrator to proxy into any user's
604 mailbox.
605
606 (typ, [data]) = <instance>.proxyauth(user)
607 """
608
609 name = 'PROXYAUTH'
610 return self._simple_command('PROXYAUTH', user)
611
612
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 def rename(self, oldmailbox, newmailbox):
614 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000615
Piers Laudere0273de2002-11-22 05:53:04 +0000616 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 """
618 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000619
620
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 def search(self, charset, *criteria):
622 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000623
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000624 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000625
Tim Peters07e99cb2001-01-14 23:47:14 +0000626 'data' is space separated list of matching message numbers.
627 """
628 name = 'SEARCH'
629 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000630 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000631 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000632 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000634
635
Piers Lauder14f39402005-08-31 10:46:29 +0000636 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000640
Piers Lauder14f39402005-08-31 10:46:29 +0000641 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
Tim Peters07e99cb2001-01-14 23:47:14 +0000643 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000644
645 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
646 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000647 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000648 self.untagged_responses = {} # Flush old responses.
649 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000650 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 name = 'EXAMINE'
652 else:
653 name = 'SELECT'
654 typ, dat = self._simple_command(name, mailbox)
655 if typ != 'OK':
656 self.state = 'AUTH' # Might have been 'SELECTED'
657 return typ, dat
658 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000659 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 and not readonly:
661 if __debug__:
662 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000663 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 raise self.readonly('%s is not writable' % mailbox)
665 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000666
667
Piers Lauder15e5d532001-07-20 10:52:06 +0000668 def setacl(self, mailbox, who, what):
669 """Set a mailbox acl.
670
Piers Lauderf167dc32004-03-25 00:12:21 +0000671 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000672 """
673 return self._simple_command('SETACL', mailbox, who, what)
674
675
Piers Lauderd80ef022005-06-01 23:50:52 +0000676 def setannotation(self, *args):
677 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
678 Set ANNOTATIONs."""
679
680 typ, dat = self._simple_command('SETANNOTATION', *args)
681 return self._untagged_response(typ, dat, 'ANNOTATION')
682
683
Piers Lauder3fca2912002-06-17 07:07:20 +0000684 def setquota(self, root, limits):
685 """Set the quota root's resource limits.
686
687 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000688 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000689 typ, dat = self._simple_command('SETQUOTA', root, limits)
690 return self._untagged_response(typ, dat, 'QUOTA')
691
692
Piers Lauder15e5d532001-07-20 10:52:06 +0000693 def sort(self, sort_criteria, charset, *search_criteria):
694 """IMAP4rev1 extension SORT command.
695
696 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
697 """
698 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000699 #if not name in self.capabilities: # Let the server decide!
700 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000701 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000702 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000703 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000704 return self._untagged_response(typ, dat, name)
705
706
Tim Peters07e99cb2001-01-14 23:47:14 +0000707 def status(self, mailbox, names):
708 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000709
Tim Peters07e99cb2001-01-14 23:47:14 +0000710 (typ, [data]) = <instance>.status(mailbox, names)
711 """
712 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000713 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000714 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000715 typ, dat = self._simple_command(name, mailbox, names)
716 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000717
718
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 def store(self, message_set, command, flags):
720 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000721
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 (typ, [data]) = <instance>.store(message_set, command, flags)
723 """
724 if (flags[0],flags[-1]) != ('(',')'):
725 flags = '(%s)' % flags # Avoid quoting the flags
726 typ, dat = self._simple_command('STORE', message_set, command, flags)
727 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000728
729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 def subscribe(self, mailbox):
731 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000732
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 (typ, [data]) = <instance>.subscribe(mailbox)
734 """
735 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
737
Martin v. Löwisd8921372003-11-10 06:44:44 +0000738 def thread(self, threading_algorithm, charset, *search_criteria):
739 """IMAPrev1 extension THREAD command.
740
Mark Dickinson8ae535b2009-12-24 16:12:49 +0000741 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000742 """
743 name = 'THREAD'
744 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
745 return self._untagged_response(typ, dat, name)
746
747
Tim Peters07e99cb2001-01-14 23:47:14 +0000748 def uid(self, command, *args):
749 """Execute "command arg ..." with messages identified by UID,
750 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000751
Tim Peters07e99cb2001-01-14 23:47:14 +0000752 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000753
Tim Peters07e99cb2001-01-14 23:47:14 +0000754 Returns response appropriate to 'command'.
755 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000756 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000757 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000758 raise self.error("Unknown IMAP4 UID command: %s" % command)
759 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000760 raise self.error("command %s illegal in state %s, "
761 "only allowed in states %s" %
762 (command, self.state,
763 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000764 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000765 typ, dat = self._simple_command(name, command, *args)
Georg Brandl05245f72010-07-31 22:32:52 +0000766 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000767 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 else:
769 name = 'FETCH'
770 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000771
772
Tim Peters07e99cb2001-01-14 23:47:14 +0000773 def unsubscribe(self, mailbox):
774 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000775
Tim Peters07e99cb2001-01-14 23:47:14 +0000776 (typ, [data]) = <instance>.unsubscribe(mailbox)
777 """
778 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000779
780
Tim Peters07e99cb2001-01-14 23:47:14 +0000781 def xatom(self, name, *args):
782 """Allow simple extension commands
783 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000784
Piers Lauder15e5d532001-07-20 10:52:06 +0000785 Assumes command is legal in current state.
786
Tim Peters07e99cb2001-01-14 23:47:14 +0000787 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000788
789 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000790 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000791 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000792 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000793 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000794 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000795 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000796 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000797
798
799
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000801
802
Tim Peters07e99cb2001-01-14 23:47:14 +0000803 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000804 if dat is None:
805 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000806 ur = self.untagged_responses
807 if __debug__:
808 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000809 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000811 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 ur[typ].append(dat)
813 else:
814 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 def _check_bye(self):
818 bye = self.untagged_responses.get('BYE')
819 if bye:
820 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000821
822
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 if self.state not in Commands[name]:
826 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000827 raise self.error("command %s illegal in state %s, "
828 "only allowed in states %s" %
829 (name, self.state,
830 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000833 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000835
Raymond Hettinger54f02222002-06-01 14:18:47 +0000836 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000837 and not self.is_readonly:
838 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000839
Tim Peters07e99cb2001-01-14 23:47:14 +0000840 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000841 name = bytes(name, 'ASCII')
842 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 for arg in args:
844 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000845 if isinstance(arg, str):
846 arg = bytes(arg, "ASCII")
Christian Heimesfb5faf02008-11-05 19:39:50 +0000847 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000848
Tim Peters07e99cb2001-01-14 23:47:14 +0000849 literal = self.literal
850 if literal is not None:
851 self.literal = None
852 if type(literal) is type(self._command):
853 literator = literal
854 else:
855 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000856 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000857
Tim Peters07e99cb2001-01-14 23:47:14 +0000858 if __debug__:
859 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000860 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000862 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000865 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000866 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 if literal is None:
870 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 while 1:
873 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000874
Tim Peters07e99cb2001-01-14 23:47:14 +0000875 while self._get_response():
876 if self.tagged_commands[tag]: # BAD/NO?
877 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000880
Tim Peters07e99cb2001-01-14 23:47:14 +0000881 if literator:
882 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 if __debug__:
885 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000886 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000887
Tim Peters07e99cb2001-01-14 23:47:14 +0000888 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000889 self.send(literal)
890 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000891 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000893
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 if not literator:
895 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000898
899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 def _command_complete(self, name, tag):
901 self._check_bye()
902 try:
903 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000904 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000906 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000907 raise self.error('command: %s => %s' % (name, val))
908 self._check_bye()
909 if typ == 'BAD':
910 raise self.error('%s command error: %s %s' % (name, typ, data))
911 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000912
913
Tim Peters07e99cb2001-01-14 23:47:14 +0000914 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
Tim Peters07e99cb2001-01-14 23:47:14 +0000916 # Read response and store.
917 #
918 # Returns None for continuation responses,
919 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 if self._match(self.tagre, resp):
926 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000927 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000931 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 dat = self.mo.group('data')
933 self.tagged_commands[tag] = (typ, [dat])
934 else:
935 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000938
Tim Peters07e99cb2001-01-14 23:47:14 +0000939 if not self._match(Untagged_response, resp):
940 if self._match(Untagged_status, resp):
941 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 if self.mo is None:
944 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 if self._match(Continuation, resp):
947 self.continuation_response = self.mo.group('data')
948 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000949
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000951
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000953 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +0000954 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000955 if dat is None: dat = b'' # Null untagged response
956 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000957
Tim Peters07e99cb2001-01-14 23:47:14 +0000958 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000959
Tim Peters07e99cb2001-01-14 23:47:14 +0000960 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000961
Tim Peters07e99cb2001-01-14 23:47:14 +0000962 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000963
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000964 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 if __debug__:
966 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000967 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000968 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000969
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000971
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000973
Tim Peters07e99cb2001-01-14 23:47:14 +0000974 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000975
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
Tim Peters07e99cb2001-01-14 23:47:14 +0000978 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000981
Tim Peters07e99cb2001-01-14 23:47:14 +0000982 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000983 typ = self.mo.group('type')
984 typ = str(typ, "ASCII")
985 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 if __debug__:
988 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000989 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000990
Tim Peters07e99cb2001-01-14 23:47:14 +0000991 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000992
993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000995
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 while 1:
997 result = self.tagged_commands[tag]
998 if result is not None:
999 del self.tagged_commands[tag]
1000 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001001
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 # Some have reported "unexpected response" exceptions.
1003 # Note that ignoring them here causes loops.
1004 # Instead, send me details of the unexpected response and
1005 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001006
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 try:
1008 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001009 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 if __debug__:
1011 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001012 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001014
1015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
Piers Lauder15e5d532001-07-20 10:52:06 +00001018 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 if not line:
1020 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 # Protocol mandates all lines terminated by CRLF
R. David Murraye8dc2582009-12-10 02:08:06 +00001023 if not line.endswith(b'\r\n'):
1024 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001025
Tim Peters07e99cb2001-01-14 23:47:14 +00001026 line = line[:-2]
1027 if __debug__:
1028 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001029 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001031 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001032 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
1034
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001036
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 # Run compiled regular expression match method on 's'.
1038 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 self.mo = cre.match(s)
1041 if __debug__:
1042 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001043 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001045
1046
Tim Peters07e99cb2001-01-14 23:47:14 +00001047 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001048
Christian Heimesfb5faf02008-11-05 19:39:50 +00001049 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 self.tagnum = self.tagnum + 1
1051 self.tagged_commands[tag] = None
1052 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001053
1054
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001056
Christian Heimesfb5faf02008-11-05 19:39:50 +00001057 arg = arg.replace(b'\\', b'\\\\')
1058 arg = arg.replace(b'"', b'\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001059
Christian Heimesfb5faf02008-11-05 19:39:50 +00001060 return b'"' + arg + b'"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001061
1062
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001064
Guido van Rossum68468eb2003-02-27 20:14:51 +00001065 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001066
1067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 if typ == 'NO':
1070 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001071 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001073 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 if __debug__:
1075 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001076 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001077 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001078
1079
Piers Lauderf2d7d152002-02-22 01:15:17 +00001080 if __debug__:
1081
1082 def _mesg(self, s, secs=None):
1083 if secs is None:
1084 secs = time.time()
1085 tm = time.strftime('%M:%S', time.localtime(secs))
1086 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1087 sys.stderr.flush()
1088
1089 def _dump_ur(self, dict):
1090 # Dump untagged responses (in `dict').
1091 l = dict.items()
1092 if not l: return
1093 t = '\n\t\t'
1094 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1095 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1096
1097 def _log(self, line):
1098 # Keep log of last `_cmd_log_len' interactions for debugging.
1099 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1100 self._cmd_log_idx += 1
1101 if self._cmd_log_idx >= self._cmd_log_len:
1102 self._cmd_log_idx = 0
1103
1104 def print_log(self):
1105 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1106 i, n = self._cmd_log_idx, self._cmd_log_len
1107 while n:
1108 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001109 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001110 except:
1111 pass
1112 i += 1
1113 if i >= self._cmd_log_len:
1114 i = 0
1115 n -= 1
1116
1117
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001118
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001119try:
1120 import ssl
1121except ImportError:
1122 pass
1123else:
1124 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001125
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001126 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001127
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001128 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001129
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001130 host - host's name (default: localhost);
1131 port - port number (default: standard IMAP4 SSL port).
1132 keyfile - PEM formatted file that contains your private key (default: None);
1133 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001134
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001135 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001136 """
Piers Laudera4f83132002-03-08 01:53:24 +00001137
1138
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001139 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1140 self.keyfile = keyfile
1141 self.certfile = certfile
1142 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001143
Christian Heimesfb5faf02008-11-05 19:39:50 +00001144 def _create_socket(self):
1145 sock = IMAP4._create_socket(self)
1146 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001147
Christian Heimesfb5faf02008-11-05 19:39:50 +00001148 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001149 """Setup connection to remote server on "host:port".
1150 (default: localhost:standard IMAP4 SSL port).
1151 This connection will be used by the routines:
1152 read, readline, send, shutdown.
1153 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001154 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001155
1156 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001157
1158
Piers Laudere0273de2002-11-22 05:53:04 +00001159class IMAP4_stream(IMAP4):
1160
1161 """IMAP4 client class over a stream
1162
1163 Instantiate with: IMAP4_stream(command)
1164
Christian Heimesfb5faf02008-11-05 19:39:50 +00001165 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001166
1167 for more documentation see the docstring of the parent class IMAP4.
1168 """
1169
1170
1171 def __init__(self, command):
1172 self.command = command
1173 IMAP4.__init__(self)
1174
1175
1176 def open(self, host = None, port = None):
1177 """Setup a stream connection.
1178 This connection will be used by the routines:
1179 read, readline, send, shutdown.
1180 """
1181 self.host = None # For compatibility with parent class
1182 self.port = None
1183 self.sock = None
1184 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001185 self.process = subprocess.Popen(self.command,
1186 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1187 shell=True, close_fds=True)
1188 self.writefile = self.process.stdin
1189 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001190
1191 def read(self, size):
1192 """Read 'size' bytes from remote."""
1193 return self.readfile.read(size)
1194
1195
1196 def readline(self):
1197 """Read line from remote."""
1198 return self.readfile.readline()
1199
1200
1201 def send(self, data):
1202 """Send data to remote."""
1203 self.writefile.write(data)
1204 self.writefile.flush()
1205
1206
1207 def shutdown(self):
1208 """Close I/O established in "open"."""
1209 self.readfile.close()
1210 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001211 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001212
1213
1214
Guido van Rossumeda960a1998-06-18 14:24:28 +00001215class _Authenticator:
1216
Tim Peters07e99cb2001-01-14 23:47:14 +00001217 """Private class to provide en/decoding
1218 for base64-based authentication conversation.
1219 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001220
Tim Peters07e99cb2001-01-14 23:47:14 +00001221 def __init__(self, mechinst):
1222 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001223
Tim Peters07e99cb2001-01-14 23:47:14 +00001224 def process(self, data):
1225 ret = self.mech(self.decode(data))
1226 if ret is None:
1227 return '*' # Abort conversation
1228 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001229
Tim Peters07e99cb2001-01-14 23:47:14 +00001230 def encode(self, inp):
1231 #
1232 # Invoke binascii.b2a_base64 iteratively with
1233 # short even length buffers, strip the trailing
1234 # line feed from the result and append. "Even"
1235 # means a number that factors to both 6 and 8,
1236 # so when it gets to the end of the 8-bit input
1237 # there's no partial 6-bit output.
1238 #
1239 oup = ''
1240 while inp:
1241 if len(inp) > 48:
1242 t = inp[:48]
1243 inp = inp[48:]
1244 else:
1245 t = inp
1246 inp = ''
1247 e = binascii.b2a_base64(t)
1248 if e:
1249 oup = oup + e[:-1]
1250 return oup
1251
1252 def decode(self, inp):
1253 if not inp:
1254 return ''
1255 return binascii.a2b_base64(inp)
1256
Guido van Rossumeda960a1998-06-18 14:24:28 +00001257
1258
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001259Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001260 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001261
1262def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001263 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001264
Tim Peters07e99cb2001-01-14 23:47:14 +00001265 Returns Python time module tuple.
1266 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001267
Tim Peters07e99cb2001-01-14 23:47:14 +00001268 mo = InternalDate.match(resp)
1269 if not mo:
1270 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001271
Tim Peters07e99cb2001-01-14 23:47:14 +00001272 mon = Mon2num[mo.group('mon')]
1273 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001274
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001275 day = int(mo.group('day'))
1276 year = int(mo.group('year'))
1277 hour = int(mo.group('hour'))
1278 min = int(mo.group('min'))
1279 sec = int(mo.group('sec'))
1280 zoneh = int(mo.group('zoneh'))
1281 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001282
Tim Peters07e99cb2001-01-14 23:47:14 +00001283 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001284
Tim Peters07e99cb2001-01-14 23:47:14 +00001285 zone = (zoneh*60 + zonem)*60
1286 if zonen == '-':
1287 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001288
Tim Peters07e99cb2001-01-14 23:47:14 +00001289 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001290
Tim Peters07e99cb2001-01-14 23:47:14 +00001291 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001292
Tim Peters07e99cb2001-01-14 23:47:14 +00001293 # Following is necessary because the time module has no 'mkgmtime'.
1294 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001295
Tim Peters07e99cb2001-01-14 23:47:14 +00001296 lt = time.localtime(utc)
1297 if time.daylight and lt[-1]:
1298 zone = zone + time.altzone
1299 else:
1300 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001301
Tim Peters07e99cb2001-01-14 23:47:14 +00001302 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001303
1304
1305
1306def Int2AP(num):
1307
Tim Peters07e99cb2001-01-14 23:47:14 +00001308 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001309
Christian Heimesfb5faf02008-11-05 19:39:50 +00001310 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 num = int(abs(num))
1312 while num:
1313 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001314 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001315 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001316
1317
1318
1319def ParseFlags(resp):
1320
Tim Peters07e99cb2001-01-14 23:47:14 +00001321 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 mo = Flags.match(resp)
1324 if not mo:
1325 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001326
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001327 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001328
1329
1330def Time2Internaldate(date_time):
1331
Tim Peters07e99cb2001-01-14 23:47:14 +00001332 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001333
Tim Peters07e99cb2001-01-14 23:47:14 +00001334 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1335 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001336
Fred Drakedb519202002-01-05 17:17:09 +00001337 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001338 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001339 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001340 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001341 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001342 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001343 else:
1344 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001345
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1347 if dt[0] == '0':
1348 dt = ' ' + dt[1:]
1349 if time.daylight and tt[-1]:
1350 zone = -time.altzone
1351 else:
1352 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001353 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001354
1355
1356
Guido van Rossum8c062211999-12-13 23:27:45 +00001357if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
Piers Laudere0273de2002-11-22 05:53:04 +00001359 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1360 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1361 # to test the IMAP4_stream class
1362
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001363 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001364
Tim Peters07e99cb2001-01-14 23:47:14 +00001365 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001366 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001367 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001368 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001369
Piers Laudere0273de2002-11-22 05:53:04 +00001370 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001371 for opt,val in optlist:
1372 if opt == '-d':
1373 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001374 elif opt == '-s':
1375 stream_command = val
1376 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001377
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001379
Tim Peters07e99cb2001-01-14 23:47:14 +00001380 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001381
Tim Peters07e99cb2001-01-14 23:47:14 +00001382 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001383 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001384
Piers Lauder47404ff2003-04-29 23:40:59 +00001385 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 +00001386 test_seq1 = (
1387 ('login', (USER, PASSWD)),
1388 ('create', ('/tmp/xxx 1',)),
1389 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1390 ('CREATE', ('/tmp/yyz 2',)),
1391 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1392 ('list', ('/tmp', 'yy*')),
1393 ('select', ('/tmp/yyz 2',)),
1394 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001395 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001397 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001398 ('expunge', ()),
1399 ('recent', ()),
1400 ('close', ()),
1401 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001402
Tim Peters07e99cb2001-01-14 23:47:14 +00001403 test_seq2 = (
1404 ('select', ()),
1405 ('response',('UIDVALIDITY',)),
1406 ('uid', ('SEARCH', 'ALL')),
1407 ('response', ('EXISTS',)),
1408 ('append', (None, None, None, test_mesg)),
1409 ('recent', ()),
1410 ('logout', ()),
1411 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001412
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001414 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001415 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001416 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001417 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001418 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001419
Tim Peters07e99cb2001-01-14 23:47:14 +00001420 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001421 if stream_command:
1422 M = IMAP4_stream(stream_command)
1423 else:
1424 M = IMAP4(host)
1425 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001426 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001427 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001428 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 for cmd,args in test_seq1:
1431 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 for ml in run('list', ('/tmp/', 'yy%')):
1434 mo = re.match(r'.*"([^"]+)"$', ml)
1435 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001436 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001437 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001438
Tim Peters07e99cb2001-01-14 23:47:14 +00001439 for cmd,args in test_seq2:
1440 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001441
Tim Peters07e99cb2001-01-14 23:47:14 +00001442 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1443 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001444
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001445 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001446 if not uid: continue
1447 run('uid', ('FETCH', '%s' % uid[-1],
1448 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001449
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001450 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001451
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001453 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001454
Tim Peters07e99cb2001-01-14 23:47:14 +00001455 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001456 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001457If you would like to see debugging output,
1458try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001459''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001460
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 raise