blob: bac4c05cb30d20383d0c612097daf801c3819310 [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
Benjamin Petersonde6cde62010-01-02 02:44:28 +000025import binascii, random, re, socket, subprocess, sys, time
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000026
Thomas Woutersa6900e82007-08-30 21:54:39 +000027__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000028 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000029
Tim Peters07e99cb2001-01-14 23:47:14 +000030# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000031
32CRLF = '\r\n'
33Debug = 0
34IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000035IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000036AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000037
Barry Warsaw4e95d602013-09-22 16:07:09 -040038# Maximal line length when calling readline(). This is to prevent
39# reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1)
40# don't specify a line length. RFC 2683 however suggests limiting client
41# command lines to 1000 octets and server command lines to 8000 octets.
42# We have selected 10000 for some extra margin and since that is supposedly
43# also what UW and Panda IMAP does.
44_MAXLINE = 10000
45
46
Tim Peters07e99cb2001-01-14 23:47:14 +000047# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000048
49Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000050 # name valid states
51 'APPEND': ('AUTH', 'SELECTED'),
52 'AUTHENTICATE': ('NONAUTH',),
53 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
54 'CHECK': ('SELECTED',),
55 'CLOSE': ('SELECTED',),
56 'COPY': ('SELECTED',),
57 'CREATE': ('AUTH', 'SELECTED'),
58 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000059 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000060 'EXAMINE': ('AUTH', 'SELECTED'),
61 'EXPUNGE': ('SELECTED',),
62 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000063 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000064 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000065 'GETQUOTA': ('AUTH', 'SELECTED'),
66 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000067 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000068 'LIST': ('AUTH', 'SELECTED'),
69 'LOGIN': ('NONAUTH',),
70 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
71 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000072 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000073 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000074 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000075 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000076 'RENAME': ('AUTH', 'SELECTED'),
77 'SEARCH': ('SELECTED',),
78 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000079 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000080 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000081 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000082 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000083 'STATUS': ('AUTH', 'SELECTED'),
84 'STORE': ('SELECTED',),
85 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000086 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000087 'UID': ('SELECTED',),
88 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
89 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000090
Tim Peters07e99cb2001-01-14 23:47:14 +000091# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000092
Guido van Rossumeda960a1998-06-18 14:24:28 +000093Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000094Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
95InternalDate = re.compile(r'.*INTERNALDATE "'
Piers Lauder8659d902005-03-02 09:13:45 +000096 r'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
Tim Peters07e99cb2001-01-14 23:47:14 +000097 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
98 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
99 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +0000100Literal = re.compile(r'.*{(?P<size>\d+)}$')
Piers Lauder533366b2003-04-29 23:58:08 +0000101MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000103Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
105
106
107
108class IMAP4:
109
Tim Peters07e99cb2001-01-14 23:47:14 +0000110 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000111
Tim Peters07e99cb2001-01-14 23:47:14 +0000112 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000113
Tim Peters07e99cb2001-01-14 23:47:14 +0000114 host - host's name (default: localhost);
115 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000116
Tim Peters07e99cb2001-01-14 23:47:14 +0000117 All IMAP4rev1 commands are supported by methods of the same
118 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000119
Tim Peters07e99cb2001-01-14 23:47:14 +0000120 All arguments to commands are converted to strings, except for
121 AUTHENTICATE, and the last argument to APPEND which is passed as
122 an IMAP4 literal. If necessary (the string contains any
123 non-printing characters or white-space and isn't enclosed with
124 either parentheses or double quotes) each string is quoted.
125 However, the 'password' argument to the LOGIN command is always
126 quoted. If you want to avoid having an argument string quoted
127 (eg: the 'flags' argument to STORE) then enclose the string in
128 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000129
Tim Peters07e99cb2001-01-14 23:47:14 +0000130 Each command returns a tuple: (type, [data, ...]) where 'type'
131 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000132 tagged response, or untagged results from command. Each 'data'
133 is either a string, or a tuple. If a tuple, then the first part
134 is the header of the response, and the second part contains
135 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000136
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 Errors raise the exception class <instance>.error("<reason>").
138 IMAP4 server errors raise <instance>.abort("<reason>"),
139 which is a sub-class of 'error'. Mailbox status changes
140 from READ-WRITE to READ-ONLY raise the exception class
141 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000142
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 "error" exceptions imply a program error.
144 "abort" exceptions imply the connection should be reset, and
145 the command re-tried.
146 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000147
Piers Lauderd80ef022005-06-01 23:50:52 +0000148 Note: to use this module, you must read the RFCs pertaining to the
149 IMAP4 protocol, as the semantics of the arguments to each IMAP4
150 command are left to the invoker, not to mention the results. Also,
151 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 class error(Exception): pass # Logical errors - debug required
155 class abort(error): pass # Service errors - close and retry
156 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000157
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000159
Tim Peters07e99cb2001-01-14 23:47:14 +0000160 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000161 self.debug = Debug
162 self.state = 'LOGOUT'
163 self.literal = None # A literal argument to a command
164 self.tagged_commands = {} # Tagged commands awaiting response
165 self.untagged_responses = {} # {typ: [data, ...], ...}
166 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000167 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000169
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000171
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000173
Tim Peters07e99cb2001-01-14 23:47:14 +0000174 # Create unique tag for this session,
175 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000176
Piers Lauder2dfc1682005-07-05 04:20:07 +0000177 self.tagpre = Int2AP(random.randint(4096, 65535))
Tim Peters07e99cb2001-01-14 23:47:14 +0000178 self.tagre = re.compile(r'(?P<tag>'
179 + self.tagpre
180 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000181
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 # Get server welcome message,
183 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000184
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000186 self._cmd_log_len = 10
187 self._cmd_log_idx = 0
188 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000190 self._mesg('imaplib version %s' % __version__)
191 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000192
Tim Peters07e99cb2001-01-14 23:47:14 +0000193 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000194 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000196 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000197 self.state = 'NONAUTH'
198 else:
199 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000200
Piers Lauderd80ef022005-06-01 23:50:52 +0000201 typ, dat = self.capability()
202 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000203 raise self.error('no CAPABILITY response from server')
Piers Lauderd80ef022005-06-01 23:50:52 +0000204 self.capabilities = tuple(dat[-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000205
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 if __debug__:
207 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000208 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000209
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 for version in AllowedVersions:
211 if not version in self.capabilities:
212 continue
213 self.PROTOCOL_VERSION = version
214 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000215
Tim Peters07e99cb2001-01-14 23:47:14 +0000216 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000217
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000218
Tim Peters07e99cb2001-01-14 23:47:14 +0000219 def __getattr__(self, attr):
220 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000221 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000222 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000224
225
226
Piers Lauder15e5d532001-07-20 10:52:06 +0000227 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000228
229
Piers Lauderf97b2d72002-06-05 22:31:57 +0000230 def open(self, host = '', port = IMAP4_PORT):
231 """Setup connection to remote server on "host:port"
232 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000233 This connection will be used by the routines:
234 read, readline, send, shutdown.
235 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000236 self.host = host
237 self.port = port
Antoine Pitroufe8fb462009-05-15 12:03:56 +0000238 self.sock = socket.create_connection((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000239 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000240
241
Piers Lauder15e5d532001-07-20 10:52:06 +0000242 def read(self, size):
243 """Read 'size' bytes from remote."""
244 return self.file.read(size)
245
246
247 def readline(self):
248 """Read line from remote."""
Barry Warsaw4e95d602013-09-22 16:07:09 -0400249 line = self.file.readline(_MAXLINE + 1)
250 if len(line) > _MAXLINE:
251 raise self.error("got more than %d bytes" % _MAXLINE)
252 return line
Piers Lauder15e5d532001-07-20 10:52:06 +0000253
254
255 def send(self, data):
256 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000257 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000258
Piers Lauderf2d7d152002-02-22 01:15:17 +0000259
Piers Lauder15e5d532001-07-20 10:52:06 +0000260 def shutdown(self):
261 """Close I/O established in "open"."""
262 self.file.close()
263 self.sock.close()
264
265
266 def socket(self):
267 """Return socket instance used to connect to IMAP4 server.
268
269 socket = <instance>.socket()
270 """
271 return self.sock
272
273
274
275 # Utility methods
276
277
Tim Peters07e99cb2001-01-14 23:47:14 +0000278 def recent(self):
279 """Return most recent 'RECENT' responses if any exist,
280 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000281
Tim Peters07e99cb2001-01-14 23:47:14 +0000282 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000283
Tim Peters07e99cb2001-01-14 23:47:14 +0000284 'data' is None if no new messages,
285 else list of RECENT responses, most recent last.
286 """
287 name = 'RECENT'
288 typ, dat = self._untagged_response('OK', [None], name)
289 if dat[-1]:
290 return typ, dat
291 typ, dat = self.noop() # Prod server for response
292 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000293
294
Tim Peters07e99cb2001-01-14 23:47:14 +0000295 def response(self, code):
296 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000297
Tim Peters07e99cb2001-01-14 23:47:14 +0000298 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000299
Tim Peters07e99cb2001-01-14 23:47:14 +0000300 (code, [data]) = <instance>.response(code)
301 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000302 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000303
304
Guido van Rossum26367a01998-09-28 15:34:46 +0000305
Tim Peters07e99cb2001-01-14 23:47:14 +0000306 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000307
308
Tim Peters07e99cb2001-01-14 23:47:14 +0000309 def append(self, mailbox, flags, date_time, message):
310 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000311
Tim Peters07e99cb2001-01-14 23:47:14 +0000312 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000313
Tim Peters07e99cb2001-01-14 23:47:14 +0000314 All args except `message' can be None.
315 """
316 name = 'APPEND'
317 if not mailbox:
318 mailbox = 'INBOX'
319 if flags:
320 if (flags[0],flags[-1]) != ('(',')'):
321 flags = '(%s)' % flags
322 else:
323 flags = None
324 if date_time:
325 date_time = Time2Internaldate(date_time)
326 else:
327 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000328 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000329 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000330
331
Tim Peters07e99cb2001-01-14 23:47:14 +0000332 def authenticate(self, mechanism, authobject):
333 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000334
Tim Peters07e99cb2001-01-14 23:47:14 +0000335 'mechanism' specifies which authentication mechanism is to
336 be used - it must appear in <instance>.capabilities in the
337 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000338
Tim Peters07e99cb2001-01-14 23:47:14 +0000339 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000340
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000342
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 It will be called to process server continuation responses.
344 It should return data that will be encoded and sent to server.
345 It should return None if the client abort response '*' should
346 be sent instead.
347 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000348 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000349 # XXX: shouldn't this code be removed, not commented out?
350 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000351 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000352 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000353 self.literal = _Authenticator(authobject).process
354 typ, dat = self._simple_command('AUTHENTICATE', mech)
355 if typ != 'OK':
356 raise self.error(dat[-1])
357 self.state = 'AUTH'
358 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000359
360
Piers Lauderd80ef022005-06-01 23:50:52 +0000361 def capability(self):
362 """(typ, [data]) = <instance>.capability()
363 Fetch capabilities list from server."""
364
365 name = 'CAPABILITY'
366 typ, dat = self._simple_command(name)
367 return self._untagged_response(typ, dat, name)
368
369
Tim Peters07e99cb2001-01-14 23:47:14 +0000370 def check(self):
371 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000372
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 (typ, [data]) = <instance>.check()
374 """
375 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000376
377
Tim Peters07e99cb2001-01-14 23:47:14 +0000378 def close(self):
379 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 Deleted messages are removed from writable mailbox.
382 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 (typ, [data]) = <instance>.close()
385 """
386 try:
387 typ, dat = self._simple_command('CLOSE')
388 finally:
389 self.state = 'AUTH'
390 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000391
392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 def copy(self, message_set, new_mailbox):
394 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000395
Tim Peters07e99cb2001-01-14 23:47:14 +0000396 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
397 """
398 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
400
Tim Peters07e99cb2001-01-14 23:47:14 +0000401 def create(self, mailbox):
402 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 (typ, [data]) = <instance>.create(mailbox)
405 """
406 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000407
408
Tim Peters07e99cb2001-01-14 23:47:14 +0000409 def delete(self, mailbox):
410 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 (typ, [data]) = <instance>.delete(mailbox)
413 """
414 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000415
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000416 def deleteacl(self, mailbox, who):
417 """Delete the ACLs (remove any rights) set for who on mailbox.
418
419 (typ, [data]) = <instance>.deleteacl(mailbox, who)
420 """
421 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000422
Tim Peters07e99cb2001-01-14 23:47:14 +0000423 def expunge(self):
424 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000425
Tim Peters07e99cb2001-01-14 23:47:14 +0000426 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 'data' is list of 'EXPUNGE'd message numbers in order received.
431 """
432 name = 'EXPUNGE'
433 typ, dat = self._simple_command(name)
434 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000435
436
Tim Peters07e99cb2001-01-14 23:47:14 +0000437 def fetch(self, message_set, message_parts):
438 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000439
Tim Peters07e99cb2001-01-14 23:47:14 +0000440 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000441
Tim Peters07e99cb2001-01-14 23:47:14 +0000442 'message_parts' should be a string of selected parts
443 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000444
Tim Peters07e99cb2001-01-14 23:47:14 +0000445 'data' are tuples of message part envelope and data.
446 """
447 name = 'FETCH'
448 typ, dat = self._simple_command(name, message_set, message_parts)
449 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000450
451
Piers Lauder15e5d532001-07-20 10:52:06 +0000452 def getacl(self, mailbox):
453 """Get the ACLs for a mailbox.
454
455 (typ, [data]) = <instance>.getacl(mailbox)
456 """
457 typ, dat = self._simple_command('GETACL', mailbox)
458 return self._untagged_response(typ, dat, 'ACL')
459
460
Piers Lauderd80ef022005-06-01 23:50:52 +0000461 def getannotation(self, mailbox, entry, attribute):
462 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
463 Retrieve ANNOTATIONs."""
464
465 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
466 return self._untagged_response(typ, dat, 'ANNOTATION')
467
468
Piers Lauder3fca2912002-06-17 07:07:20 +0000469 def getquota(self, root):
470 """Get the quota root's resource usage and limits.
471
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000472 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000473
474 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000475 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000476 typ, dat = self._simple_command('GETQUOTA', root)
477 return self._untagged_response(typ, dat, 'QUOTA')
478
479
480 def getquotaroot(self, mailbox):
481 """Get the list of quota roots for the named mailbox.
482
483 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000484 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000485 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000486 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
487 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000488 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000489
490
Tim Peters07e99cb2001-01-14 23:47:14 +0000491 def list(self, directory='""', pattern='*'):
492 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000493
Tim Peters07e99cb2001-01-14 23:47:14 +0000494 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 'data' is list of LIST responses.
497 """
498 name = 'LIST'
499 typ, dat = self._simple_command(name, directory, pattern)
500 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000501
502
Tim Peters07e99cb2001-01-14 23:47:14 +0000503 def login(self, user, password):
504 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
Tim Peters07e99cb2001-01-14 23:47:14 +0000506 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000507
Tim Peters07e99cb2001-01-14 23:47:14 +0000508 NB: 'password' will be quoted.
509 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
511 if typ != 'OK':
512 raise self.error(dat[-1])
513 self.state = 'AUTH'
514 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000515
516
Piers Laudere0273de2002-11-22 05:53:04 +0000517 def login_cram_md5(self, user, password):
518 """ Force use of CRAM-MD5 authentication.
519
520 (typ, [data]) = <instance>.login_cram_md5(user, password)
521 """
522 self.user, self.password = user, password
523 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
524
525
526 def _CRAM_MD5_AUTH(self, challenge):
527 """ Authobject to use with CRAM-MD5 authentication. """
528 import hmac
529 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
530
531
Tim Peters07e99cb2001-01-14 23:47:14 +0000532 def logout(self):
533 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000534
Tim Peters07e99cb2001-01-14 23:47:14 +0000535 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 Returns server 'BYE' response.
538 """
539 self.state = 'LOGOUT'
540 try: typ, dat = self._simple_command('LOGOUT')
541 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000542 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000543 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000544 return 'BYE', self.untagged_responses['BYE']
545 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000546
547
Tim Peters07e99cb2001-01-14 23:47:14 +0000548 def lsub(self, directory='""', pattern='*'):
549 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000550
Tim Peters07e99cb2001-01-14 23:47:14 +0000551 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000552
Tim Peters07e99cb2001-01-14 23:47:14 +0000553 'data' are tuples of message part envelope and data.
554 """
555 name = 'LSUB'
556 typ, dat = self._simple_command(name, directory, pattern)
557 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000558
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000559 def myrights(self, mailbox):
560 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
561
562 (typ, [data]) = <instance>.myrights(mailbox)
563 """
564 typ,dat = self._simple_command('MYRIGHTS', mailbox)
565 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000566
Piers Lauder15e5d532001-07-20 10:52:06 +0000567 def namespace(self):
568 """ Returns IMAP namespaces ala rfc2342
569
570 (typ, [data, ...]) = <instance>.namespace()
571 """
572 name = 'NAMESPACE'
573 typ, dat = self._simple_command(name)
574 return self._untagged_response(typ, dat, name)
575
576
Tim Peters07e99cb2001-01-14 23:47:14 +0000577 def noop(self):
578 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000579
Piers Laudere0273de2002-11-22 05:53:04 +0000580 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 """
582 if __debug__:
583 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000584 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000586
587
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 def partial(self, message_num, message_part, start, length):
589 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000590
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000592
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 'data' is tuple of message part envelope and data.
594 """
595 name = 'PARTIAL'
596 typ, dat = self._simple_command(name, message_num, message_part, start, length)
597 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000598
599
Piers Laudere0273de2002-11-22 05:53:04 +0000600 def proxyauth(self, user):
601 """Assume authentication as "user".
602
603 Allows an authorised administrator to proxy into any user's
604 mailbox.
605
606 (typ, [data]) = <instance>.proxyauth(user)
607 """
608
609 name = 'PROXYAUTH'
610 return self._simple_command('PROXYAUTH', user)
611
612
Tim Peters07e99cb2001-01-14 23:47:14 +0000613 def rename(self, oldmailbox, newmailbox):
614 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000615
Piers Laudere0273de2002-11-22 05:53:04 +0000616 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000617 """
618 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000619
620
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 def search(self, charset, *criteria):
622 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000623
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000624 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000625
Tim Peters07e99cb2001-01-14 23:47:14 +0000626 'data' is space separated list of matching message numbers.
627 """
628 name = 'SEARCH'
629 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000630 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000631 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000632 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000634
635
Piers Lauder14f39402005-08-31 10:46:29 +0000636 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000638
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000640
Piers Lauder14f39402005-08-31 10:46:29 +0000641 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000642
Tim Peters07e99cb2001-01-14 23:47:14 +0000643 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000644
645 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
646 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000647 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000648 self.untagged_responses = {} # Flush old responses.
649 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000650 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000651 name = 'EXAMINE'
652 else:
653 name = 'SELECT'
654 typ, dat = self._simple_command(name, mailbox)
655 if typ != 'OK':
656 self.state = 'AUTH' # Might have been 'SELECTED'
657 return typ, dat
658 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000659 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000660 and not readonly:
661 if __debug__:
662 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000663 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 raise self.readonly('%s is not writable' % mailbox)
665 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000666
667
Piers Lauder15e5d532001-07-20 10:52:06 +0000668 def setacl(self, mailbox, who, what):
669 """Set a mailbox acl.
670
Piers Lauderf167dc32004-03-25 00:12:21 +0000671 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000672 """
673 return self._simple_command('SETACL', mailbox, who, what)
674
675
Piers Lauderd80ef022005-06-01 23:50:52 +0000676 def setannotation(self, *args):
677 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
678 Set ANNOTATIONs."""
679
680 typ, dat = self._simple_command('SETANNOTATION', *args)
681 return self._untagged_response(typ, dat, 'ANNOTATION')
682
683
Piers Lauder3fca2912002-06-17 07:07:20 +0000684 def setquota(self, root, limits):
685 """Set the quota root's resource limits.
686
687 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000688 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000689 typ, dat = self._simple_command('SETQUOTA', root, limits)
690 return self._untagged_response(typ, dat, 'QUOTA')
691
692
Piers Lauder15e5d532001-07-20 10:52:06 +0000693 def sort(self, sort_criteria, charset, *search_criteria):
694 """IMAP4rev1 extension SORT command.
695
696 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
697 """
698 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000699 #if not name in self.capabilities: # Let the server decide!
700 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000701 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000702 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000703 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000704 return self._untagged_response(typ, dat, name)
705
706
Tim Peters07e99cb2001-01-14 23:47:14 +0000707 def status(self, mailbox, names):
708 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000709
Tim Peters07e99cb2001-01-14 23:47:14 +0000710 (typ, [data]) = <instance>.status(mailbox, names)
711 """
712 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000713 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000714 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000715 typ, dat = self._simple_command(name, mailbox, names)
716 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000717
718
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 def store(self, message_set, command, flags):
720 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000721
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 (typ, [data]) = <instance>.store(message_set, command, flags)
723 """
724 if (flags[0],flags[-1]) != ('(',')'):
725 flags = '(%s)' % flags # Avoid quoting the flags
726 typ, dat = self._simple_command('STORE', message_set, command, flags)
727 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000728
729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 def subscribe(self, mailbox):
731 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000732
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 (typ, [data]) = <instance>.subscribe(mailbox)
734 """
735 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000736
737
Martin v. Löwisd8921372003-11-10 06:44:44 +0000738 def thread(self, threading_algorithm, charset, *search_criteria):
739 """IMAPrev1 extension THREAD command.
740
Mark Dickinson995d6a32009-12-24 16:09:31 +0000741 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000742 """
743 name = 'THREAD'
744 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
745 return self._untagged_response(typ, dat, name)
746
747
Tim Peters07e99cb2001-01-14 23:47:14 +0000748 def uid(self, command, *args):
749 """Execute "command arg ..." with messages identified by UID,
750 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000751
Tim Peters07e99cb2001-01-14 23:47:14 +0000752 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000753
Tim Peters07e99cb2001-01-14 23:47:14 +0000754 Returns response appropriate to 'command'.
755 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000756 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000757 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000758 raise self.error("Unknown IMAP4 UID command: %s" % command)
759 if self.state not in Commands[command]:
Georg Brandl6c104f62007-03-13 18:24:40 +0000760 raise self.error("command %s illegal in state %s, "
761 "only allowed in states %s" %
762 (command, self.state,
763 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000764 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000765 typ, dat = self._simple_command(name, command, *args)
Georg Brandl03c1cff2010-08-01 22:05:31 +0000766 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000767 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 else:
769 name = 'FETCH'
770 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000771
772
Tim Peters07e99cb2001-01-14 23:47:14 +0000773 def unsubscribe(self, mailbox):
774 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000775
Tim Peters07e99cb2001-01-14 23:47:14 +0000776 (typ, [data]) = <instance>.unsubscribe(mailbox)
777 """
778 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000779
780
Tim Peters07e99cb2001-01-14 23:47:14 +0000781 def xatom(self, name, *args):
782 """Allow simple extension commands
783 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000784
Piers Lauder15e5d532001-07-20 10:52:06 +0000785 Assumes command is legal in current state.
786
Tim Peters07e99cb2001-01-14 23:47:14 +0000787 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000788
789 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000790 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000791 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000792 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000793 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000794 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000795 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000796 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000797
798
799
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000801
802
Tim Peters07e99cb2001-01-14 23:47:14 +0000803 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000804
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 if dat is None: dat = ''
806 ur = self.untagged_responses
807 if __debug__:
808 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000809 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000810 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000811 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 ur[typ].append(dat)
813 else:
814 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000815
816
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 def _check_bye(self):
818 bye = self.untagged_responses.get('BYE')
819 if bye:
820 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000821
822
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 if self.state not in Commands[name]:
826 self.literal = None
Georg Brandl6c104f62007-03-13 18:24:40 +0000827 raise self.error("command %s illegal in state %s, "
828 "only allowed in states %s" %
829 (name, self.state,
830 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000831
Tim Peters07e99cb2001-01-14 23:47:14 +0000832 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000833 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000835
Raymond Hettinger54f02222002-06-01 14:18:47 +0000836 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000837 and not self.is_readonly:
838 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000839
Tim Peters07e99cb2001-01-14 23:47:14 +0000840 tag = self._new_tag()
841 data = '%s %s' % (tag, name)
842 for arg in args:
843 if arg is None: continue
844 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 literal = self.literal
847 if literal is not None:
848 self.literal = None
849 if type(literal) is type(self._command):
850 literator = literal
851 else:
852 literator = None
853 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000854
Tim Peters07e99cb2001-01-14 23:47:14 +0000855 if __debug__:
856 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000857 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000858 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000859 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000862 self.send('%s%s' % (data, CRLF))
863 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000865
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 if literal is None:
867 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 while 1:
870 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000871
Tim Peters07e99cb2001-01-14 23:47:14 +0000872 while self._get_response():
873 if self.tagged_commands[tag]: # BAD/NO?
874 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000877
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 if literator:
879 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000880
Tim Peters07e99cb2001-01-14 23:47:14 +0000881 if __debug__:
882 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000883 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000886 self.send(literal)
887 self.send(CRLF)
888 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000889 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000890
Tim Peters07e99cb2001-01-14 23:47:14 +0000891 if not literator:
892 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000893
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000895
896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 def _command_complete(self, name, tag):
898 self._check_bye()
899 try:
900 typ, data = self._get_tagged_response(tag)
901 except self.abort, val:
902 raise self.abort('command: %s => %s' % (name, val))
903 except self.error, val:
904 raise self.error('command: %s => %s' % (name, val))
905 self._check_bye()
906 if typ == 'BAD':
907 raise self.error('%s command error: %s %s' % (name, typ, data))
908 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000909
910
Tim Peters07e99cb2001-01-14 23:47:14 +0000911 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000912
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 # Read response and store.
914 #
915 # Returns None for continuation responses,
916 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000917
Tim Peters07e99cb2001-01-14 23:47:14 +0000918 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000919
Tim Peters07e99cb2001-01-14 23:47:14 +0000920 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000921
Tim Peters07e99cb2001-01-14 23:47:14 +0000922 if self._match(self.tagre, resp):
923 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000924 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 typ = self.mo.group('type')
928 dat = self.mo.group('data')
929 self.tagged_commands[tag] = (typ, [dat])
930 else:
931 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000932
Tim Peters07e99cb2001-01-14 23:47:14 +0000933 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000934
Tim Peters07e99cb2001-01-14 23:47:14 +0000935 if not self._match(Untagged_response, resp):
936 if self._match(Untagged_status, resp):
937 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000938
Tim Peters07e99cb2001-01-14 23:47:14 +0000939 if self.mo is None:
940 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000941
Tim Peters07e99cb2001-01-14 23:47:14 +0000942 if self._match(Continuation, resp):
943 self.continuation_response = self.mo.group('data')
944 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000945
Tim Peters07e99cb2001-01-14 23:47:14 +0000946 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000947
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 typ = self.mo.group('type')
949 dat = self.mo.group('data')
950 if dat is None: dat = '' # Null untagged response
951 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000958
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000959 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000960 if __debug__:
961 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000962 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000963 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000970
Tim Peters07e99cb2001-01-14 23:47:14 +0000971 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
Tim Peters07e99cb2001-01-14 23:47:14 +0000973 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
978 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000979
Tim Peters07e99cb2001-01-14 23:47:14 +0000980 if __debug__:
981 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000982 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000983
Tim Peters07e99cb2001-01-14 23:47:14 +0000984 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000985
986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000988
Tim Peters07e99cb2001-01-14 23:47:14 +0000989 while 1:
990 result = self.tagged_commands[tag]
991 if result is not None:
992 del self.tagged_commands[tag]
993 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000994
Tim Peters07e99cb2001-01-14 23:47:14 +0000995 # Some have reported "unexpected response" exceptions.
996 # Note that ignoring them here causes loops.
997 # Instead, send me details of the unexpected response and
998 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000999
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 try:
1001 self._get_response()
1002 except self.abort, val:
1003 if __debug__:
1004 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001005 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001007
1008
Tim Peters07e99cb2001-01-14 23:47:14 +00001009 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001010
Piers Lauder15e5d532001-07-20 10:52:06 +00001011 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001012 if not line:
1013 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001014
Tim Peters07e99cb2001-01-14 23:47:14 +00001015 # Protocol mandates all lines terminated by CRLF
R. David Murray07ca7612009-12-12 18:36:47 +00001016 if not line.endswith('\r\n'):
1017 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001018
Tim Peters07e99cb2001-01-14 23:47:14 +00001019 line = line[:-2]
1020 if __debug__:
1021 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001022 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001023 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001024 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001026
1027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 # Run compiled regular expression match method on 's'.
1031 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001032
Tim Peters07e99cb2001-01-14 23:47:14 +00001033 self.mo = cre.match(s)
1034 if __debug__:
1035 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001036 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001037 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001038
1039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001041
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 tag = '%s%s' % (self.tagpre, self.tagnum)
1043 self.tagnum = self.tagnum + 1
1044 self.tagged_commands[tag] = None
1045 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001046
1047
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 # Must quote command args if non-alphanumeric chars present,
1051 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001052
Tim Peters07e99cb2001-01-14 23:47:14 +00001053 if type(arg) is not type(''):
1054 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001055 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001056 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001057 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 return arg
1059 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001060
1061
Tim Peters07e99cb2001-01-14 23:47:14 +00001062 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001063
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001064 arg = arg.replace('\\', '\\\\')
1065 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001066
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001068
1069
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001071
Guido van Rossum68468eb2003-02-27 20:14:51 +00001072 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001073
1074
Tim Peters07e99cb2001-01-14 23:47:14 +00001075 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001076
Tim Peters07e99cb2001-01-14 23:47:14 +00001077 if typ == 'NO':
1078 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001079 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001080 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001081 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001082 if __debug__:
1083 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001084 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001085 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001086
1087
Piers Lauderf2d7d152002-02-22 01:15:17 +00001088 if __debug__:
1089
1090 def _mesg(self, s, secs=None):
1091 if secs is None:
1092 secs = time.time()
1093 tm = time.strftime('%M:%S', time.localtime(secs))
1094 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1095 sys.stderr.flush()
1096
1097 def _dump_ur(self, dict):
1098 # Dump untagged responses (in `dict').
1099 l = dict.items()
1100 if not l: return
1101 t = '\n\t\t'
1102 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1103 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1104
1105 def _log(self, line):
1106 # Keep log of last `_cmd_log_len' interactions for debugging.
1107 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1108 self._cmd_log_idx += 1
1109 if self._cmd_log_idx >= self._cmd_log_len:
1110 self._cmd_log_idx = 0
1111
1112 def print_log(self):
1113 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1114 i, n = self._cmd_log_idx, self._cmd_log_len
1115 while n:
1116 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001117 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001118 except:
1119 pass
1120 i += 1
1121 if i >= self._cmd_log_len:
1122 i = 0
1123 n -= 1
1124
1125
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001126
Bill Janssen426ea0a2007-08-29 22:35:05 +00001127try:
1128 import ssl
1129except ImportError:
1130 pass
1131else:
1132 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001133
Bill Janssen426ea0a2007-08-29 22:35:05 +00001134 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001135
Bill Janssen426ea0a2007-08-29 22:35:05 +00001136 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001137
Bill Janssen426ea0a2007-08-29 22:35:05 +00001138 host - host's name (default: localhost);
1139 port - port number (default: standard IMAP4 SSL port).
1140 keyfile - PEM formatted file that contains your private key (default: None);
1141 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001142
Bill Janssen426ea0a2007-08-29 22:35:05 +00001143 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001144 """
Piers Laudera4f83132002-03-08 01:53:24 +00001145
1146
Bill Janssen426ea0a2007-08-29 22:35:05 +00001147 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1148 self.keyfile = keyfile
1149 self.certfile = certfile
1150 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001151
1152
Bill Janssen426ea0a2007-08-29 22:35:05 +00001153 def open(self, host = '', port = IMAP4_SSL_PORT):
1154 """Setup connection to remote server on "host:port".
1155 (default: localhost:standard IMAP4 SSL port).
1156 This connection will be used by the routines:
1157 read, readline, send, shutdown.
1158 """
1159 self.host = host
1160 self.port = port
Antoine Pitroufe8fb462009-05-15 12:03:56 +00001161 self.sock = socket.create_connection((host, port))
Bill Janssen98d19da2007-09-10 21:51:02 +00001162 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001163
1164
Bill Janssen426ea0a2007-08-29 22:35:05 +00001165 def read(self, size):
1166 """Read 'size' bytes from remote."""
1167 # sslobj.read() sometimes returns < size bytes
1168 chunks = []
1169 read = 0
1170 while read < size:
Andrew M. Kuchling1219a802008-02-23 19:02:33 +00001171 data = self.sslobj.read(min(size-read, 16384))
Bill Janssen426ea0a2007-08-29 22:35:05 +00001172 read += len(data)
1173 chunks.append(data)
1174
1175 return ''.join(chunks)
Piers Laudera4f83132002-03-08 01:53:24 +00001176
1177
Bill Janssen426ea0a2007-08-29 22:35:05 +00001178 def readline(self):
1179 """Read line from remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001180 line = []
1181 while 1:
1182 char = self.sslobj.read(1)
1183 line.append(char)
R. David Murray07ca7612009-12-12 18:36:47 +00001184 if char in ("\n", ""): return ''.join(line)
Piers Laudera4f83132002-03-08 01:53:24 +00001185
1186
Bill Janssen426ea0a2007-08-29 22:35:05 +00001187 def send(self, data):
1188 """Send data to remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001189 bytes = len(data)
1190 while bytes > 0:
1191 sent = self.sslobj.write(data)
1192 if sent == bytes:
1193 break # avoid copy
1194 data = data[sent:]
1195 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001196
1197
Bill Janssen426ea0a2007-08-29 22:35:05 +00001198 def shutdown(self):
1199 """Close I/O established in "open"."""
1200 self.sock.close()
Piers Laudera4f83132002-03-08 01:53:24 +00001201
Bill Janssen426ea0a2007-08-29 22:35:05 +00001202
1203 def socket(self):
1204 """Return socket instance used to connect to IMAP4 server.
1205
1206 socket = <instance>.socket()
1207 """
1208 return self.sock
1209
1210
1211 def ssl(self):
1212 """Return SSLObject instance used to communicate with the IMAP4 server.
1213
Bill Janssen98d19da2007-09-10 21:51:02 +00001214 ssl = ssl.wrap_socket(<instance>.socket)
Bill Janssen426ea0a2007-08-29 22:35:05 +00001215 """
1216 return self.sslobj
Piers Laudera4f83132002-03-08 01:53:24 +00001217
Thomas Woutersa6900e82007-08-30 21:54:39 +00001218 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001219
1220
Piers Laudere0273de2002-11-22 05:53:04 +00001221class IMAP4_stream(IMAP4):
1222
1223 """IMAP4 client class over a stream
1224
1225 Instantiate with: IMAP4_stream(command)
1226
Georg Brandlb6453a92010-03-21 19:16:28 +00001227 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001228
1229 for more documentation see the docstring of the parent class IMAP4.
1230 """
1231
1232
1233 def __init__(self, command):
1234 self.command = command
1235 IMAP4.__init__(self)
1236
1237
1238 def open(self, host = None, port = None):
1239 """Setup a stream connection.
1240 This connection will be used by the routines:
1241 read, readline, send, shutdown.
1242 """
1243 self.host = None # For compatibility with parent class
1244 self.port = None
1245 self.sock = None
1246 self.file = None
Benjamin Petersonde6cde62010-01-02 02:44:28 +00001247 self.process = subprocess.Popen(self.command,
1248 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1249 shell=True, close_fds=True)
1250 self.writefile = self.process.stdin
1251 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001252
1253
1254 def read(self, size):
1255 """Read 'size' bytes from remote."""
1256 return self.readfile.read(size)
1257
1258
1259 def readline(self):
1260 """Read line from remote."""
1261 return self.readfile.readline()
1262
1263
1264 def send(self, data):
1265 """Send data to remote."""
1266 self.writefile.write(data)
1267 self.writefile.flush()
1268
1269
1270 def shutdown(self):
1271 """Close I/O established in "open"."""
1272 self.readfile.close()
1273 self.writefile.close()
Benjamin Petersonde6cde62010-01-02 02:44:28 +00001274 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001275
1276
1277
Guido van Rossumeda960a1998-06-18 14:24:28 +00001278class _Authenticator:
1279
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 """Private class to provide en/decoding
1281 for base64-based authentication conversation.
1282 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001283
Tim Peters07e99cb2001-01-14 23:47:14 +00001284 def __init__(self, mechinst):
1285 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001286
Tim Peters07e99cb2001-01-14 23:47:14 +00001287 def process(self, data):
1288 ret = self.mech(self.decode(data))
1289 if ret is None:
1290 return '*' # Abort conversation
1291 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001292
Tim Peters07e99cb2001-01-14 23:47:14 +00001293 def encode(self, inp):
1294 #
1295 # Invoke binascii.b2a_base64 iteratively with
1296 # short even length buffers, strip the trailing
1297 # line feed from the result and append. "Even"
1298 # means a number that factors to both 6 and 8,
1299 # so when it gets to the end of the 8-bit input
1300 # there's no partial 6-bit output.
1301 #
1302 oup = ''
1303 while inp:
1304 if len(inp) > 48:
1305 t = inp[:48]
1306 inp = inp[48:]
1307 else:
1308 t = inp
1309 inp = ''
1310 e = binascii.b2a_base64(t)
1311 if e:
1312 oup = oup + e[:-1]
1313 return oup
1314
1315 def decode(self, inp):
1316 if not inp:
1317 return ''
1318 return binascii.a2b_base64(inp)
1319
Guido van Rossumeda960a1998-06-18 14:24:28 +00001320
1321
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001324
1325def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001326 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001327
Tim Peters07e99cb2001-01-14 23:47:14 +00001328 Returns Python time module tuple.
1329 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001330
Tim Peters07e99cb2001-01-14 23:47:14 +00001331 mo = InternalDate.match(resp)
1332 if not mo:
1333 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001334
Tim Peters07e99cb2001-01-14 23:47:14 +00001335 mon = Mon2num[mo.group('mon')]
1336 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001338 day = int(mo.group('day'))
1339 year = int(mo.group('year'))
1340 hour = int(mo.group('hour'))
1341 min = int(mo.group('min'))
1342 sec = int(mo.group('sec'))
1343 zoneh = int(mo.group('zoneh'))
1344 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001345
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001347
Tim Peters07e99cb2001-01-14 23:47:14 +00001348 zone = (zoneh*60 + zonem)*60
1349 if zonen == '-':
1350 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001351
Tim Peters07e99cb2001-01-14 23:47:14 +00001352 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001353
Tim Peters07e99cb2001-01-14 23:47:14 +00001354 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001355
Tim Peters07e99cb2001-01-14 23:47:14 +00001356 # Following is necessary because the time module has no 'mkgmtime'.
1357 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
Tim Peters07e99cb2001-01-14 23:47:14 +00001359 lt = time.localtime(utc)
1360 if time.daylight and lt[-1]:
1361 zone = zone + time.altzone
1362 else:
1363 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001364
Tim Peters07e99cb2001-01-14 23:47:14 +00001365 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001366
1367
1368
1369def Int2AP(num):
1370
Tim Peters07e99cb2001-01-14 23:47:14 +00001371 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001372
Tim Peters07e99cb2001-01-14 23:47:14 +00001373 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1374 num = int(abs(num))
1375 while num:
1376 num, mod = divmod(num, 16)
1377 val = AP[mod] + val
1378 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001379
1380
1381
1382def ParseFlags(resp):
1383
Tim Peters07e99cb2001-01-14 23:47:14 +00001384 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001385
Tim Peters07e99cb2001-01-14 23:47:14 +00001386 mo = Flags.match(resp)
1387 if not mo:
1388 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001389
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001390 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001391
1392
1393def Time2Internaldate(date_time):
1394
Tim Peters07e99cb2001-01-14 23:47:14 +00001395 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001396
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1398 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001399
Fred Drakedb519202002-01-05 17:17:09 +00001400 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001401 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001402 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001403 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001404 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001405 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001406 else:
1407 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001408
Tim Peters07e99cb2001-01-14 23:47:14 +00001409 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1410 if dt[0] == '0':
1411 dt = ' ' + dt[1:]
1412 if time.daylight and tt[-1]:
1413 zone = -time.altzone
1414 else:
1415 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001416 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001417
1418
1419
Guido van Rossum8c062211999-12-13 23:27:45 +00001420if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001421
Piers Laudere0273de2002-11-22 05:53:04 +00001422 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1423 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1424 # to test the IMAP4_stream class
1425
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001426 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001427
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001429 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001431 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001432
Piers Laudere0273de2002-11-22 05:53:04 +00001433 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001434 for opt,val in optlist:
1435 if opt == '-d':
1436 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001437 elif opt == '-s':
1438 stream_command = val
1439 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001440
Tim Peters07e99cb2001-01-14 23:47:14 +00001441 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001442
Tim Peters07e99cb2001-01-14 23:47:14 +00001443 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001444
Tim Peters07e99cb2001-01-14 23:47:14 +00001445 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001446 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001447
Piers Lauder47404ff2003-04-29 23:40:59 +00001448 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 +00001449 test_seq1 = (
1450 ('login', (USER, PASSWD)),
1451 ('create', ('/tmp/xxx 1',)),
1452 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1453 ('CREATE', ('/tmp/yyz 2',)),
1454 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1455 ('list', ('/tmp', 'yy*')),
1456 ('select', ('/tmp/yyz 2',)),
1457 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001458 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001459 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001460 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 ('expunge', ()),
1462 ('recent', ()),
1463 ('close', ()),
1464 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001465
Tim Peters07e99cb2001-01-14 23:47:14 +00001466 test_seq2 = (
1467 ('select', ()),
1468 ('response',('UIDVALIDITY',)),
1469 ('uid', ('SEARCH', 'ALL')),
1470 ('response', ('EXISTS',)),
1471 ('append', (None, None, None, test_mesg)),
1472 ('recent', ()),
1473 ('logout', ()),
1474 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001475
Tim Peters07e99cb2001-01-14 23:47:14 +00001476 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001477 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001478 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001479 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001480 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001481 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001482
Tim Peters07e99cb2001-01-14 23:47:14 +00001483 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001484 if stream_command:
1485 M = IMAP4_stream(stream_command)
1486 else:
1487 M = IMAP4(host)
1488 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001489 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001490 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001491 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001492
Tim Peters07e99cb2001-01-14 23:47:14 +00001493 for cmd,args in test_seq1:
1494 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001495
Tim Peters07e99cb2001-01-14 23:47:14 +00001496 for ml in run('list', ('/tmp/', 'yy%')):
1497 mo = re.match(r'.*"([^"]+)"$', ml)
1498 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001499 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001500 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001501
Tim Peters07e99cb2001-01-14 23:47:14 +00001502 for cmd,args in test_seq2:
1503 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001504
Tim Peters07e99cb2001-01-14 23:47:14 +00001505 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1506 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001507
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001508 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001509 if not uid: continue
1510 run('uid', ('FETCH', '%s' % uid[-1],
1511 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001512
Tim Peters07e99cb2001-01-14 23:47:14 +00001513 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001514
Tim Peters07e99cb2001-01-14 23:47:14 +00001515 except:
1516 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001517
Tim Peters07e99cb2001-01-14 23:47:14 +00001518 if not Debug:
1519 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001520If you would like to see debugging output,
1521try: %s -d5
1522''' % sys.argv[0]
1523
Tim Peters07e99cb2001-01-14 23:47:14 +00001524 raise