blob: 156b15db3d12149f99a09aa07ef8e28089720429 [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
Piers Laudere0273de2002-11-22 05:53:04 +000025import binascii, os, random, re, socket, 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
32CRLF = '\r\n'
33Debug = 0
34IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000035IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000036AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000037
Tim Peters07e99cb2001-01-14 23:47:14 +000038# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000039
40Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000041 # name valid states
42 'APPEND': ('AUTH', 'SELECTED'),
43 'AUTHENTICATE': ('NONAUTH',),
44 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
45 'CHECK': ('SELECTED',),
46 'CLOSE': ('SELECTED',),
47 'COPY': ('SELECTED',),
48 'CREATE': ('AUTH', 'SELECTED'),
49 'DELETE': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000050 'DELETEACL': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000051 'EXAMINE': ('AUTH', 'SELECTED'),
52 'EXPUNGE': ('SELECTED',),
53 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000054 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000055 'GETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000056 'GETQUOTA': ('AUTH', 'SELECTED'),
57 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Martin v. Löwis7b9190b2004-07-27 05:07:19 +000058 'MYRIGHTS': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000059 'LIST': ('AUTH', 'SELECTED'),
60 'LOGIN': ('NONAUTH',),
61 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
62 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000063 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000064 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000065 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000066 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000067 'RENAME': ('AUTH', 'SELECTED'),
68 'SEARCH': ('SELECTED',),
69 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000070 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauderd80ef022005-06-01 23:50:52 +000071 'SETANNOTATION':('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000072 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000073 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000074 'STATUS': ('AUTH', 'SELECTED'),
75 'STORE': ('SELECTED',),
76 'SUBSCRIBE': ('AUTH', 'SELECTED'),
Martin v. Löwisd8921372003-11-10 06:44:44 +000077 'THREAD': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000078 'UID': ('SELECTED',),
79 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
80 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000081
Tim Peters07e99cb2001-01-14 23:47:14 +000082# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000083
Guido van Rossumeda960a1998-06-18 14:24:28 +000084Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000085Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
86InternalDate = re.compile(r'.*INTERNALDATE "'
Piers Lauder8659d902005-03-02 09:13:45 +000087 r'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
Tim Peters07e99cb2001-01-14 23:47:14 +000088 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
89 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
90 r'"')
Antoine Pitroufd036452008-08-19 17:56:33 +000091Literal = re.compile(r'.*{(?P<size>\d+)}$', re.ASCII)
Piers Lauder533366b2003-04-29 23:58:08 +000092MapCRLF = re.compile(r'\r\n|\r|\n')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000093Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000094Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Antoine Pitroufd036452008-08-19 17:56:33 +000095Untagged_status = re.compile(
96 r'\* (?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
Antoine Pitroufd036452008-08-19 17:56:33 +0000150 mustquote = re.compile(r"[^\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))
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 self.tagre = re.compile(r'(?P<tag>'
171 + self.tagpre
Antoine Pitroufd036452008-08-19 17:56:33 +0000172 + r'\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')
Piers Lauderd80ef022005-06-01 23:50:52 +0000196 self.capabilities = tuple(dat[-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000197
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 if __debug__:
199 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000200 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000201
Tim Peters07e99cb2001-01-14 23:47:14 +0000202 for version in AllowedVersions:
203 if not version in self.capabilities:
204 continue
205 self.PROTOCOL_VERSION = version
206 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000207
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000209
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000210
Tim Peters07e99cb2001-01-14 23:47:14 +0000211 def __getattr__(self, attr):
212 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000213 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000214 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000216
217
218
Piers Lauder15e5d532001-07-20 10:52:06 +0000219 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000220
221
Piers Lauderf97b2d72002-06-05 22:31:57 +0000222 def open(self, host = '', port = IMAP4_PORT):
223 """Setup connection to remote server on "host:port"
224 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000225 This connection will be used by the routines:
226 read, readline, send, shutdown.
227 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000228 self.host = host
229 self.port = port
Tim Peters07e99cb2001-01-14 23:47:14 +0000230 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +0000231 self.sock.connect((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000232 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000233
234
Piers Lauder15e5d532001-07-20 10:52:06 +0000235 def read(self, size):
236 """Read 'size' bytes from remote."""
237 return self.file.read(size)
238
239
240 def readline(self):
241 """Read line from remote."""
242 return self.file.readline()
243
244
245 def send(self, data):
246 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000247 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000248
Piers Lauderf2d7d152002-02-22 01:15:17 +0000249
Piers Lauder15e5d532001-07-20 10:52:06 +0000250 def shutdown(self):
251 """Close I/O established in "open"."""
252 self.file.close()
253 self.sock.close()
254
255
256 def socket(self):
257 """Return socket instance used to connect to IMAP4 server.
258
259 socket = <instance>.socket()
260 """
261 return self.sock
262
263
264
265 # Utility methods
266
267
Tim Peters07e99cb2001-01-14 23:47:14 +0000268 def recent(self):
269 """Return most recent 'RECENT' responses if any exist,
270 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000271
Tim Peters07e99cb2001-01-14 23:47:14 +0000272 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000273
Tim Peters07e99cb2001-01-14 23:47:14 +0000274 'data' is None if no new messages,
275 else list of RECENT responses, most recent last.
276 """
277 name = 'RECENT'
278 typ, dat = self._untagged_response('OK', [None], name)
279 if dat[-1]:
280 return typ, dat
281 typ, dat = self.noop() # Prod server for response
282 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000283
284
Tim Peters07e99cb2001-01-14 23:47:14 +0000285 def response(self, code):
286 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000287
Tim Peters07e99cb2001-01-14 23:47:14 +0000288 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000289
Tim Peters07e99cb2001-01-14 23:47:14 +0000290 (code, [data]) = <instance>.response(code)
291 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000292 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000293
294
Guido van Rossum26367a01998-09-28 15:34:46 +0000295
Tim Peters07e99cb2001-01-14 23:47:14 +0000296 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000297
298
Tim Peters07e99cb2001-01-14 23:47:14 +0000299 def append(self, mailbox, flags, date_time, message):
300 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000301
Tim Peters07e99cb2001-01-14 23:47:14 +0000302 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000303
Tim Peters07e99cb2001-01-14 23:47:14 +0000304 All args except `message' can be None.
305 """
306 name = 'APPEND'
307 if not mailbox:
308 mailbox = 'INBOX'
309 if flags:
310 if (flags[0],flags[-1]) != ('(',')'):
311 flags = '(%s)' % flags
312 else:
313 flags = None
314 if date_time:
315 date_time = Time2Internaldate(date_time)
316 else:
317 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000318 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000319 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000320
321
Tim Peters07e99cb2001-01-14 23:47:14 +0000322 def authenticate(self, mechanism, authobject):
323 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000324
Tim Peters07e99cb2001-01-14 23:47:14 +0000325 'mechanism' specifies which authentication mechanism is to
326 be used - it must appear in <instance>.capabilities in the
327 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000328
Tim Peters07e99cb2001-01-14 23:47:14 +0000329 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000330
Tim Peters07e99cb2001-01-14 23:47:14 +0000331 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000332
Tim Peters07e99cb2001-01-14 23:47:14 +0000333 It will be called to process server continuation responses.
334 It should return data that will be encoded and sent to server.
335 It should return None if the client abort response '*' should
336 be sent instead.
337 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000338 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000339 # XXX: shouldn't this code be removed, not commented out?
340 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000341 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000342 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 self.literal = _Authenticator(authobject).process
344 typ, dat = self._simple_command('AUTHENTICATE', mech)
345 if typ != 'OK':
346 raise self.error(dat[-1])
347 self.state = 'AUTH'
348 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000349
350
Piers Lauderd80ef022005-06-01 23:50:52 +0000351 def capability(self):
352 """(typ, [data]) = <instance>.capability()
353 Fetch capabilities list from server."""
354
355 name = 'CAPABILITY'
356 typ, dat = self._simple_command(name)
357 return self._untagged_response(typ, dat, name)
358
359
Tim Peters07e99cb2001-01-14 23:47:14 +0000360 def check(self):
361 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000362
Tim Peters07e99cb2001-01-14 23:47:14 +0000363 (typ, [data]) = <instance>.check()
364 """
365 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000366
367
Tim Peters07e99cb2001-01-14 23:47:14 +0000368 def close(self):
369 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000370
Tim Peters07e99cb2001-01-14 23:47:14 +0000371 Deleted messages are removed from writable mailbox.
372 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 (typ, [data]) = <instance>.close()
375 """
376 try:
377 typ, dat = self._simple_command('CLOSE')
378 finally:
379 self.state = 'AUTH'
380 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000381
382
Tim Peters07e99cb2001-01-14 23:47:14 +0000383 def copy(self, message_set, new_mailbox):
384 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000385
Tim Peters07e99cb2001-01-14 23:47:14 +0000386 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
387 """
388 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000389
390
Tim Peters07e99cb2001-01-14 23:47:14 +0000391 def create(self, mailbox):
392 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000393
Tim Peters07e99cb2001-01-14 23:47:14 +0000394 (typ, [data]) = <instance>.create(mailbox)
395 """
396 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000397
398
Tim Peters07e99cb2001-01-14 23:47:14 +0000399 def delete(self, mailbox):
400 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000401
Tim Peters07e99cb2001-01-14 23:47:14 +0000402 (typ, [data]) = <instance>.delete(mailbox)
403 """
404 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000405
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000406 def deleteacl(self, mailbox, who):
407 """Delete the ACLs (remove any rights) set for who on mailbox.
408
409 (typ, [data]) = <instance>.deleteacl(mailbox, who)
410 """
411 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000412
Tim Peters07e99cb2001-01-14 23:47:14 +0000413 def expunge(self):
414 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000419
Tim Peters07e99cb2001-01-14 23:47:14 +0000420 'data' is list of 'EXPUNGE'd message numbers in order received.
421 """
422 name = 'EXPUNGE'
423 typ, dat = self._simple_command(name)
424 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000425
426
Tim Peters07e99cb2001-01-14 23:47:14 +0000427 def fetch(self, message_set, message_parts):
428 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000431
Tim Peters07e99cb2001-01-14 23:47:14 +0000432 'message_parts' should be a string of selected parts
433 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000434
Tim Peters07e99cb2001-01-14 23:47:14 +0000435 'data' are tuples of message part envelope and data.
436 """
437 name = 'FETCH'
438 typ, dat = self._simple_command(name, message_set, message_parts)
439 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000440
441
Piers Lauder15e5d532001-07-20 10:52:06 +0000442 def getacl(self, mailbox):
443 """Get the ACLs for a mailbox.
444
445 (typ, [data]) = <instance>.getacl(mailbox)
446 """
447 typ, dat = self._simple_command('GETACL', mailbox)
448 return self._untagged_response(typ, dat, 'ACL')
449
450
Piers Lauderd80ef022005-06-01 23:50:52 +0000451 def getannotation(self, mailbox, entry, attribute):
452 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
453 Retrieve ANNOTATIONs."""
454
455 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
456 return self._untagged_response(typ, dat, 'ANNOTATION')
457
458
Piers Lauder3fca2912002-06-17 07:07:20 +0000459 def getquota(self, root):
460 """Get the quota root's resource usage and limits.
461
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000462 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000463
464 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000465 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000466 typ, dat = self._simple_command('GETQUOTA', root)
467 return self._untagged_response(typ, dat, 'QUOTA')
468
469
470 def getquotaroot(self, mailbox):
471 """Get the list of quota roots for the named mailbox.
472
473 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000474 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000475 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000476 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
477 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000478 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000479
480
Tim Peters07e99cb2001-01-14 23:47:14 +0000481 def list(self, directory='""', pattern='*'):
482 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000483
Tim Peters07e99cb2001-01-14 23:47:14 +0000484 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000485
Tim Peters07e99cb2001-01-14 23:47:14 +0000486 'data' is list of LIST responses.
487 """
488 name = 'LIST'
489 typ, dat = self._simple_command(name, directory, pattern)
490 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000491
492
Tim Peters07e99cb2001-01-14 23:47:14 +0000493 def login(self, user, password):
494 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000497
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 NB: 'password' will be quoted.
499 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000500 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
501 if typ != 'OK':
502 raise self.error(dat[-1])
503 self.state = 'AUTH'
504 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
506
Piers Laudere0273de2002-11-22 05:53:04 +0000507 def login_cram_md5(self, user, password):
508 """ Force use of CRAM-MD5 authentication.
509
510 (typ, [data]) = <instance>.login_cram_md5(user, password)
511 """
512 self.user, self.password = user, password
513 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
514
515
516 def _CRAM_MD5_AUTH(self, challenge):
517 """ Authobject to use with CRAM-MD5 authentication. """
518 import hmac
519 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
520
521
Tim Peters07e99cb2001-01-14 23:47:14 +0000522 def logout(self):
523 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000524
Tim Peters07e99cb2001-01-14 23:47:14 +0000525 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000526
Tim Peters07e99cb2001-01-14 23:47:14 +0000527 Returns server 'BYE' response.
528 """
529 self.state = 'LOGOUT'
530 try: typ, dat = self._simple_command('LOGOUT')
531 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000532 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000533 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000534 return 'BYE', self.untagged_responses['BYE']
535 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000536
537
Tim Peters07e99cb2001-01-14 23:47:14 +0000538 def lsub(self, directory='""', pattern='*'):
539 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000540
Tim Peters07e99cb2001-01-14 23:47:14 +0000541 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000542
Tim Peters07e99cb2001-01-14 23:47:14 +0000543 'data' are tuples of message part envelope and data.
544 """
545 name = 'LSUB'
546 typ, dat = self._simple_command(name, directory, pattern)
547 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000548
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000549 def myrights(self, mailbox):
550 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
551
552 (typ, [data]) = <instance>.myrights(mailbox)
553 """
554 typ,dat = self._simple_command('MYRIGHTS', mailbox)
555 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000556
Piers Lauder15e5d532001-07-20 10:52:06 +0000557 def namespace(self):
558 """ Returns IMAP namespaces ala rfc2342
559
560 (typ, [data, ...]) = <instance>.namespace()
561 """
562 name = 'NAMESPACE'
563 typ, dat = self._simple_command(name)
564 return self._untagged_response(typ, dat, name)
565
566
Tim Peters07e99cb2001-01-14 23:47:14 +0000567 def noop(self):
568 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000569
Piers Laudere0273de2002-11-22 05:53:04 +0000570 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000571 """
572 if __debug__:
573 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000574 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000575 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000576
577
Tim Peters07e99cb2001-01-14 23:47:14 +0000578 def partial(self, message_num, message_part, start, length):
579 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000580
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000582
Tim Peters07e99cb2001-01-14 23:47:14 +0000583 'data' is tuple of message part envelope and data.
584 """
585 name = 'PARTIAL'
586 typ, dat = self._simple_command(name, message_num, message_part, start, length)
587 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000588
589
Piers Laudere0273de2002-11-22 05:53:04 +0000590 def proxyauth(self, user):
591 """Assume authentication as "user".
592
593 Allows an authorised administrator to proxy into any user's
594 mailbox.
595
596 (typ, [data]) = <instance>.proxyauth(user)
597 """
598
599 name = 'PROXYAUTH'
600 return self._simple_command('PROXYAUTH', user)
601
602
Tim Peters07e99cb2001-01-14 23:47:14 +0000603 def rename(self, oldmailbox, newmailbox):
604 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000605
Piers Laudere0273de2002-11-22 05:53:04 +0000606 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000607 """
608 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000609
610
Tim Peters07e99cb2001-01-14 23:47:14 +0000611 def search(self, charset, *criteria):
612 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000613
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000614 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000615
Tim Peters07e99cb2001-01-14 23:47:14 +0000616 'data' is space separated list of matching message numbers.
617 """
618 name = 'SEARCH'
619 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000620 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000621 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000622 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000623 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000624
625
Piers Lauder14f39402005-08-31 10:46:29 +0000626 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000627 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000628
Tim Peters07e99cb2001-01-14 23:47:14 +0000629 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000630
Piers Lauder14f39402005-08-31 10:46:29 +0000631 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000632
Tim Peters07e99cb2001-01-14 23:47:14 +0000633 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000634
635 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
636 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000638 self.untagged_responses = {} # Flush old responses.
639 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000640 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000641 name = 'EXAMINE'
642 else:
643 name = 'SELECT'
644 typ, dat = self._simple_command(name, mailbox)
645 if typ != 'OK':
646 self.state = 'AUTH' # Might have been 'SELECTED'
647 return typ, dat
648 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000649 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000650 and not readonly:
651 if __debug__:
652 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000653 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000654 raise self.readonly('%s is not writable' % mailbox)
655 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000656
657
Piers Lauder15e5d532001-07-20 10:52:06 +0000658 def setacl(self, mailbox, who, what):
659 """Set a mailbox acl.
660
Piers Lauderf167dc32004-03-25 00:12:21 +0000661 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000662 """
663 return self._simple_command('SETACL', mailbox, who, what)
664
665
Piers Lauderd80ef022005-06-01 23:50:52 +0000666 def setannotation(self, *args):
667 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
668 Set ANNOTATIONs."""
669
670 typ, dat = self._simple_command('SETANNOTATION', *args)
671 return self._untagged_response(typ, dat, 'ANNOTATION')
672
673
Piers Lauder3fca2912002-06-17 07:07:20 +0000674 def setquota(self, root, limits):
675 """Set the quota root's resource limits.
676
677 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000678 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000679 typ, dat = self._simple_command('SETQUOTA', root, limits)
680 return self._untagged_response(typ, dat, 'QUOTA')
681
682
Piers Lauder15e5d532001-07-20 10:52:06 +0000683 def sort(self, sort_criteria, charset, *search_criteria):
684 """IMAP4rev1 extension SORT command.
685
686 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
687 """
688 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000689 #if not name in self.capabilities: # Let the server decide!
690 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000691 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000692 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000693 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000694 return self._untagged_response(typ, dat, name)
695
696
Tim Peters07e99cb2001-01-14 23:47:14 +0000697 def status(self, mailbox, names):
698 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000699
Tim Peters07e99cb2001-01-14 23:47:14 +0000700 (typ, [data]) = <instance>.status(mailbox, names)
701 """
702 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000703 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000704 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000705 typ, dat = self._simple_command(name, mailbox, names)
706 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000707
708
Tim Peters07e99cb2001-01-14 23:47:14 +0000709 def store(self, message_set, command, flags):
710 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000711
Tim Peters07e99cb2001-01-14 23:47:14 +0000712 (typ, [data]) = <instance>.store(message_set, command, flags)
713 """
714 if (flags[0],flags[-1]) != ('(',')'):
715 flags = '(%s)' % flags # Avoid quoting the flags
716 typ, dat = self._simple_command('STORE', message_set, command, flags)
717 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000718
719
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 def subscribe(self, mailbox):
721 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000722
Tim Peters07e99cb2001-01-14 23:47:14 +0000723 (typ, [data]) = <instance>.subscribe(mailbox)
724 """
725 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000726
727
Martin v. Löwisd8921372003-11-10 06:44:44 +0000728 def thread(self, threading_algorithm, charset, *search_criteria):
729 """IMAPrev1 extension THREAD command.
730
731 (type, [data]) = <instance>.thread(threading_alogrithm, charset, search_criteria, ...)
732 """
733 name = 'THREAD'
734 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
735 return self._untagged_response(typ, dat, name)
736
737
Tim Peters07e99cb2001-01-14 23:47:14 +0000738 def uid(self, command, *args):
739 """Execute "command arg ..." with messages identified by UID,
740 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000741
Tim Peters07e99cb2001-01-14 23:47:14 +0000742 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000743
Tim Peters07e99cb2001-01-14 23:47:14 +0000744 Returns response appropriate to 'command'.
745 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000746 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000747 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000748 raise self.error("Unknown IMAP4 UID command: %s" % command)
749 if self.state not in Commands[command]:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000750 raise self.error("command %s illegal in state %s, "
751 "only allowed in states %s" %
752 (command, self.state,
753 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000754 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000755 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000756 if command in ('SEARCH', 'SORT'):
757 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000758 else:
759 name = 'FETCH'
760 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000761
762
Tim Peters07e99cb2001-01-14 23:47:14 +0000763 def unsubscribe(self, mailbox):
764 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000765
Tim Peters07e99cb2001-01-14 23:47:14 +0000766 (typ, [data]) = <instance>.unsubscribe(mailbox)
767 """
768 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000769
770
Tim Peters07e99cb2001-01-14 23:47:14 +0000771 def xatom(self, name, *args):
772 """Allow simple extension commands
773 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000774
Piers Lauder15e5d532001-07-20 10:52:06 +0000775 Assumes command is legal in current state.
776
Tim Peters07e99cb2001-01-14 23:47:14 +0000777 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000778
779 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000780 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000781 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000782 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000783 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000784 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000785 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000786 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000787
788
789
Tim Peters07e99cb2001-01-14 23:47:14 +0000790 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000791
792
Tim Peters07e99cb2001-01-14 23:47:14 +0000793 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000794
Tim Peters07e99cb2001-01-14 23:47:14 +0000795 if dat is None: dat = ''
796 ur = self.untagged_responses
797 if __debug__:
798 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000799 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000801 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000802 ur[typ].append(dat)
803 else:
804 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000805
806
Tim Peters07e99cb2001-01-14 23:47:14 +0000807 def _check_bye(self):
808 bye = self.untagged_responses.get('BYE')
809 if bye:
810 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000811
812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000814
Tim Peters07e99cb2001-01-14 23:47:14 +0000815 if self.state not in Commands[name]:
816 self.literal = None
Guido van Rossumd8faa362007-04-27 19:54:29 +0000817 raise self.error("command %s illegal in state %s, "
818 "only allowed in states %s" %
819 (name, self.state,
820 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000821
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000823 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000824 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000825
Raymond Hettinger54f02222002-06-01 14:18:47 +0000826 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000827 and not self.is_readonly:
828 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000829
Tim Peters07e99cb2001-01-14 23:47:14 +0000830 tag = self._new_tag()
831 data = '%s %s' % (tag, name)
832 for arg in args:
833 if arg is None: continue
834 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000835
Tim Peters07e99cb2001-01-14 23:47:14 +0000836 literal = self.literal
837 if literal is not None:
838 self.literal = None
839 if type(literal) is type(self._command):
840 literator = literal
841 else:
842 literator = None
843 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000844
Tim Peters07e99cb2001-01-14 23:47:14 +0000845 if __debug__:
846 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000847 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000848 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000849 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000850
Tim Peters07e99cb2001-01-14 23:47:14 +0000851 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000852 self.send('%s%s' % (data, CRLF))
Guido van Rossumb940e112007-01-10 16:19:56 +0000853 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000855
Tim Peters07e99cb2001-01-14 23:47:14 +0000856 if literal is None:
857 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000858
Tim Peters07e99cb2001-01-14 23:47:14 +0000859 while 1:
860 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000861
Tim Peters07e99cb2001-01-14 23:47:14 +0000862 while self._get_response():
863 if self.tagged_commands[tag]: # BAD/NO?
864 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000865
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000867
Tim Peters07e99cb2001-01-14 23:47:14 +0000868 if literator:
869 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000870
Tim Peters07e99cb2001-01-14 23:47:14 +0000871 if __debug__:
872 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000873 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000874
Tim Peters07e99cb2001-01-14 23:47:14 +0000875 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000876 self.send(literal)
877 self.send(CRLF)
Guido van Rossumb940e112007-01-10 16:19:56 +0000878 except (socket.error, OSError) as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000880
Tim Peters07e99cb2001-01-14 23:47:14 +0000881 if not literator:
882 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
Tim Peters07e99cb2001-01-14 23:47:14 +0000884 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000885
886
Tim Peters07e99cb2001-01-14 23:47:14 +0000887 def _command_complete(self, name, tag):
888 self._check_bye()
889 try:
890 typ, data = self._get_tagged_response(tag)
Guido van Rossumb940e112007-01-10 16:19:56 +0000891 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000892 raise self.abort('command: %s => %s' % (name, val))
Guido van Rossumb940e112007-01-10 16:19:56 +0000893 except self.error as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000894 raise self.error('command: %s => %s' % (name, val))
895 self._check_bye()
896 if typ == 'BAD':
897 raise self.error('%s command error: %s %s' % (name, typ, data))
898 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000899
900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000902
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 # Read response and store.
904 #
905 # Returns None for continuation responses,
906 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000911
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 if self._match(self.tagre, resp):
913 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000914 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000916
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 typ = self.mo.group('type')
918 dat = self.mo.group('data')
919 self.tagged_commands[tag] = (typ, [dat])
920 else:
921 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 if not self._match(Untagged_response, resp):
926 if self._match(Untagged_status, resp):
927 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000928
Tim Peters07e99cb2001-01-14 23:47:14 +0000929 if self.mo is None:
930 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000931
Tim Peters07e99cb2001-01-14 23:47:14 +0000932 if self._match(Continuation, resp):
933 self.continuation_response = self.mo.group('data')
934 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000935
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000937
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 typ = self.mo.group('type')
939 dat = self.mo.group('data')
940 if dat is None: dat = '' # Null untagged response
941 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000949 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000950 if __debug__:
951 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000952 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000953 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000960
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
968 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000969
Tim Peters07e99cb2001-01-14 23:47:14 +0000970 if __debug__:
971 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000972 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000973
Tim Peters07e99cb2001-01-14 23:47:14 +0000974 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000975
976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 while 1:
980 result = self.tagged_commands[tag]
981 if result is not None:
982 del self.tagged_commands[tag]
983 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000984
Tim Peters07e99cb2001-01-14 23:47:14 +0000985 # Some have reported "unexpected response" exceptions.
986 # Note that ignoring them here causes loops.
987 # Instead, send me details of the unexpected response and
988 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000989
Tim Peters07e99cb2001-01-14 23:47:14 +0000990 try:
991 self._get_response()
Guido van Rossumb940e112007-01-10 16:19:56 +0000992 except self.abort as val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 if __debug__:
994 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000995 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000997
998
Tim Peters07e99cb2001-01-14 23:47:14 +0000999 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001000
Piers Lauder15e5d532001-07-20 10:52:06 +00001001 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001002 if not line:
1003 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001004
Tim Peters07e99cb2001-01-14 23:47:14 +00001005 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001006
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 line = line[:-2]
1008 if __debug__:
1009 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001010 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001012 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001013 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001014
1015
Tim Peters07e99cb2001-01-14 23:47:14 +00001016 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001017
Tim Peters07e99cb2001-01-14 23:47:14 +00001018 # Run compiled regular expression match method on 's'.
1019 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001020
Tim Peters07e99cb2001-01-14 23:47:14 +00001021 self.mo = cre.match(s)
1022 if __debug__:
1023 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001024 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001026
1027
Tim Peters07e99cb2001-01-14 23:47:14 +00001028 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001029
Tim Peters07e99cb2001-01-14 23:47:14 +00001030 tag = '%s%s' % (self.tagpre, self.tagnum)
1031 self.tagnum = self.tagnum + 1
1032 self.tagged_commands[tag] = None
1033 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001034
1035
Tim Peters07e99cb2001-01-14 23:47:14 +00001036 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001037
Tim Peters07e99cb2001-01-14 23:47:14 +00001038 # Must quote command args if non-alphanumeric chars present,
1039 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001040
Tim Peters07e99cb2001-01-14 23:47:14 +00001041 if type(arg) is not type(''):
1042 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001043 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001044 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001045 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001046 return arg
1047 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001048
1049
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001051
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001052 arg = arg.replace('\\', '\\\\')
1053 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001054
Tim Peters07e99cb2001-01-14 23:47:14 +00001055 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001056
1057
Tim Peters07e99cb2001-01-14 23:47:14 +00001058 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001059
Guido van Rossum68468eb2003-02-27 20:14:51 +00001060 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001061
1062
Tim Peters07e99cb2001-01-14 23:47:14 +00001063 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001064
Tim Peters07e99cb2001-01-14 23:47:14 +00001065 if typ == 'NO':
1066 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001067 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001068 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001069 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001070 if __debug__:
1071 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001072 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001073 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001074
1075
Piers Lauderf2d7d152002-02-22 01:15:17 +00001076 if __debug__:
1077
1078 def _mesg(self, s, secs=None):
1079 if secs is None:
1080 secs = time.time()
1081 tm = time.strftime('%M:%S', time.localtime(secs))
1082 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1083 sys.stderr.flush()
1084
1085 def _dump_ur(self, dict):
1086 # Dump untagged responses (in `dict').
1087 l = dict.items()
1088 if not l: return
1089 t = '\n\t\t'
1090 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1091 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1092
1093 def _log(self, line):
1094 # Keep log of last `_cmd_log_len' interactions for debugging.
1095 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1096 self._cmd_log_idx += 1
1097 if self._cmd_log_idx >= self._cmd_log_len:
1098 self._cmd_log_idx = 0
1099
1100 def print_log(self):
1101 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1102 i, n = self._cmd_log_idx, self._cmd_log_len
1103 while n:
1104 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001105 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001106 except:
1107 pass
1108 i += 1
1109 if i >= self._cmd_log_len:
1110 i = 0
1111 n -= 1
1112
1113
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001114
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001115try:
1116 import ssl
1117except ImportError:
1118 pass
1119else:
1120 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001121
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001122 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001123
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001124 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001125
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001126 host - host's name (default: localhost);
1127 port - port number (default: standard IMAP4 SSL port).
1128 keyfile - PEM formatted file that contains your private key (default: None);
1129 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001130
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001131 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001132 """
Piers Laudera4f83132002-03-08 01:53:24 +00001133
1134
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001135 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1136 self.keyfile = keyfile
1137 self.certfile = certfile
1138 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001139
1140
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001141 def open(self, host = '', port = IMAP4_SSL_PORT):
1142 """Setup connection to remote server on "host:port".
1143 (default: localhost:standard IMAP4 SSL port).
1144 This connection will be used by the routines:
1145 read, readline, send, shutdown.
1146 """
1147 self.host = host
1148 self.port = port
Bill Janssena7712092008-02-01 02:16:46 +00001149 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1150 sock.connect((host, port))
1151 self.sock = ssl.wrap_socket(sock, self.keyfile, self.certfile)
1152 self.file = self.sock.makefile('rb')
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001153
1154
Christian Heimesd3eb5a152008-02-24 00:38:49 +00001155 def read(self, size):
1156 """Read 'size' bytes from remote."""
1157 # sslobj.read() sometimes returns < size bytes
1158 chunks = []
1159 read = 0
1160 while read < size:
1161 data = self.sslobj.read(min(size-read, 16384))
1162 read += len(data)
1163 chunks.append(data)
1164
1165 return b''.join(chunks)
1166
1167
1168 def readline(self):
1169 """Read line from remote."""
1170 line = []
1171 while 1:
1172 char = self.sslobj.read(1)
1173 line.append(char)
1174 if char == b"\n": return b''.join(line)
1175
1176
1177 def send(self, data):
1178 """Send data to remote."""
1179 bytes = len(data)
1180 while bytes > 0:
1181 sent = self.sslobj.write(data)
1182 if sent == bytes:
1183 break # avoid copy
1184 data = data[sent:]
1185 bytes = bytes - sent
1186
1187
1188 def shutdown(self):
1189 """Close I/O established in "open"."""
1190 self.sock.close()
1191
1192
1193 def socket(self):
1194 """Return socket instance used to connect to IMAP4 server.
1195
1196 socket = <instance>.socket()
1197 """
1198 return self.sock
1199
1200
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001201 def ssl(self):
1202 """Return SSLObject instance used to communicate with the IMAP4 server.
1203
Thomas Wouters1b7f8912007-09-19 03:06:30 +00001204 ssl = ssl.wrap_socket(<instance>.socket)
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001205 """
Bill Janssena7712092008-02-01 02:16:46 +00001206 return self.sock
Thomas Wouters47b49bf2007-08-30 22:15:33 +00001207
1208 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001209
1210
Piers Laudere0273de2002-11-22 05:53:04 +00001211class IMAP4_stream(IMAP4):
1212
1213 """IMAP4 client class over a stream
1214
1215 Instantiate with: IMAP4_stream(command)
1216
1217 where "command" is a string that can be passed to os.popen2()
1218
1219 for more documentation see the docstring of the parent class IMAP4.
1220 """
1221
1222
1223 def __init__(self, command):
1224 self.command = command
1225 IMAP4.__init__(self)
1226
1227
1228 def open(self, host = None, port = None):
1229 """Setup a stream connection.
1230 This connection will be used by the routines:
1231 read, readline, send, shutdown.
1232 """
1233 self.host = None # For compatibility with parent class
1234 self.port = None
1235 self.sock = None
1236 self.file = None
1237 self.writefile, self.readfile = os.popen2(self.command)
1238
1239
1240 def read(self, size):
1241 """Read 'size' bytes from remote."""
1242 return self.readfile.read(size)
1243
1244
1245 def readline(self):
1246 """Read line from remote."""
1247 return self.readfile.readline()
1248
1249
1250 def send(self, data):
1251 """Send data to remote."""
1252 self.writefile.write(data)
1253 self.writefile.flush()
1254
1255
1256 def shutdown(self):
1257 """Close I/O established in "open"."""
1258 self.readfile.close()
1259 self.writefile.close()
1260
1261
1262
Guido van Rossumeda960a1998-06-18 14:24:28 +00001263class _Authenticator:
1264
Tim Peters07e99cb2001-01-14 23:47:14 +00001265 """Private class to provide en/decoding
1266 for base64-based authentication conversation.
1267 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001268
Tim Peters07e99cb2001-01-14 23:47:14 +00001269 def __init__(self, mechinst):
1270 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001271
Tim Peters07e99cb2001-01-14 23:47:14 +00001272 def process(self, data):
1273 ret = self.mech(self.decode(data))
1274 if ret is None:
1275 return '*' # Abort conversation
1276 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001277
Tim Peters07e99cb2001-01-14 23:47:14 +00001278 def encode(self, inp):
1279 #
1280 # Invoke binascii.b2a_base64 iteratively with
1281 # short even length buffers, strip the trailing
1282 # line feed from the result and append. "Even"
1283 # means a number that factors to both 6 and 8,
1284 # so when it gets to the end of the 8-bit input
1285 # there's no partial 6-bit output.
1286 #
1287 oup = ''
1288 while inp:
1289 if len(inp) > 48:
1290 t = inp[:48]
1291 inp = inp[48:]
1292 else:
1293 t = inp
1294 inp = ''
1295 e = binascii.b2a_base64(t)
1296 if e:
1297 oup = oup + e[:-1]
1298 return oup
1299
1300 def decode(self, inp):
1301 if not inp:
1302 return ''
1303 return binascii.a2b_base64(inp)
1304
Guido van Rossumeda960a1998-06-18 14:24:28 +00001305
1306
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001307Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001308 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001309
1310def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001312
Tim Peters07e99cb2001-01-14 23:47:14 +00001313 Returns Python time module tuple.
1314 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001315
Tim Peters07e99cb2001-01-14 23:47:14 +00001316 mo = InternalDate.match(resp)
1317 if not mo:
1318 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001319
Tim Peters07e99cb2001-01-14 23:47:14 +00001320 mon = Mon2num[mo.group('mon')]
1321 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001323 day = int(mo.group('day'))
1324 year = int(mo.group('year'))
1325 hour = int(mo.group('hour'))
1326 min = int(mo.group('min'))
1327 sec = int(mo.group('sec'))
1328 zoneh = int(mo.group('zoneh'))
1329 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001330
Tim Peters07e99cb2001-01-14 23:47:14 +00001331 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001332
Tim Peters07e99cb2001-01-14 23:47:14 +00001333 zone = (zoneh*60 + zonem)*60
1334 if zonen == '-':
1335 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001336
Tim Peters07e99cb2001-01-14 23:47:14 +00001337 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001338
Tim Peters07e99cb2001-01-14 23:47:14 +00001339 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001340
Tim Peters07e99cb2001-01-14 23:47:14 +00001341 # Following is necessary because the time module has no 'mkgmtime'.
1342 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 lt = time.localtime(utc)
1345 if time.daylight and lt[-1]:
1346 zone = zone + time.altzone
1347 else:
1348 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001349
Tim Peters07e99cb2001-01-14 23:47:14 +00001350 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001351
1352
1353
1354def Int2AP(num):
1355
Tim Peters07e99cb2001-01-14 23:47:14 +00001356 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001357
Tim Peters07e99cb2001-01-14 23:47:14 +00001358 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1359 num = int(abs(num))
1360 while num:
1361 num, mod = divmod(num, 16)
1362 val = AP[mod] + val
1363 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001364
1365
1366
1367def ParseFlags(resp):
1368
Tim Peters07e99cb2001-01-14 23:47:14 +00001369 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001370
Tim Peters07e99cb2001-01-14 23:47:14 +00001371 mo = Flags.match(resp)
1372 if not mo:
1373 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001374
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001375 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001376
1377
1378def Time2Internaldate(date_time):
1379
Tim Peters07e99cb2001-01-14 23:47:14 +00001380 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001381
Tim Peters07e99cb2001-01-14 23:47:14 +00001382 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1383 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001384
Fred Drakedb519202002-01-05 17:17:09 +00001385 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001386 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001387 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001388 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001389 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001390 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001391 else:
1392 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001393
Tim Peters07e99cb2001-01-14 23:47:14 +00001394 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1395 if dt[0] == '0':
1396 dt = ' ' + dt[1:]
1397 if time.daylight and tt[-1]:
1398 zone = -time.altzone
1399 else:
1400 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001401 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001402
1403
1404
Guido van Rossum8c062211999-12-13 23:27:45 +00001405if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001406
Piers Laudere0273de2002-11-22 05:53:04 +00001407 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1408 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1409 # to test the IMAP4_stream class
1410
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001411 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001412
Tim Peters07e99cb2001-01-14 23:47:14 +00001413 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001414 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Guido van Rossumb940e112007-01-10 16:19:56 +00001415 except getopt.error as val:
Piers Laudere0273de2002-11-22 05:53:04 +00001416 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001417
Piers Laudere0273de2002-11-22 05:53:04 +00001418 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001419 for opt,val in optlist:
1420 if opt == '-d':
1421 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001422 elif opt == '-s':
1423 stream_command = val
1424 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001425
Tim Peters07e99cb2001-01-14 23:47:14 +00001426 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001427
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001431 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001432
Piers Lauder47404ff2003-04-29 23:40:59 +00001433 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 +00001434 test_seq1 = (
1435 ('login', (USER, PASSWD)),
1436 ('create', ('/tmp/xxx 1',)),
1437 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1438 ('CREATE', ('/tmp/yyz 2',)),
1439 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1440 ('list', ('/tmp', 'yy*')),
1441 ('select', ('/tmp/yyz 2',)),
1442 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001443 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001444 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001445 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001446 ('expunge', ()),
1447 ('recent', ()),
1448 ('close', ()),
1449 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001450
Tim Peters07e99cb2001-01-14 23:47:14 +00001451 test_seq2 = (
1452 ('select', ()),
1453 ('response',('UIDVALIDITY',)),
1454 ('uid', ('SEARCH', 'ALL')),
1455 ('response', ('EXISTS',)),
1456 ('append', (None, None, None, test_mesg)),
1457 ('recent', ()),
1458 ('logout', ()),
1459 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001460
Tim Peters07e99cb2001-01-14 23:47:14 +00001461 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001462 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001463 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001464 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001465 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001466 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001467
Tim Peters07e99cb2001-01-14 23:47:14 +00001468 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001469 if stream_command:
1470 M = IMAP4_stream(stream_command)
1471 else:
1472 M = IMAP4(host)
1473 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001474 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001475 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001476 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001477
Tim Peters07e99cb2001-01-14 23:47:14 +00001478 for cmd,args in test_seq1:
1479 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001480
Tim Peters07e99cb2001-01-14 23:47:14 +00001481 for ml in run('list', ('/tmp/', 'yy%')):
1482 mo = re.match(r'.*"([^"]+)"$', ml)
1483 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001484 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001485 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001486
Tim Peters07e99cb2001-01-14 23:47:14 +00001487 for cmd,args in test_seq2:
1488 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001489
Tim Peters07e99cb2001-01-14 23:47:14 +00001490 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1491 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001492
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001493 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001494 if not uid: continue
1495 run('uid', ('FETCH', '%s' % uid[-1],
1496 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001497
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001498 print('\nAll tests OK.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001499
Tim Peters07e99cb2001-01-14 23:47:14 +00001500 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001501 print('\nTests failed.')
Guido van Rossum66d45132000-03-28 20:20:53 +00001502
Tim Peters07e99cb2001-01-14 23:47:14 +00001503 if not Debug:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001504 print('''
Guido van Rossum66d45132000-03-28 20:20:53 +00001505If you would like to see debugging output,
1506try: %s -d5
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001507''' % sys.argv[0])
Guido van Rossum66d45132000-03-28 20:20:53 +00001508
Tim Peters07e99cb2001-01-14 23:47:14 +00001509 raise