blob: 6469df2dcf04aa16dd9cf12c2c41e522e04025c3 [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):
225 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
226 sock.connect((self.host, self.port))
227 return sock
228
Piers Lauderf97b2d72002-06-05 22:31:57 +0000229 def open(self, host = '', port = IMAP4_PORT):
230 """Setup connection to remote server on "host:port"
231 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000232 This connection will be used by the routines:
233 read, readline, send, shutdown.
234 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000235 self.host = host
236 self.port = port
Christian Heimesfb5faf02008-11-05 19:39:50 +0000237 self.sock = self._create_socket()
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000238 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000239
240
Piers Lauder15e5d532001-07-20 10:52:06 +0000241 def read(self, size):
242 """Read 'size' bytes from remote."""
Christian Heimesfb5faf02008-11-05 19:39:50 +0000243 chunks = []
244 read = 0
245 while read < size:
246 data = self.file.read(min(size-read, 4096))
247 if not data:
248 break
249 read += len(data)
250 chunks.append(data)
251 return b''.join(chunks)
Piers Lauder15e5d532001-07-20 10:52:06 +0000252
253
254 def readline(self):
255 """Read line from remote."""
256 return self.file.readline()
257
258
259 def send(self, data):
260 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000261 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000262
Piers Lauderf2d7d152002-02-22 01:15:17 +0000263
Piers Lauder15e5d532001-07-20 10:52:06 +0000264 def shutdown(self):
265 """Close I/O established in "open"."""
266 self.file.close()
267 self.sock.close()
268
269
270 def socket(self):
271 """Return socket instance used to connect to IMAP4 server.
272
273 socket = <instance>.socket()
274 """
275 return self.sock
276
277
278
279 # Utility methods
280
281
Tim Peters07e99cb2001-01-14 23:47:14 +0000282 def recent(self):
283 """Return most recent 'RECENT' responses if any exist,
284 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000285
Tim Peters07e99cb2001-01-14 23:47:14 +0000286 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000287
Tim Peters07e99cb2001-01-14 23:47:14 +0000288 'data' is None if no new messages,
289 else list of RECENT responses, most recent last.
290 """
291 name = 'RECENT'
292 typ, dat = self._untagged_response('OK', [None], name)
293 if dat[-1]:
294 return typ, dat
295 typ, dat = self.noop() # Prod server for response
296 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000297
298
Tim Peters07e99cb2001-01-14 23:47:14 +0000299 def response(self, code):
300 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000301
Tim Peters07e99cb2001-01-14 23:47:14 +0000302 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000303
Tim Peters07e99cb2001-01-14 23:47:14 +0000304 (code, [data]) = <instance>.response(code)
305 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000306 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000307
308
Guido van Rossum26367a01998-09-28 15:34:46 +0000309
Tim Peters07e99cb2001-01-14 23:47:14 +0000310 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000311
312
Tim Peters07e99cb2001-01-14 23:47:14 +0000313 def append(self, mailbox, flags, date_time, message):
314 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000315
Tim Peters07e99cb2001-01-14 23:47:14 +0000316 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000317
Tim Peters07e99cb2001-01-14 23:47:14 +0000318 All args except `message' can be None.
319 """
320 name = 'APPEND'
321 if not mailbox:
322 mailbox = 'INBOX'
323 if flags:
324 if (flags[0],flags[-1]) != ('(',')'):
325 flags = '(%s)' % flags
326 else:
327 flags = None
328 if date_time:
329 date_time = Time2Internaldate(date_time)
330 else:
331 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000332 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000334
335
Tim Peters07e99cb2001-01-14 23:47:14 +0000336 def authenticate(self, mechanism, authobject):
337 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000338
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 'mechanism' specifies which authentication mechanism is to
340 be used - it must appear in <instance>.capabilities in the
341 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000342
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000344
Tim Peters07e99cb2001-01-14 23:47:14 +0000345 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000346
Tim Peters07e99cb2001-01-14 23:47:14 +0000347 It will be called to process server continuation responses.
348 It should return data that will be encoded and sent to server.
349 It should return None if the client abort response '*' should
350 be sent instead.
351 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000352 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000353 # XXX: shouldn't this code be removed, not commented out?
354 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000355 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000356 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000357 self.literal = _Authenticator(authobject).process
358 typ, dat = self._simple_command('AUTHENTICATE', mech)
359 if typ != 'OK':
360 raise self.error(dat[-1])
361 self.state = 'AUTH'
362 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000363
364
Piers Lauderd80ef022005-06-01 23:50:52 +0000365 def capability(self):
366 """(typ, [data]) = <instance>.capability()
367 Fetch capabilities list from server."""
368
369 name = 'CAPABILITY'
370 typ, dat = self._simple_command(name)
371 return self._untagged_response(typ, dat, name)
372
373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 def check(self):
375 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000376
Tim Peters07e99cb2001-01-14 23:47:14 +0000377 (typ, [data]) = <instance>.check()
378 """
379 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000380
381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 def close(self):
383 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 Deleted messages are removed from writable mailbox.
386 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000387
Tim Peters07e99cb2001-01-14 23:47:14 +0000388 (typ, [data]) = <instance>.close()
389 """
390 try:
391 typ, dat = self._simple_command('CLOSE')
392 finally:
393 self.state = 'AUTH'
394 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000395
396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 def copy(self, message_set, new_mailbox):
398 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
Tim Peters07e99cb2001-01-14 23:47:14 +0000400 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
401 """
402 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
404
Tim Peters07e99cb2001-01-14 23:47:14 +0000405 def create(self, mailbox):
406 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
Tim Peters07e99cb2001-01-14 23:47:14 +0000408 (typ, [data]) = <instance>.create(mailbox)
409 """
410 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 def delete(self, mailbox):
414 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 (typ, [data]) = <instance>.delete(mailbox)
417 """
418 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000419
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000420 def deleteacl(self, mailbox, who):
421 """Delete the ACLs (remove any rights) set for who on mailbox.
422
423 (typ, [data]) = <instance>.deleteacl(mailbox, who)
424 """
425 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000426
Tim Peters07e99cb2001-01-14 23:47:14 +0000427 def expunge(self):
428 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000431
Tim Peters07e99cb2001-01-14 23:47:14 +0000432 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000433
Tim Peters07e99cb2001-01-14 23:47:14 +0000434 'data' is list of 'EXPUNGE'd message numbers in order received.
435 """
436 name = 'EXPUNGE'
437 typ, dat = self._simple_command(name)
438 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000439
440
Tim Peters07e99cb2001-01-14 23:47:14 +0000441 def fetch(self, message_set, message_parts):
442 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000443
Tim Peters07e99cb2001-01-14 23:47:14 +0000444 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000445
Tim Peters07e99cb2001-01-14 23:47:14 +0000446 'message_parts' should be a string of selected parts
447 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000448
Tim Peters07e99cb2001-01-14 23:47:14 +0000449 'data' are tuples of message part envelope and data.
450 """
451 name = 'FETCH'
452 typ, dat = self._simple_command(name, message_set, message_parts)
453 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000454
455
Piers Lauder15e5d532001-07-20 10:52:06 +0000456 def getacl(self, mailbox):
457 """Get the ACLs for a mailbox.
458
459 (typ, [data]) = <instance>.getacl(mailbox)
460 """
461 typ, dat = self._simple_command('GETACL', mailbox)
462 return self._untagged_response(typ, dat, 'ACL')
463
464
Piers Lauderd80ef022005-06-01 23:50:52 +0000465 def getannotation(self, mailbox, entry, attribute):
466 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
467 Retrieve ANNOTATIONs."""
468
469 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
470 return self._untagged_response(typ, dat, 'ANNOTATION')
471
472
Piers Lauder3fca2912002-06-17 07:07:20 +0000473 def getquota(self, root):
474 """Get the quota root's resource usage and limits.
475
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000476 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000477
478 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000479 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000480 typ, dat = self._simple_command('GETQUOTA', root)
481 return self._untagged_response(typ, dat, 'QUOTA')
482
483
484 def getquotaroot(self, mailbox):
485 """Get the list of quota roots for the named mailbox.
486
487 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000488 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000489 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000490 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
491 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000492 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000493
494
Tim Peters07e99cb2001-01-14 23:47:14 +0000495 def list(self, directory='""', pattern='*'):
496 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000499
Tim Peters07e99cb2001-01-14 23:47:14 +0000500 'data' is list of LIST responses.
501 """
502 name = 'LIST'
503 typ, dat = self._simple_command(name, directory, pattern)
504 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
506
Tim Peters07e99cb2001-01-14 23:47:14 +0000507 def login(self, user, password):
508 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 NB: 'password' will be quoted.
513 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000514 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
515 if typ != 'OK':
516 raise self.error(dat[-1])
517 self.state = 'AUTH'
518 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000519
520
Piers Laudere0273de2002-11-22 05:53:04 +0000521 def login_cram_md5(self, user, password):
522 """ Force use of CRAM-MD5 authentication.
523
524 (typ, [data]) = <instance>.login_cram_md5(user, password)
525 """
526 self.user, self.password = user, password
527 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
528
529
530 def _CRAM_MD5_AUTH(self, challenge):
531 """ Authobject to use with CRAM-MD5 authentication. """
532 import hmac
533 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
534
535
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 def logout(self):
537 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000538
Tim Peters07e99cb2001-01-14 23:47:14 +0000539 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000540
Tim Peters07e99cb2001-01-14 23:47:14 +0000541 Returns server 'BYE' response.
542 """
543 self.state = 'LOGOUT'
544 try: typ, dat = self._simple_command('LOGOUT')
545 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000546 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000547 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000548 return 'BYE', self.untagged_responses['BYE']
549 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000550
551
Tim Peters07e99cb2001-01-14 23:47:14 +0000552 def lsub(self, directory='""', pattern='*'):
553 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000554
Tim Peters07e99cb2001-01-14 23:47:14 +0000555 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000556
Tim Peters07e99cb2001-01-14 23:47:14 +0000557 'data' are tuples of message part envelope and data.
558 """
559 name = 'LSUB'
560 typ, dat = self._simple_command(name, directory, pattern)
561 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000562
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000563 def myrights(self, mailbox):
564 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
565
566 (typ, [data]) = <instance>.myrights(mailbox)
567 """
568 typ,dat = self._simple_command('MYRIGHTS', mailbox)
569 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000570
Piers Lauder15e5d532001-07-20 10:52:06 +0000571 def namespace(self):
572 """ Returns IMAP namespaces ala rfc2342
573
574 (typ, [data, ...]) = <instance>.namespace()
575 """
576 name = 'NAMESPACE'
577 typ, dat = self._simple_command(name)
578 return self._untagged_response(typ, dat, name)
579
580
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 def noop(self):
582 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000583
Piers Laudere0273de2002-11-22 05:53:04 +0000584 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 """
586 if __debug__:
587 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000588 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000589 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000590
591
Tim Peters07e99cb2001-01-14 23:47:14 +0000592 def partial(self, message_num, message_part, start, length):
593 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000596
Tim Peters07e99cb2001-01-14 23:47:14 +0000597 'data' is tuple of message part envelope and data.
598 """
599 name = 'PARTIAL'
600 typ, dat = self._simple_command(name, message_num, message_part, start, length)
601 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000602
603
Piers Laudere0273de2002-11-22 05:53:04 +0000604 def proxyauth(self, user):
605 """Assume authentication as "user".
606
607 Allows an authorised administrator to proxy into any user's
608 mailbox.
609
610 (typ, [data]) = <instance>.proxyauth(user)
611 """
612
613 name = 'PROXYAUTH'
614 return self._simple_command('PROXYAUTH', user)
615
616
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 def rename(self, oldmailbox, newmailbox):
618 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000619
Piers Laudere0273de2002-11-22 05:53:04 +0000620 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 """
622 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000623
624
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 def search(self, charset, *criteria):
626 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000628 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000629
Tim Peters07e99cb2001-01-14 23:47:14 +0000630 'data' is space separated list of matching message numbers.
631 """
632 name = 'SEARCH'
633 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000634 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000635 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000636 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
639
Piers Lauder14f39402005-08-31 10:46:29 +0000640 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
Tim Peters07e99cb2001-01-14 23:47:14 +0000643 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000644
Piers Lauder14f39402005-08-31 10:46:29 +0000645 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000646
Tim Peters07e99cb2001-01-14 23:47:14 +0000647 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000648
649 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
650 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000652 self.untagged_responses = {} # Flush old responses.
653 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000654 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000655 name = 'EXAMINE'
656 else:
657 name = 'SELECT'
658 typ, dat = self._simple_command(name, mailbox)
659 if typ != 'OK':
660 self.state = 'AUTH' # Might have been 'SELECTED'
661 return typ, dat
662 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000663 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 and not readonly:
665 if __debug__:
666 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000667 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000668 raise self.readonly('%s is not writable' % mailbox)
669 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000670
671
Piers Lauder15e5d532001-07-20 10:52:06 +0000672 def setacl(self, mailbox, who, what):
673 """Set a mailbox acl.
674
Piers Lauderf167dc32004-03-25 00:12:21 +0000675 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000676 """
677 return self._simple_command('SETACL', mailbox, who, what)
678
679
Piers Lauderd80ef022005-06-01 23:50:52 +0000680 def setannotation(self, *args):
681 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
682 Set ANNOTATIONs."""
683
684 typ, dat = self._simple_command('SETANNOTATION', *args)
685 return self._untagged_response(typ, dat, 'ANNOTATION')
686
687
Piers Lauder3fca2912002-06-17 07:07:20 +0000688 def setquota(self, root, limits):
689 """Set the quota root's resource limits.
690
691 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000692 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000693 typ, dat = self._simple_command('SETQUOTA', root, limits)
694 return self._untagged_response(typ, dat, 'QUOTA')
695
696
Piers Lauder15e5d532001-07-20 10:52:06 +0000697 def sort(self, sort_criteria, charset, *search_criteria):
698 """IMAP4rev1 extension SORT command.
699
700 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
701 """
702 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000703 #if not name in self.capabilities: # Let the server decide!
704 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000705 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000706 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000707 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000708 return self._untagged_response(typ, dat, name)
709
710
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 def status(self, mailbox, names):
712 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000713
Tim Peters07e99cb2001-01-14 23:47:14 +0000714 (typ, [data]) = <instance>.status(mailbox, names)
715 """
716 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000717 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000718 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 typ, dat = self._simple_command(name, mailbox, names)
720 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000721
722
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 def store(self, message_set, command, flags):
724 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000725
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 (typ, [data]) = <instance>.store(message_set, command, flags)
727 """
728 if (flags[0],flags[-1]) != ('(',')'):
729 flags = '(%s)' % flags # Avoid quoting the flags
730 typ, dat = self._simple_command('STORE', message_set, command, flags)
731 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000732
733
Tim Peters07e99cb2001-01-14 23:47:14 +0000734 def subscribe(self, mailbox):
735 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 (typ, [data]) = <instance>.subscribe(mailbox)
738 """
739 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000740
741
Martin v. Löwisd8921372003-11-10 06:44:44 +0000742 def thread(self, threading_algorithm, charset, *search_criteria):
743 """IMAPrev1 extension THREAD command.
744
745 (type, [data]) = <instance>.thread(threading_alogrithm, charset, search_criteria, ...)
746 """
747 name = 'THREAD'
748 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
749 return self._untagged_response(typ, dat, name)
750
751
Tim Peters07e99cb2001-01-14 23:47:14 +0000752 def uid(self, command, *args):
753 """Execute "command arg ..." with messages identified by UID,
754 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000755
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000757
Tim Peters07e99cb2001-01-14 23:47:14 +0000758 Returns response appropriate to 'command'.
759 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000760 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000761 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 raise self.error("Unknown IMAP4 UID command: %s" % command)
763 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000764 raise self.error("command %s illegal in state %s, "
765 "only allowed in states %s" %
766 (command, self.state,
767 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000769 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000770 if command in ('SEARCH', 'SORT'):
771 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000772 else:
773 name = 'FETCH'
774 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000775
776
Tim Peters07e99cb2001-01-14 23:47:14 +0000777 def unsubscribe(self, mailbox):
778 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000779
Tim Peters07e99cb2001-01-14 23:47:14 +0000780 (typ, [data]) = <instance>.unsubscribe(mailbox)
781 """
782 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000783
784
Tim Peters07e99cb2001-01-14 23:47:14 +0000785 def xatom(self, name, *args):
786 """Allow simple extension commands
787 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000788
Piers Lauder15e5d532001-07-20 10:52:06 +0000789 Assumes command is legal in current state.
790
Tim Peters07e99cb2001-01-14 23:47:14 +0000791 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000792
793 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000795 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000796 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000797 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000798 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000799 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000800 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000801
802
803
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000805
806
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 def _append_untagged(self, typ, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000808 if dat is None:
809 dat = b''
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 ur = self.untagged_responses
811 if __debug__:
812 if self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000813 self._mesg('untagged_responses[%s] %s += ["%r"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000815 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000816 ur[typ].append(dat)
817 else:
818 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
820
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 def _check_bye(self):
822 bye = self.untagged_responses.get('BYE')
823 if bye:
824 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000825
826
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000828
Tim Peters07e99cb2001-01-14 23:47:14 +0000829 if self.state not in Commands[name]:
830 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000831 raise self.error("command %s illegal in state %s, "
832 "only allowed in states %s" %
833 (name, self.state,
834 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000837 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000838 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000839
Raymond Hettinger54f02222002-06-01 14:18:47 +0000840 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000841 and not self.is_readonly:
842 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000843
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 tag = self._new_tag()
Christian Heimesfb5faf02008-11-05 19:39:50 +0000845 name = bytes(name, 'ASCII')
846 data = tag + b' ' + name
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 for arg in args:
848 if arg is None: continue
Christian Heimesfb5faf02008-11-05 19:39:50 +0000849 if isinstance(arg, str):
850 arg = bytes(arg, "ASCII")
851 #data = data + b' ' + self._checkquote(arg)
852 data = data + b' ' + arg
Guido van Rossum6884af71998-05-29 13:34:03 +0000853
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 literal = self.literal
855 if literal is not None:
856 self.literal = None
857 if type(literal) is type(self._command):
858 literator = literal
859 else:
860 literator = None
Christian Heimesfb5faf02008-11-05 19:39:50 +0000861 data = data + bytes(' {%s}' % len(literal), 'ASCII')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 if __debug__:
864 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000865 self._mesg('> %r' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000867 self._log('> %r' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 try:
Christian Heimesfb5faf02008-11-05 19:39:50 +0000870 self.send(data + CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000871 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000873
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 if literal is None:
875 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000876
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 while 1:
878 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 while self._get_response():
881 if self.tagged_commands[tag]: # BAD/NO?
882 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 if literator:
887 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000888
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 if __debug__:
890 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000891 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000894 self.send(literal)
895 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000896 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000898
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 if not literator:
900 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
Tim Peters07e99cb2001-01-14 23:47:14 +0000902 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000903
904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 def _command_complete(self, name, tag):
906 self._check_bye()
907 try:
908 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000909 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000911 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 raise self.error('command: %s => %s' % (name, val))
913 self._check_bye()
914 if typ == 'BAD':
915 raise self.error('%s command error: %s %s' % (name, typ, data))
916 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000917
918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 # Read response and store.
922 #
923 # Returns None for continuation responses,
924 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000925
Tim Peters07e99cb2001-01-14 23:47:14 +0000926 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 if self._match(self.tagre, resp):
931 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000932 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000936 typ = str(typ, 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 dat = self.mo.group('data')
938 self.tagged_commands[tag] = (typ, [dat])
939 else:
940 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000941
Tim Peters07e99cb2001-01-14 23:47:14 +0000942 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000943
Tim Peters07e99cb2001-01-14 23:47:14 +0000944 if not self._match(Untagged_response, resp):
945 if self._match(Untagged_status, resp):
946 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000947
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 if self.mo is None:
949 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000950
Tim Peters07e99cb2001-01-14 23:47:14 +0000951 if self._match(Continuation, resp):
952 self.continuation_response = self.mo.group('data')
953 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 typ = self.mo.group('type')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000958 typ = str(typ, 'ascii')
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 dat = self.mo.group('data')
Christian Heimesfb5faf02008-11-05 19:39:50 +0000960 if dat is None: dat = b'' # Null untagged response
961 if dat2: dat = dat + b' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000969 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 if __debug__:
971 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000972 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000973 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000982
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000988 typ = self.mo.group('type')
989 typ = str(typ, "ASCII")
990 self._append_untagged(typ, self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000991
Tim Peters07e99cb2001-01-14 23:47:14 +0000992 if __debug__:
993 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Christian Heimesfb5faf02008-11-05 19:39:50 +0000994 self._mesg('%s response: %r' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000995
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000997
998
Tim Peters07e99cb2001-01-14 23:47:14 +0000999 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001000
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 while 1:
1002 result = self.tagged_commands[tag]
1003 if result is not None:
1004 del self.tagged_commands[tag]
1005 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +00001006
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 # Some have reported "unexpected response" exceptions.
1008 # Note that ignoring them here causes loops.
1009 # Instead, send me details of the unexpected response and
1010 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +00001011
Tim Peters07e99cb2001-01-14 23:47:14 +00001012 try:
1013 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +00001014 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +00001015 if __debug__:
1016 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001017 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001019
1020
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001022
Piers Lauder15e5d532001-07-20 10:52:06 +00001023 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001024 if not line:
1025 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001026
Tim Peters07e99cb2001-01-14 23:47:14 +00001027 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001028
Tim Peters07e99cb2001-01-14 23:47:14 +00001029 line = line[:-2]
1030 if __debug__:
1031 if self.debug >= 4:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001032 self._mesg('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 else:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001034 self._log('< %r' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001035 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001036
1037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 # Run compiled regular expression match method on 's'.
1041 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001042
Tim Peters07e99cb2001-01-14 23:47:14 +00001043 self.mo = cre.match(s)
1044 if __debug__:
1045 if self.mo is not None and self.debug >= 5:
Christian Heimesfb5faf02008-11-05 19:39:50 +00001046 self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001047 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001048
1049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001051
Christian Heimesfb5faf02008-11-05 19:39:50 +00001052 tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 self.tagnum = self.tagnum + 1
1054 self.tagged_commands[tag] = None
1055 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001056
1057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001059
Tim Peters07e99cb2001-01-14 23:47:14 +00001060 # Must quote command args if non-alphanumeric chars present,
1061 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001062
Piers Lauderc09acfd2004-10-08 04:05:39 +00001063 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001064 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001065 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001066 return arg
1067 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001068
1069
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001071
Christian Heimesfb5faf02008-11-05 19:39:50 +00001072 arg = arg.replace(b'\\', b'\\\\')
1073 arg = arg.replace(b'"', b'\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001074
Christian Heimesfb5faf02008-11-05 19:39:50 +00001075 return b'"' + arg + b'"'
Guido van Rossum8c062211999-12-13 23:27:45 +00001076
1077
Tim Peters07e99cb2001-01-14 23:47:14 +00001078 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001079
Guido van Rossum68468eb2003-02-27 20:14:51 +00001080 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001081
1082
Tim Peters07e99cb2001-01-14 23:47:14 +00001083 def _untagged_response(self, typ, dat, name):
Tim Peters07e99cb2001-01-14 23:47:14 +00001084 if typ == 'NO':
1085 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001086 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001087 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001088 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001089 if __debug__:
1090 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001091 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001092 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001093
1094
Piers Lauderf2d7d152002-02-22 01:15:17 +00001095 if __debug__:
1096
1097 def _mesg(self, s, secs=None):
1098 if secs is None:
1099 secs = time.time()
1100 tm = time.strftime('%M:%S', time.localtime(secs))
1101 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1102 sys.stderr.flush()
1103
1104 def _dump_ur(self, dict):
1105 # Dump untagged responses (in `dict').
1106 l = dict.items()
1107 if not l: return
1108 t = '\n\t\t'
1109 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1110 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1111
1112 def _log(self, line):
1113 # Keep log of last `_cmd_log_len' interactions for debugging.
1114 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1115 self._cmd_log_idx += 1
1116 if self._cmd_log_idx >= self._cmd_log_len:
1117 self._cmd_log_idx = 0
1118
1119 def print_log(self):
1120 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1121 i, n = self._cmd_log_idx, self._cmd_log_len
1122 while n:
1123 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001124 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001125 except:
1126 pass
1127 i += 1
1128 if i >= self._cmd_log_len:
1129 i = 0
1130 n -= 1
1131
1132
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001133
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001134try:
1135 import ssl
1136except ImportError:
1137 pass
1138else:
1139 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001140
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001141 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001142
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001143 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001144
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001145 host - host's name (default: localhost);
1146 port - port number (default: standard IMAP4 SSL port).
1147 keyfile - PEM formatted file that contains your private key (default: None);
1148 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001149
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001150 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001151 """
Piers Laudera4f83132002-03-08 01:53:24 +00001152
1153
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001154 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1155 self.keyfile = keyfile
1156 self.certfile = certfile
1157 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001158
Christian Heimesfb5faf02008-11-05 19:39:50 +00001159 def _create_socket(self):
1160 sock = IMAP4._create_socket(self)
1161 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001162
Christian Heimesfb5faf02008-11-05 19:39:50 +00001163 def open(self, host='', port=IMAP4_SSL_PORT):
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001164 """Setup connection to remote server on "host:port".
1165 (default: localhost:standard IMAP4 SSL port).
1166 This connection will be used by the routines:
1167 read, readline, send, shutdown.
1168 """
Christian Heimesfb5faf02008-11-05 19:39:50 +00001169 IMAP4.open(self, host, port)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001170
1171 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001172
1173
Piers Laudere0273de2002-11-22 05:53:04 +00001174class IMAP4_stream(IMAP4):
1175
1176 """IMAP4 client class over a stream
1177
1178 Instantiate with: IMAP4_stream(command)
1179
Christian Heimesfb5faf02008-11-05 19:39:50 +00001180 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001181
1182 for more documentation see the docstring of the parent class IMAP4.
1183 """
1184
1185
1186 def __init__(self, command):
1187 self.command = command
1188 IMAP4.__init__(self)
1189
1190
1191 def open(self, host = None, port = None):
1192 """Setup a stream connection.
1193 This connection will be used by the routines:
1194 read, readline, send, shutdown.
1195 """
1196 self.host = None # For compatibility with parent class
1197 self.port = None
1198 self.sock = None
1199 self.file = None
Christian Heimesfb5faf02008-11-05 19:39:50 +00001200 self.process = subprocess.Popen(self.command,
1201 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1202 shell=True, close_fds=True)
1203 self.writefile = self.process.stdin
1204 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001205
1206 def read(self, size):
1207 """Read 'size' bytes from remote."""
1208 return self.readfile.read(size)
1209
1210
1211 def readline(self):
1212 """Read line from remote."""
1213 return self.readfile.readline()
1214
1215
1216 def send(self, data):
1217 """Send data to remote."""
1218 self.writefile.write(data)
1219 self.writefile.flush()
1220
1221
1222 def shutdown(self):
1223 """Close I/O established in "open"."""
1224 self.readfile.close()
1225 self.writefile.close()
Christian Heimesfb5faf02008-11-05 19:39:50 +00001226 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001227
1228
1229
Guido van Rossumeda960a1998-06-18 14:24:28 +00001230class _Authenticator:
1231
Tim Peters07e99cb2001-01-14 23:47:14 +00001232 """Private class to provide en/decoding
1233 for base64-based authentication conversation.
1234 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001235
Tim Peters07e99cb2001-01-14 23:47:14 +00001236 def __init__(self, mechinst):
1237 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001238
Tim Peters07e99cb2001-01-14 23:47:14 +00001239 def process(self, data):
1240 ret = self.mech(self.decode(data))
1241 if ret is None:
1242 return '*' # Abort conversation
1243 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001244
Tim Peters07e99cb2001-01-14 23:47:14 +00001245 def encode(self, inp):
1246 #
1247 # Invoke binascii.b2a_base64 iteratively with
1248 # short even length buffers, strip the trailing
1249 # line feed from the result and append. "Even"
1250 # means a number that factors to both 6 and 8,
1251 # so when it gets to the end of the 8-bit input
1252 # there's no partial 6-bit output.
1253 #
1254 oup = ''
1255 while inp:
1256 if len(inp) > 48:
1257 t = inp[:48]
1258 inp = inp[48:]
1259 else:
1260 t = inp
1261 inp = ''
1262 e = binascii.b2a_base64(t)
1263 if e:
1264 oup = oup + e[:-1]
1265 return oup
1266
1267 def decode(self, inp):
1268 if not inp:
1269 return ''
1270 return binascii.a2b_base64(inp)
1271
Guido van Rossumeda960a1998-06-18 14:24:28 +00001272
1273
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001274Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001275 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001276
1277def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001278 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001279
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 Returns Python time module tuple.
1281 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001282
Tim Peters07e99cb2001-01-14 23:47:14 +00001283 mo = InternalDate.match(resp)
1284 if not mo:
1285 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001286
Tim Peters07e99cb2001-01-14 23:47:14 +00001287 mon = Mon2num[mo.group('mon')]
1288 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001289
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001290 day = int(mo.group('day'))
1291 year = int(mo.group('year'))
1292 hour = int(mo.group('hour'))
1293 min = int(mo.group('min'))
1294 sec = int(mo.group('sec'))
1295 zoneh = int(mo.group('zoneh'))
1296 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001297
Tim Peters07e99cb2001-01-14 23:47:14 +00001298 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001299
Tim Peters07e99cb2001-01-14 23:47:14 +00001300 zone = (zoneh*60 + zonem)*60
1301 if zonen == '-':
1302 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001303
Tim Peters07e99cb2001-01-14 23:47:14 +00001304 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001305
Tim Peters07e99cb2001-01-14 23:47:14 +00001306 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001307
Tim Peters07e99cb2001-01-14 23:47:14 +00001308 # Following is necessary because the time module has no 'mkgmtime'.
1309 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001310
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 lt = time.localtime(utc)
1312 if time.daylight and lt[-1]:
1313 zone = zone + time.altzone
1314 else:
1315 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001316
Tim Peters07e99cb2001-01-14 23:47:14 +00001317 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001318
1319
1320
1321def Int2AP(num):
1322
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001324
Christian Heimesfb5faf02008-11-05 19:39:50 +00001325 val = b''; AP = b'ABCDEFGHIJKLMNOP'
Tim Peters07e99cb2001-01-14 23:47:14 +00001326 num = int(abs(num))
1327 while num:
1328 num, mod = divmod(num, 16)
Christian Heimesfb5faf02008-11-05 19:39:50 +00001329 val = AP[mod:mod+1] + val
Tim Peters07e99cb2001-01-14 23:47:14 +00001330 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001331
1332
1333
1334def ParseFlags(resp):
1335
Tim Peters07e99cb2001-01-14 23:47:14 +00001336 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Tim Peters07e99cb2001-01-14 23:47:14 +00001338 mo = Flags.match(resp)
1339 if not mo:
1340 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001341
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001342 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
1344
1345def Time2Internaldate(date_time):
1346
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001348
Tim Peters07e99cb2001-01-14 23:47:14 +00001349 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1350 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001351
Fred Drakedb519202002-01-05 17:17:09 +00001352 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001353 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001354 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001355 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001356 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001358 else:
1359 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1362 if dt[0] == '0':
1363 dt = ' ' + dt[1:]
1364 if time.daylight and tt[-1]:
1365 zone = -time.altzone
1366 else:
1367 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001368 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001369
1370
1371
Guido van Rossum8c062211999-12-13 23:27:45 +00001372if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001373
Piers Laudere0273de2002-11-22 05:53:04 +00001374 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1375 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1376 # to test the IMAP4_stream class
1377
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001378 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001379
Tim Peters07e99cb2001-01-14 23:47:14 +00001380 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001381 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001382 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001383 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001384
Piers Laudere0273de2002-11-22 05:53:04 +00001385 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001386 for opt,val in optlist:
1387 if opt == '-d':
1388 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001389 elif opt == '-s':
1390 stream_command = val
1391 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001392
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001394
Tim Peters07e99cb2001-01-14 23:47:14 +00001395 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001396
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001398 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001399
Piers Lauder47404ff2003-04-29 23:40:59 +00001400 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 +00001401 test_seq1 = (
1402 ('login', (USER, PASSWD)),
1403 ('create', ('/tmp/xxx 1',)),
1404 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1405 ('CREATE', ('/tmp/yyz 2',)),
1406 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1407 ('list', ('/tmp', 'yy*')),
1408 ('select', ('/tmp/yyz 2',)),
1409 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001410 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001411 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001412 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 ('expunge', ()),
1414 ('recent', ()),
1415 ('close', ()),
1416 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001417
Tim Peters07e99cb2001-01-14 23:47:14 +00001418 test_seq2 = (
1419 ('select', ()),
1420 ('response',('UIDVALIDITY',)),
1421 ('uid', ('SEARCH', 'ALL')),
1422 ('response', ('EXISTS',)),
1423 ('append', (None, None, None, test_mesg)),
1424 ('recent', ()),
1425 ('logout', ()),
1426 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001427
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001429 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001430 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001431 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001432 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001434
Tim Peters07e99cb2001-01-14 23:47:14 +00001435 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001436 if stream_command:
1437 M = IMAP4_stream(stream_command)
1438 else:
1439 M = IMAP4(host)
1440 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001441 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001442 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001443 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001444
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 for cmd,args in test_seq1:
1446 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001447
Tim Peters07e99cb2001-01-14 23:47:14 +00001448 for ml in run('list', ('/tmp/', 'yy%')):
1449 mo = re.match(r'.*"([^"]+)"$', ml)
1450 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001451 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001452 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001453
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 for cmd,args in test_seq2:
1455 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001456
Tim Peters07e99cb2001-01-14 23:47:14 +00001457 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1458 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001459
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001460 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 if not uid: continue
1462 run('uid', ('FETCH', '%s' % uid[-1],
1463 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001464
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001465 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001466
Tim Peters07e99cb2001-01-14 23:47:14 +00001467 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001468 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001469
Tim Peters07e99cb2001-01-14 23:47:14 +00001470 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001471 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001472If you would like to see debugging output,
1473try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001474''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001475
Tim Peters07e99cb2001-01-14 23:47:14 +00001476 raise