blob: df35cb14b02add7c711bce27af887b130c63ff26 [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
Christian Heimesfb5faf02008-11-05 19:39:50 +0000150 mustquote = re.compile(br"[^\w!#$%&'*+,.:;<=>?^`|~-]", re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000151
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 self.debug = Debug
154 self.state = 'LOGOUT'
155 self.literal = None # A literal argument to a command
156 self.tagged_commands = {} # Tagged commands awaiting response
157 self.untagged_responses = {} # {typ: [data, ...], ...}
158 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000159 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000163
Tim Peters07e99cb2001-01-14 23:47:14 +0000164 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000165
Tim Peters07e99cb2001-01-14 23:47:14 +0000166 # Create unique tag for this session,
167 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000168
Piers Lauder2dfc1682005-07-05 04:20:07 +0000169 self.tagpre = Int2AP(random.randint(4096, 65535))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000170 self.tagre = re.compile(br'(?P<tag>'
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 + self.tagpre
Christian Heimesfb5faf02008-11-05 19:39:50 +0000172 + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000173
Tim Peters07e99cb2001-01-14 23:47:14 +0000174 # Get server welcome message,
175 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000176
Tim Peters07e99cb2001-01-14 23:47:14 +0000177 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000178 self._cmd_log_len = 10
179 self._cmd_log_idx = 0
180 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000181 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000182 self._mesg('imaplib version %s' % __version__)
183 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000184
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000186 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000187 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000188 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 self.state = 'NONAUTH'
190 else:
191 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000192
Piers Lauderd80ef022005-06-01 23:50:52 +0000193 typ, dat = self.capability()
194 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 raise self.error('no CAPABILITY response from server')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000196 dat = str(dat[-1], "ASCII")
197 dat = dat.upper()
198 self.capabilities = tuple(dat.split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000199
Tim Peters07e99cb2001-01-14 23:47:14 +0000200 if __debug__:
201 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000202 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000203
Tim Peters07e99cb2001-01-14 23:47:14 +0000204 for version in AllowedVersions:
205 if not version in self.capabilities:
206 continue
207 self.PROTOCOL_VERSION = version
208 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000209
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000211
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000212
Tim Peters07e99cb2001-01-14 23:47:14 +0000213 def __getattr__(self, attr):
214 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000215 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000216 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000217 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000218
219
220
Piers Lauder15e5d532001-07-20 10:52:06 +0000221 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000222
223
Christian Heimesfb5faf02008-11-05 19:39:50 +0000224 def _create_socket(self):
Antoine Pitrouc1d09362009-05-15 12:15:51 +0000225 return socket.create_connection((self.host, self.port))
Christian Heimesfb5faf02008-11-05 19:39:50 +0000226
Piers Lauderf97b2d72002-06-05 22:31:57 +0000227 def open(self, host = '', port = IMAP4_PORT):
228 """Setup connection to remote server on "host:port"
229 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000230 This connection will be used by the routines:
231 read, readline, send, shutdown.
232 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000233 self.host = host
234 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000235 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000236 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000237
238
Piers Lauder15e5d532001-07-20 10:52:06 +0000239 def read(self, size):
240 """Read 'size' bytes from remote."""
Christian Heimesfb5faf02008-11-05 19:39:50 +0000241 chunks = []
242 read = 0
243 while read < size:
244 data = self.file.read(min(size-read, 4096))
245 if not data:
246 break
247 read += len(data)
248 chunks.append(data)
249 return b''.join(chunks)
Piers Lauder15e5d532001-07-20 10:52:06 +0000250
251
252 def readline(self):
253 """Read line from remote."""
254 return self.file.readline()
255
256
257 def send(self, data):
258 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000259 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000260
Piers Lauderf2d7d152002-02-22 01:15:17 +0000261
Piers Lauder15e5d532001-07-20 10:52:06 +0000262 def shutdown(self):
263 """Close I/O established in "open"."""
264 self.file.close()
265 self.sock.close()
266
267
268 def socket(self):
269 """Return socket instance used to connect to IMAP4 server.
270
271 socket = <instance>.socket()
272 """
273 return self.sock
274
275
276
277 # Utility methods
278
279
Tim Peters07e99cb2001-01-14 23:47:14 +0000280 def recent(self):
281 """Return most recent 'RECENT' responses if any exist,
282 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000283
Tim Peters07e99cb2001-01-14 23:47:14 +0000284 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000285
Tim Peters07e99cb2001-01-14 23:47:14 +0000286 'data' is None if no new messages,
287 else list of RECENT responses, most recent last.
288 """
289 name = 'RECENT'
290 typ, dat = self._untagged_response('OK', [None], name)
291 if dat[-1]:
292 return typ, dat
293 typ, dat = self.noop() # Prod server for response
294 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000295
296
Tim Peters07e99cb2001-01-14 23:47:14 +0000297 def response(self, code):
298 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000299
Tim Peters07e99cb2001-01-14 23:47:14 +0000300 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000301
Tim Peters07e99cb2001-01-14 23:47:14 +0000302 (code, [data]) = <instance>.response(code)
303 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000304 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000305
306
Guido van Rossum26367a01998-09-28 15:34:46 +0000307
Tim Peters07e99cb2001-01-14 23:47:14 +0000308 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000309
310
Tim Peters07e99cb2001-01-14 23:47:14 +0000311 def append(self, mailbox, flags, date_time, message):
312 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000313
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000315
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 All args except `message' can be None.
317 """
318 name = 'APPEND'
319 if not mailbox:
320 mailbox = 'INBOX'
321 if flags:
322 if (flags[0],flags[-1]) != ('(',')'):
323 flags = '(%s)' % flags
324 else:
325 flags = None
326 if date_time:
327 date_time = Time2Internaldate(date_time)
328 else:
329 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000330 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000331 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000332
333
Tim Peters07e99cb2001-01-14 23:47:14 +0000334 def authenticate(self, mechanism, authobject):
335 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000336
Tim Peters07e99cb2001-01-14 23:47:14 +0000337 'mechanism' specifies which authentication mechanism is to
338 be used - it must appear in <instance>.capabilities in the
339 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000342
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 It will be called to process server continuation responses.
346 It should return data that will be encoded and sent to server.
347 It should return None if the client abort response '*' should
348 be sent instead.
349 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000350 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000351 # XXX: shouldn't this code be removed, not commented out?
352 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000353 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000354 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000355 self.literal = _Authenticator(authobject).process
356 typ, dat = self._simple_command('AUTHENTICATE', mech)
357 if typ != 'OK':
358 raise self.error(dat[-1])
359 self.state = 'AUTH'
360 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000361
362
Piers Lauderd80ef022005-06-01 23:50:52 +0000363 def capability(self):
364 """(typ, [data]) = <instance>.capability()
365 Fetch capabilities list from server."""
366
367 name = 'CAPABILITY'
368 typ, dat = self._simple_command(name)
369 return self._untagged_response(typ, dat, name)
370
371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 def check(self):
373 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000374
Tim Peters07e99cb2001-01-14 23:47:14 +0000375 (typ, [data]) = <instance>.check()
376 """
377 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000378
379
Tim Peters07e99cb2001-01-14 23:47:14 +0000380 def close(self):
381 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 Deleted messages are removed from writable mailbox.
384 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
Tim Peters07e99cb2001-01-14 23:47:14 +0000386 (typ, [data]) = <instance>.close()
387 """
388 try:
389 typ, dat = self._simple_command('CLOSE')
390 finally:
391 self.state = 'AUTH'
392 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 def copy(self, message_set, new_mailbox):
396 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
399 """
400 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000401
402
Tim Peters07e99cb2001-01-14 23:47:14 +0000403 def create(self, mailbox):
404 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000405
Tim Peters07e99cb2001-01-14 23:47:14 +0000406 (typ, [data]) = <instance>.create(mailbox)
407 """
408 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000409
410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 def delete(self, mailbox):
412 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000413
Tim Peters07e99cb2001-01-14 23:47:14 +0000414 (typ, [data]) = <instance>.delete(mailbox)
415 """
416 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000418 def deleteacl(self, mailbox, who):
419 """Delete the ACLs (remove any rights) set for who on mailbox.
420
421 (typ, [data]) = <instance>.deleteacl(mailbox, who)
422 """
423 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000424
Tim Peters07e99cb2001-01-14 23:47:14 +0000425 def expunge(self):
426 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000431
Tim Peters07e99cb2001-01-14 23:47:14 +0000432 'data' is list of 'EXPUNGE'd message numbers in order received.
433 """
434 name = 'EXPUNGE'
435 typ, dat = self._simple_command(name)
436 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000437
438
Tim Peters07e99cb2001-01-14 23:47:14 +0000439 def fetch(self, message_set, message_parts):
440 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Tim Peters07e99cb2001-01-14 23:47:14 +0000442 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 'message_parts' should be a string of selected parts
445 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000446
Tim Peters07e99cb2001-01-14 23:47:14 +0000447 'data' are tuples of message part envelope and data.
448 """
449 name = 'FETCH'
450 typ, dat = self._simple_command(name, message_set, message_parts)
451 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000452
453
Piers Lauder15e5d532001-07-20 10:52:06 +0000454 def getacl(self, mailbox):
455 """Get the ACLs for a mailbox.
456
457 (typ, [data]) = <instance>.getacl(mailbox)
458 """
459 typ, dat = self._simple_command('GETACL', mailbox)
460 return self._untagged_response(typ, dat, 'ACL')
461
462
Piers Lauderd80ef022005-06-01 23:50:52 +0000463 def getannotation(self, mailbox, entry, attribute):
464 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
465 Retrieve ANNOTATIONs."""
466
467 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
468 return self._untagged_response(typ, dat, 'ANNOTATION')
469
470
Piers Lauder3fca2912002-06-17 07:07:20 +0000471 def getquota(self, root):
472 """Get the quota root's resource usage and limits.
473
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000474 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000475
476 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000477 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000478 typ, dat = self._simple_command('GETQUOTA', root)
479 return self._untagged_response(typ, dat, 'QUOTA')
480
481
482 def getquotaroot(self, mailbox):
483 """Get the list of quota roots for the named mailbox.
484
485 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000486 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000487 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000488 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
489 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000490 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000491
492
Tim Peters07e99cb2001-01-14 23:47:14 +0000493 def list(self, directory='""', pattern='*'):
494 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 'data' is list of LIST responses.
499 """
500 name = 'LIST'
501 typ, dat = self._simple_command(name, directory, pattern)
502 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000503
504
Tim Peters07e99cb2001-01-14 23:47:14 +0000505 def login(self, user, password):
506 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 NB: 'password' will be quoted.
511 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
513 if typ != 'OK':
514 raise self.error(dat[-1])
515 self.state = 'AUTH'
516 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000517
518
Piers Laudere0273de2002-11-22 05:53:04 +0000519 def login_cram_md5(self, user, password):
520 """ Force use of CRAM-MD5 authentication.
521
522 (typ, [data]) = <instance>.login_cram_md5(user, password)
523 """
524 self.user, self.password = user, password
525 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
526
527
528 def _CRAM_MD5_AUTH(self, challenge):
529 """ Authobject to use with CRAM-MD5 authentication. """
530 import hmac
531 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
532
533
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 def logout(self):
535 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000538
Tim Peters07e99cb2001-01-14 23:47:14 +0000539 Returns server 'BYE' response.
540 """
541 self.state = 'LOGOUT'
542 try: typ, dat = self._simple_command('LOGOUT')
543 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000544 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000545 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000546 return 'BYE', self.untagged_responses['BYE']
547 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000548
549
Tim Peters07e99cb2001-01-14 23:47:14 +0000550 def lsub(self, directory='""', pattern='*'):
551 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000552
Tim Peters07e99cb2001-01-14 23:47:14 +0000553 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000554
Tim Peters07e99cb2001-01-14 23:47:14 +0000555 'data' are tuples of message part envelope and data.
556 """
557 name = 'LSUB'
558 typ, dat = self._simple_command(name, directory, pattern)
559 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000560
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000561 def myrights(self, mailbox):
562 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
563
564 (typ, [data]) = <instance>.myrights(mailbox)
565 """
566 typ,dat = self._simple_command('MYRIGHTS', mailbox)
567 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000568
Piers Lauder15e5d532001-07-20 10:52:06 +0000569 def namespace(self):
570 """ Returns IMAP namespaces ala rfc2342
571
572 (typ, [data, ...]) = <instance>.namespace()
573 """
574 name = 'NAMESPACE'
575 typ, dat = self._simple_command(name)
576 return self._untagged_response(typ, dat, name)
577
578
Tim Peters07e99cb2001-01-14 23:47:14 +0000579 def noop(self):
580 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000581
Piers Laudere0273de2002-11-22 05:53:04 +0000582 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 """
584 if __debug__:
585 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000586 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000587 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000588
589
Tim Peters07e99cb2001-01-14 23:47:14 +0000590 def partial(self, message_num, message_part, start, length):
591 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000592
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 'data' is tuple of message part envelope and data.
596 """
597 name = 'PARTIAL'
598 typ, dat = self._simple_command(name, message_num, message_part, start, length)
599 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000600
601
Piers Laudere0273de2002-11-22 05:53:04 +0000602 def proxyauth(self, user):
603 """Assume authentication as "user".
604
605 Allows an authorised administrator to proxy into any user's
606 mailbox.
607
608 (typ, [data]) = <instance>.proxyauth(user)
609 """
610
611 name = 'PROXYAUTH'
612 return self._simple_command('PROXYAUTH', user)
613
614
Tim Peters07e99cb2001-01-14 23:47:14 +0000615 def rename(self, oldmailbox, newmailbox):
616 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000617
Piers Laudere0273de2002-11-22 05:53:04 +0000618 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000619 """
620 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000621
622
Tim Peters07e99cb2001-01-14 23:47:14 +0000623 def search(self, charset, *criteria):
624 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000625
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000626 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 'data' is space separated list of matching message numbers.
629 """
630 name = 'SEARCH'
631 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000632 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000633 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000634 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000635 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000636
637
Piers Lauder14f39402005-08-31 10:46:29 +0000638 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000640
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000642
Piers Lauder14f39402005-08-31 10:46:29 +0000643 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000644
Tim Peters07e99cb2001-01-14 23:47:14 +0000645 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000646
647 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
648 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 self.untagged_responses = {} # Flush old responses.
651 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000652 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 name = 'EXAMINE'
654 else:
655 name = 'SELECT'
656 typ, dat = self._simple_command(name, mailbox)
657 if typ != 'OK':
658 self.state = 'AUTH' # Might have been 'SELECTED'
659 return typ, dat
660 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000661 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000662 and not readonly:
663 if __debug__:
664 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000665 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000666 raise self.readonly('%s is not writable' % mailbox)
667 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000668
669
Piers Lauder15e5d532001-07-20 10:52:06 +0000670 def setacl(self, mailbox, who, what):
671 """Set a mailbox acl.
672
Piers Lauderf167dc32004-03-25 00:12:21 +0000673 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000674 """
675 return self._simple_command('SETACL', mailbox, who, what)
676
677
Piers Lauderd80ef022005-06-01 23:50:52 +0000678 def setannotation(self, *args):
679 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
680 Set ANNOTATIONs."""
681
682 typ, dat = self._simple_command('SETANNOTATION', *args)
683 return self._untagged_response(typ, dat, 'ANNOTATION')
684
685
Piers Lauder3fca2912002-06-17 07:07:20 +0000686 def setquota(self, root, limits):
687 """Set the quota root's resource limits.
688
689 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000690 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000691 typ, dat = self._simple_command('SETQUOTA', root, limits)
692 return self._untagged_response(typ, dat, 'QUOTA')
693
694
Piers Lauder15e5d532001-07-20 10:52:06 +0000695 def sort(self, sort_criteria, charset, *search_criteria):
696 """IMAP4rev1 extension SORT command.
697
698 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
699 """
700 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000701 #if not name in self.capabilities: # Let the server decide!
702 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000703 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000704 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000705 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000706 return self._untagged_response(typ, dat, name)
707
708
Tim Peters07e99cb2001-01-14 23:47:14 +0000709 def status(self, mailbox, names):
710 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000711
Tim Peters07e99cb2001-01-14 23:47:14 +0000712 (typ, [data]) = <instance>.status(mailbox, names)
713 """
714 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000715 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000716 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 typ, dat = self._simple_command(name, mailbox, names)
718 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000719
720
Tim Peters07e99cb2001-01-14 23:47:14 +0000721 def store(self, message_set, command, flags):
722 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000723
Tim Peters07e99cb2001-01-14 23:47:14 +0000724 (typ, [data]) = <instance>.store(message_set, command, flags)
725 """
726 if (flags[0],flags[-1]) != ('(',')'):
727 flags = '(%s)' % flags # Avoid quoting the flags
728 typ, dat = self._simple_command('STORE', message_set, command, flags)
729 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000730
731
Tim Peters07e99cb2001-01-14 23:47:14 +0000732 def subscribe(self, mailbox):
733 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000734
Tim Peters07e99cb2001-01-14 23:47:14 +0000735 (typ, [data]) = <instance>.subscribe(mailbox)
736 """
737 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000738
739
Martin v. Löwisd8921372003-11-10 06:44:44 +0000740 def thread(self, threading_algorithm, charset, *search_criteria):
741 """IMAPrev1 extension THREAD command.
742
743 (type, [data]) = <instance>.thread(threading_alogrithm, charset, search_criteria, ...)
744 """
745 name = 'THREAD'
746 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
747 return self._untagged_response(typ, dat, name)
748
749
Tim Peters07e99cb2001-01-14 23:47:14 +0000750 def uid(self, command, *args):
751 """Execute "command arg ..." with messages identified by UID,
752 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000753
Tim Peters07e99cb2001-01-14 23:47:14 +0000754 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 Returns response appropriate to 'command'.
757 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000758 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000759 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000760 raise self.error("Unknown IMAP4 UID command: %s" % command)
761 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000762 raise self.error("command %s illegal in state %s, "
763 "only allowed in states %s" %
764 (command, self.state,
765 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000767 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000768 if command in ('SEARCH', 'SORT'):
769 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000770 else:
771 name = 'FETCH'
772 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000773
774
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 def unsubscribe(self, mailbox):
776 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000777
Tim Peters07e99cb2001-01-14 23:47:14 +0000778 (typ, [data]) = <instance>.unsubscribe(mailbox)
779 """
780 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000781
782
Tim Peters07e99cb2001-01-14 23:47:14 +0000783 def xatom(self, name, *args):
784 """Allow simple extension commands
785 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000786
Piers Lauder15e5d532001-07-20 10:52:06 +0000787 Assumes command is legal in current state.
788
Tim Peters07e99cb2001-01-14 23:47:14 +0000789 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000790
791 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000793 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000794 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000795 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000796 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000797 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000798 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000799
800
801
Tim Peters07e99cb2001-01-14 23:47:14 +0000802 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000803
804
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000806 if dat is None:
807 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000808 ur = self.untagged_responses
809 if __debug__:
810 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000811 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000813 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 ur[typ].append(dat)
815 else:
816 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000817
818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 def _check_bye(self):
820 bye = self.untagged_responses.get('BYE')
821 if bye:
822 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000823
824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 if self.state not in Commands[name]:
828 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000829 raise self.error("command %s illegal in state %s, "
830 "only allowed in states %s" %
831 (name, self.state,
832 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000835 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000837
Raymond Hettinger54f02222002-06-01 14:18:47 +0000838 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 and not self.is_readonly:
840 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000841
Tim Peters07e99cb2001-01-14 23:47:14 +0000842 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000843 name = bytes(name, 'ASCII')
844 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000845 for arg in args:
846 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000847 if isinstance(arg, str):
848 arg = bytes(arg, "ASCII")
849 #data = data + b' ' + self._checkquote(arg)
850 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000851
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 literal = self.literal
853 if literal is not None:
854 self.literal = None
855 if type(literal) is type(self._command):
856 literator = literal
857 else:
858 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000859 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 if __debug__:
862 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000863 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000865 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000866
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000868 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000869 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 if literal is None:
873 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000874
Tim Peters07e99cb2001-01-14 23:47:14 +0000875 while 1:
876 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 while self._get_response():
879 if self.tagged_commands[tag]: # BAD/NO?
880 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 if literator:
885 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 if __debug__:
888 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000889 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000890
Tim Peters07e99cb2001-01-14 23:47:14 +0000891 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000892 self.send(literal)
893 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000894 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 if not literator:
898 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
Tim Peters07e99cb2001-01-14 23:47:14 +0000900 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
902
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 def _command_complete(self, name, tag):
904 self._check_bye()
905 try:
906 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000907 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000909 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 raise self.error('command: %s => %s' % (name, val))
911 self._check_bye()
912 if typ == 'BAD':
913 raise self.error('%s command error: %s %s' % (name, typ, data))
914 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000915
916
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 # Read response and store.
920 #
921 # Returns None for continuation responses,
922 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000923
Tim Peters07e99cb2001-01-14 23:47:14 +0000924 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 if self._match(self.tagre, resp):
929 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000930 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000934 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 dat = self.mo.group('data')
936 self.tagged_commands[tag] = (typ, [dat])
937 else:
938 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000939
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000941
Tim Peters07e99cb2001-01-14 23:47:14 +0000942 if not self._match(Untagged_response, resp):
943 if self._match(Untagged_status, resp):
944 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 if self.mo is None:
947 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 if self._match(Continuation, resp):
950 self.continuation_response = self.mo.group('data')
951 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000956 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000958 if dat is None: dat = b'' # Null untagged response
959 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000960
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000967 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 if __debug__:
969 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000970 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000971 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000986 typ = self.mo.group('type')
987 typ = str(typ, "ASCII")
988 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 if __debug__:
991 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000992 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000993
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000995
996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
Tim Peters07e99cb2001-01-14 23:47:14 +0000999 while 1:
1000 result = self.tagged_commands[tag]
1001 if result is not None:
1002 del self.tagged_commands[tag]
1003 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001004
Tim Peters07e99cb2001-01-14 23:47:14 +00001005 # Some have reported "unexpected response" exceptions.
1006 # Note that ignoring them here causes loops.
1007 # Instead, send me details of the unexpected response and
1008 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001009
Tim Peters07e99cb2001-01-14 23:47:14 +00001010 try:
1011 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001012 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 if __debug__:
1014 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001015 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
1018
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001020
Piers Lauder15e5d532001-07-20 10:52:06 +00001021 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 if not line:
1023 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001024
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001026
Tim Peters07e99cb2001-01-14 23:47:14 +00001027 line = line[:-2]
1028 if __debug__:
1029 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001030 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001031 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001032 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001034
1035
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 # Run compiled regular expression match method on 's'.
1039 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001040
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 self.mo = cre.match(s)
1042 if __debug__:
1043 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001044 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001045 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001046
1047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001049
Christian Heimesfb5faf02008-11-05 19:39:50 +00001050 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001051 self.tagnum = self.tagnum + 1
1052 self.tagged_commands[tag] = None
1053 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001054
1055
Tim Peters07e99cb2001-01-14 23:47:14 +00001056 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 # Must quote command args if non-alphanumeric chars present,
1059 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001060
Piers Lauderc09acfd2004-10-08 04:05:39 +00001061 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001062 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001063 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 return arg
1065 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001066
1067
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001069
Christian Heimesfb5faf02008-11-05 19:39:50 +00001070 arg = arg.replace(b'\\', b'\\\\')
1071 arg = arg.replace(b'"', b'\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001072
Christian Heimesfb5faf02008-11-05 19:39:50 +00001073 return b'"' + arg + b'"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001074
1075
Tim Peters07e99cb2001-01-14 23:47:14 +00001076 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001077
Guido van Rossum68468eb2003-02-27 20:14:51 +00001078 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
1080
Tim Peters07e99cb2001-01-14 23:47:14 +00001081 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001082 if typ == 'NO':
1083 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001084 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001085 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001086 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001087 if __debug__:
1088 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001089 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001090 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001091
1092
Piers Lauderf2d7d152002-02-22 01:15:17 +00001093 if __debug__:
1094
1095 def _mesg(self, s, secs=None):
1096 if secs is None:
1097 secs = time.time()
1098 tm = time.strftime('%M:%S', time.localtime(secs))
1099 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1100 sys.stderr.flush()
1101
1102 def _dump_ur(self, dict):
1103 # Dump untagged responses (in `dict').
1104 l = dict.items()
1105 if not l: return
1106 t = '\n\t\t'
1107 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1108 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1109
1110 def _log(self, line):
1111 # Keep log of last `_cmd_log_len' interactions for debugging.
1112 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1113 self._cmd_log_idx += 1
1114 if self._cmd_log_idx >= self._cmd_log_len:
1115 self._cmd_log_idx = 0
1116
1117 def print_log(self):
1118 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1119 i, n = self._cmd_log_idx, self._cmd_log_len
1120 while n:
1121 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001122 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001123 except:
1124 pass
1125 i += 1
1126 if i >= self._cmd_log_len:
1127 i = 0
1128 n -= 1
1129
1130
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001131
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001132try:
1133 import ssl
1134except ImportError:
1135 pass
1136else:
1137 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001138
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001139 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001140
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001141 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001142
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001143 host - host's name (default: localhost);
1144 port - port number (default: standard IMAP4 SSL port).
1145 keyfile - PEM formatted file that contains your private key (default: None);
1146 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001147
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001148 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001149 """
Piers Laudera4f83132002-03-08 01:53:24 +00001150
1151
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001152 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1153 self.keyfile = keyfile
1154 self.certfile = certfile
1155 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001156
Christian Heimesfb5faf02008-11-05 19:39:50 +00001157 def _create_socket(self):
1158 sock = IMAP4._create_socket(self)
1159 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001160
Christian Heimesfb5faf02008-11-05 19:39:50 +00001161 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001162 """Setup connection to remote server on "host:port".
1163 (default: localhost:standard IMAP4 SSL port).
1164 This connection will be used by the routines:
1165 read, readline, send, shutdown.
1166 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001167 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001168
1169 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001170
1171
Piers Laudere0273de2002-11-22 05:53:04 +00001172class IMAP4_stream(IMAP4):
1173
1174 """IMAP4 client class over a stream
1175
1176 Instantiate with: IMAP4_stream(command)
1177
Christian Heimesfb5faf02008-11-05 19:39:50 +00001178 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001179
1180 for more documentation see the docstring of the parent class IMAP4.
1181 """
1182
1183
1184 def __init__(self, command):
1185 self.command = command
1186 IMAP4.__init__(self)
1187
1188
1189 def open(self, host = None, port = None):
1190 """Setup a stream connection.
1191 This connection will be used by the routines:
1192 read, readline, send, shutdown.
1193 """
1194 self.host = None # For compatibility with parent class
1195 self.port = None
1196 self.sock = None
1197 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001198 self.process = subprocess.Popen(self.command,
1199 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1200 shell=True, close_fds=True)
1201 self.writefile = self.process.stdin
1202 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001203
1204 def read(self, size):
1205 """Read 'size' bytes from remote."""
1206 return self.readfile.read(size)
1207
1208
1209 def readline(self):
1210 """Read line from remote."""
1211 return self.readfile.readline()
1212
1213
1214 def send(self, data):
1215 """Send data to remote."""
1216 self.writefile.write(data)
1217 self.writefile.flush()
1218
1219
1220 def shutdown(self):
1221 """Close I/O established in "open"."""
1222 self.readfile.close()
1223 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001224 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001225
1226
1227
Guido van Rossumeda960a1998-06-18 14:24:28 +00001228class _Authenticator:
1229
Tim Peters07e99cb2001-01-14 23:47:14 +00001230 """Private class to provide en/decoding
1231 for base64-based authentication conversation.
1232 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001233
Tim Peters07e99cb2001-01-14 23:47:14 +00001234 def __init__(self, mechinst):
1235 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001236
Tim Peters07e99cb2001-01-14 23:47:14 +00001237 def process(self, data):
1238 ret = self.mech(self.decode(data))
1239 if ret is None:
1240 return '*' # Abort conversation
1241 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001242
Tim Peters07e99cb2001-01-14 23:47:14 +00001243 def encode(self, inp):
1244 #
1245 # Invoke binascii.b2a_base64 iteratively with
1246 # short even length buffers, strip the trailing
1247 # line feed from the result and append. "Even"
1248 # means a number that factors to both 6 and 8,
1249 # so when it gets to the end of the 8-bit input
1250 # there's no partial 6-bit output.
1251 #
1252 oup = ''
1253 while inp:
1254 if len(inp) > 48:
1255 t = inp[:48]
1256 inp = inp[48:]
1257 else:
1258 t = inp
1259 inp = ''
1260 e = binascii.b2a_base64(t)
1261 if e:
1262 oup = oup + e[:-1]
1263 return oup
1264
1265 def decode(self, inp):
1266 if not inp:
1267 return ''
1268 return binascii.a2b_base64(inp)
1269
Guido van Rossumeda960a1998-06-18 14:24:28 +00001270
1271
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001272Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001273 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001274
1275def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001277
Tim Peters07e99cb2001-01-14 23:47:14 +00001278 Returns Python time module tuple.
1279 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001280
Tim Peters07e99cb2001-01-14 23:47:14 +00001281 mo = InternalDate.match(resp)
1282 if not mo:
1283 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001284
Tim Peters07e99cb2001-01-14 23:47:14 +00001285 mon = Mon2num[mo.group('mon')]
1286 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001287
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001288 day = int(mo.group('day'))
1289 year = int(mo.group('year'))
1290 hour = int(mo.group('hour'))
1291 min = int(mo.group('min'))
1292 sec = int(mo.group('sec'))
1293 zoneh = int(mo.group('zoneh'))
1294 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001295
Tim Peters07e99cb2001-01-14 23:47:14 +00001296 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001297
Tim Peters07e99cb2001-01-14 23:47:14 +00001298 zone = (zoneh*60 + zonem)*60
1299 if zonen == '-':
1300 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001301
Tim Peters07e99cb2001-01-14 23:47:14 +00001302 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001303
Tim Peters07e99cb2001-01-14 23:47:14 +00001304 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001305
Tim Peters07e99cb2001-01-14 23:47:14 +00001306 # Following is necessary because the time module has no 'mkgmtime'.
1307 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001308
Tim Peters07e99cb2001-01-14 23:47:14 +00001309 lt = time.localtime(utc)
1310 if time.daylight and lt[-1]:
1311 zone = zone + time.altzone
1312 else:
1313 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001314
Tim Peters07e99cb2001-01-14 23:47:14 +00001315 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001316
1317
1318
1319def Int2AP(num):
1320
Tim Peters07e99cb2001-01-14 23:47:14 +00001321 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322
Christian Heimesfb5faf02008-11-05 19:39:50 +00001323 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001324 num = int(abs(num))
1325 while num:
1326 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001327 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001328 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001329
1330
1331
1332def ParseFlags(resp):
1333
Tim Peters07e99cb2001-01-14 23:47:14 +00001334 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001335
Tim Peters07e99cb2001-01-14 23:47:14 +00001336 mo = Flags.match(resp)
1337 if not mo:
1338 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001339
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001340 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001341
1342
1343def Time2Internaldate(date_time):
1344
Tim Peters07e99cb2001-01-14 23:47:14 +00001345 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001346
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1348 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001349
Fred Drakedb519202002-01-05 17:17:09 +00001350 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001351 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001352 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001353 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001354 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001356 else:
1357 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
Tim Peters07e99cb2001-01-14 23:47:14 +00001359 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1360 if dt[0] == '0':
1361 dt = ' ' + dt[1:]
1362 if time.daylight and tt[-1]:
1363 zone = -time.altzone
1364 else:
1365 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001366 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001367
1368
1369
Guido van Rossum8c062211999-12-13 23:27:45 +00001370if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001371
Piers Laudere0273de2002-11-22 05:53:04 +00001372 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1373 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1374 # to test the IMAP4_stream class
1375
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001376 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001377
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001379 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001380 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001381 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001382
Piers Laudere0273de2002-11-22 05:53:04 +00001383 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001384 for opt,val in optlist:
1385 if opt == '-d':
1386 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001387 elif opt == '-s':
1388 stream_command = val
1389 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001390
Tim Peters07e99cb2001-01-14 23:47:14 +00001391 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001392
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001394
Tim Peters07e99cb2001-01-14 23:47:14 +00001395 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001396 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001397
Piers Lauder47404ff2003-04-29 23:40:59 +00001398 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 +00001399 test_seq1 = (
1400 ('login', (USER, PASSWD)),
1401 ('create', ('/tmp/xxx 1',)),
1402 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1403 ('CREATE', ('/tmp/yyz 2',)),
1404 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1405 ('list', ('/tmp', 'yy*')),
1406 ('select', ('/tmp/yyz 2',)),
1407 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001408 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001410 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001411 ('expunge', ()),
1412 ('recent', ()),
1413 ('close', ()),
1414 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001415
Tim Peters07e99cb2001-01-14 23:47:14 +00001416 test_seq2 = (
1417 ('select', ()),
1418 ('response',('UIDVALIDITY',)),
1419 ('uid', ('SEARCH', 'ALL')),
1420 ('response', ('EXISTS',)),
1421 ('append', (None, None, None, test_mesg)),
1422 ('recent', ()),
1423 ('logout', ()),
1424 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001425
Tim Peters07e99cb2001-01-14 23:47:14 +00001426 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001427 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001428 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001429 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001430 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001431 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001434 if stream_command:
1435 M = IMAP4_stream(stream_command)
1436 else:
1437 M = IMAP4(host)
1438 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001439 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001440 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001441 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001442
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 for cmd,args in test_seq1:
1444 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001445
Tim Peters07e99cb2001-01-14 23:47:14 +00001446 for ml in run('list', ('/tmp/', 'yy%')):
1447 mo = re.match(r'.*"([^"]+)"$', ml)
1448 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001449 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001450 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001451
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 for cmd,args in test_seq2:
1453 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001454
Tim Peters07e99cb2001-01-14 23:47:14 +00001455 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1456 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001457
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001458 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001459 if not uid: continue
1460 run('uid', ('FETCH', '%s' % uid[-1],
1461 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001462
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001463 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001464
Tim Peters07e99cb2001-01-14 23:47:14 +00001465 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001466 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001467
Tim Peters07e99cb2001-01-14 23:47:14 +00001468 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001469 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001470If you would like to see debugging output,
1471try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001472''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001473
Tim Peters07e99cb2001-01-14 23:47:14 +00001474 raise