blob: d1f62b0934528037c211a1d4a7c2ee771c853c5d [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 Peterson1a635e42010-01-02 02:43:04 +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
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'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000091Literal = re.compile(r'.*{(?P<size>\d+)}$')
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>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000095Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
96
97
98
99class IMAP4:
100
Tim Peters07e99cb2001-01-14 23:47:14 +0000101 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000102
Tim Peters07e99cb2001-01-14 23:47:14 +0000103 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000104
Tim Peters07e99cb2001-01-14 23:47:14 +0000105 host - host's name (default: localhost);
106 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000107
Tim Peters07e99cb2001-01-14 23:47:14 +0000108 All IMAP4rev1 commands are supported by methods of the same
109 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000110
Tim Peters07e99cb2001-01-14 23:47:14 +0000111 All arguments to commands are converted to strings, except for
112 AUTHENTICATE, and the last argument to APPEND which is passed as
113 an IMAP4 literal. If necessary (the string contains any
114 non-printing characters or white-space and isn't enclosed with
115 either parentheses or double quotes) each string is quoted.
116 However, the 'password' argument to the LOGIN command is always
117 quoted. If you want to avoid having an argument string quoted
118 (eg: the 'flags' argument to STORE) then enclose the string in
119 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000120
Tim Peters07e99cb2001-01-14 23:47:14 +0000121 Each command returns a tuple: (type, [data, ...]) where 'type'
122 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000123 tagged response, or untagged results from command. Each 'data'
124 is either a string, or a tuple. If a tuple, then the first part
125 is the header of the response, and the second part contains
126 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000127
Tim Peters07e99cb2001-01-14 23:47:14 +0000128 Errors raise the exception class <instance>.error("<reason>").
129 IMAP4 server errors raise <instance>.abort("<reason>"),
130 which is a sub-class of 'error'. Mailbox status changes
131 from READ-WRITE to READ-ONLY raise the exception class
132 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000133
Tim Peters07e99cb2001-01-14 23:47:14 +0000134 "error" exceptions imply a program error.
135 "abort" exceptions imply the connection should be reset, and
136 the command re-tried.
137 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000138
Piers Lauderd80ef022005-06-01 23:50:52 +0000139 Note: to use this module, you must read the RFCs pertaining to the
140 IMAP4 protocol, as the semantics of the arguments to each IMAP4
141 command are left to the invoker, not to mention the results. Also,
142 most IMAP servers implement a sub-set of the commands available here.
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000144
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 class error(Exception): pass # Logical errors - debug required
146 class abort(error): pass # Service errors - close and retry
147 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000148
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000150
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 self.debug = Debug
153 self.state = 'LOGOUT'
154 self.literal = None # A literal argument to a command
155 self.tagged_commands = {} # Tagged commands awaiting response
156 self.untagged_responses = {} # {typ: [data, ...], ...}
157 self.continuation_response = '' # Last continuation response
Piers Lauder14f39402005-08-31 10:46:29 +0000158 self.is_readonly = False # READ-ONLY desired state
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000160
Tim Peters07e99cb2001-01-14 23:47:14 +0000161 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000162
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000164
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 # Create unique tag for this session,
166 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000167
Piers Lauder2dfc1682005-07-05 04:20:07 +0000168 self.tagpre = Int2AP(random.randint(4096, 65535))
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 self.tagre = re.compile(r'(?P<tag>'
170 + self.tagpre
171 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000172
Tim Peters07e99cb2001-01-14 23:47:14 +0000173 # Get server welcome message,
174 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000175
Tim Peters07e99cb2001-01-14 23:47:14 +0000176 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000177 self._cmd_log_len = 10
178 self._cmd_log_idx = 0
179 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000180 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000181 self._mesg('imaplib version %s' % __version__)
182 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000183
Tim Peters07e99cb2001-01-14 23:47:14 +0000184 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000185 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000186 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000187 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000188 self.state = 'NONAUTH'
189 else:
190 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000191
Piers Lauderd80ef022005-06-01 23:50:52 +0000192 typ, dat = self.capability()
193 if dat == [None]:
Tim Peters07e99cb2001-01-14 23:47:14 +0000194 raise self.error('no CAPABILITY response from server')
Piers Lauderd80ef022005-06-01 23:50:52 +0000195 self.capabilities = tuple(dat[-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000196
Tim Peters07e99cb2001-01-14 23:47:14 +0000197 if __debug__:
198 if self.debug >= 3:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000199 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000200
Tim Peters07e99cb2001-01-14 23:47:14 +0000201 for version in AllowedVersions:
202 if not version in self.capabilities:
203 continue
204 self.PROTOCOL_VERSION = version
205 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000206
Tim Peters07e99cb2001-01-14 23:47:14 +0000207 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000208
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000209
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 def __getattr__(self, attr):
211 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000212 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000213 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000214 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000215
216
217
Piers Lauder15e5d532001-07-20 10:52:06 +0000218 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000219
220
Piers Lauderf97b2d72002-06-05 22:31:57 +0000221 def open(self, host = '', port = IMAP4_PORT):
222 """Setup connection to remote server on "host:port"
223 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000224 This connection will be used by the routines:
225 read, readline, send, shutdown.
226 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000227 self.host = host
228 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +0000229 self.sock = socket.create_connection((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000230 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000231
232
Piers Lauder15e5d532001-07-20 10:52:06 +0000233 def read(self, size):
234 """Read 'size' bytes from remote."""
235 return self.file.read(size)
236
237
238 def readline(self):
239 """Read line from remote."""
240 return self.file.readline()
241
242
243 def send(self, data):
244 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000245 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000246
Piers Lauderf2d7d152002-02-22 01:15:17 +0000247
Piers Lauder15e5d532001-07-20 10:52:06 +0000248 def shutdown(self):
249 """Close I/O established in "open"."""
250 self.file.close()
251 self.sock.close()
252
253
254 def socket(self):
255 """Return socket instance used to connect to IMAP4 server.
256
257 socket = <instance>.socket()
258 """
259 return self.sock
260
261
262
263 # Utility methods
264
265
Tim Peters07e99cb2001-01-14 23:47:14 +0000266 def recent(self):
267 """Return most recent 'RECENT' responses if any exist,
268 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000269
Tim Peters07e99cb2001-01-14 23:47:14 +0000270 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000271
Tim Peters07e99cb2001-01-14 23:47:14 +0000272 'data' is None if no new messages,
273 else list of RECENT responses, most recent last.
274 """
275 name = 'RECENT'
276 typ, dat = self._untagged_response('OK', [None], name)
277 if dat[-1]:
278 return typ, dat
279 typ, dat = self.noop() # Prod server for response
280 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000281
282
Tim Peters07e99cb2001-01-14 23:47:14 +0000283 def response(self, code):
284 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000285
Tim Peters07e99cb2001-01-14 23:47:14 +0000286 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000287
Tim Peters07e99cb2001-01-14 23:47:14 +0000288 (code, [data]) = <instance>.response(code)
289 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000290 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000291
292
Guido van Rossum26367a01998-09-28 15:34:46 +0000293
Tim Peters07e99cb2001-01-14 23:47:14 +0000294 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000295
296
Tim Peters07e99cb2001-01-14 23:47:14 +0000297 def append(self, mailbox, flags, date_time, message):
298 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000299
Tim Peters07e99cb2001-01-14 23:47:14 +0000300 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000301
Tim Peters07e99cb2001-01-14 23:47:14 +0000302 All args except `message' can be None.
303 """
304 name = 'APPEND'
305 if not mailbox:
306 mailbox = 'INBOX'
307 if flags:
308 if (flags[0],flags[-1]) != ('(',')'):
309 flags = '(%s)' % flags
310 else:
311 flags = None
312 if date_time:
313 date_time = Time2Internaldate(date_time)
314 else:
315 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000316 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000317 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000318
319
Tim Peters07e99cb2001-01-14 23:47:14 +0000320 def authenticate(self, mechanism, authobject):
321 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000322
Tim Peters07e99cb2001-01-14 23:47:14 +0000323 'mechanism' specifies which authentication mechanism is to
324 be used - it must appear in <instance>.capabilities in the
325 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000326
Tim Peters07e99cb2001-01-14 23:47:14 +0000327 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000328
Tim Peters07e99cb2001-01-14 23:47:14 +0000329 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000330
Tim Peters07e99cb2001-01-14 23:47:14 +0000331 It will be called to process server continuation responses.
332 It should return data that will be encoded and sent to server.
333 It should return None if the client abort response '*' should
334 be sent instead.
335 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000336 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000337 # XXX: shouldn't this code be removed, not commented out?
338 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000339 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000340 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000341 self.literal = _Authenticator(authobject).process
342 typ, dat = self._simple_command('AUTHENTICATE', mech)
343 if typ != 'OK':
344 raise self.error(dat[-1])
345 self.state = 'AUTH'
346 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000347
348
Piers Lauderd80ef022005-06-01 23:50:52 +0000349 def capability(self):
350 """(typ, [data]) = <instance>.capability()
351 Fetch capabilities list from server."""
352
353 name = 'CAPABILITY'
354 typ, dat = self._simple_command(name)
355 return self._untagged_response(typ, dat, name)
356
357
Tim Peters07e99cb2001-01-14 23:47:14 +0000358 def check(self):
359 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000360
Tim Peters07e99cb2001-01-14 23:47:14 +0000361 (typ, [data]) = <instance>.check()
362 """
363 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000364
365
Tim Peters07e99cb2001-01-14 23:47:14 +0000366 def close(self):
367 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 Deleted messages are removed from writable mailbox.
370 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000371
Tim Peters07e99cb2001-01-14 23:47:14 +0000372 (typ, [data]) = <instance>.close()
373 """
374 try:
375 typ, dat = self._simple_command('CLOSE')
376 finally:
377 self.state = 'AUTH'
378 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000379
380
Tim Peters07e99cb2001-01-14 23:47:14 +0000381 def copy(self, message_set, new_mailbox):
382 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000383
Tim Peters07e99cb2001-01-14 23:47:14 +0000384 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
385 """
386 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000387
388
Tim Peters07e99cb2001-01-14 23:47:14 +0000389 def create(self, mailbox):
390 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000391
Tim Peters07e99cb2001-01-14 23:47:14 +0000392 (typ, [data]) = <instance>.create(mailbox)
393 """
394 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000395
396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 def delete(self, mailbox):
398 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000399
Tim Peters07e99cb2001-01-14 23:47:14 +0000400 (typ, [data]) = <instance>.delete(mailbox)
401 """
402 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000403
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000404 def deleteacl(self, mailbox, who):
405 """Delete the ACLs (remove any rights) set for who on mailbox.
406
407 (typ, [data]) = <instance>.deleteacl(mailbox, who)
408 """
409 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000410
Tim Peters07e99cb2001-01-14 23:47:14 +0000411 def expunge(self):
412 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000413
Tim Peters07e99cb2001-01-14 23:47:14 +0000414 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000415
Tim Peters07e99cb2001-01-14 23:47:14 +0000416 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
Tim Peters07e99cb2001-01-14 23:47:14 +0000418 'data' is list of 'EXPUNGE'd message numbers in order received.
419 """
420 name = 'EXPUNGE'
421 typ, dat = self._simple_command(name)
422 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000423
424
Tim Peters07e99cb2001-01-14 23:47:14 +0000425 def fetch(self, message_set, message_parts):
426 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000427
Tim Peters07e99cb2001-01-14 23:47:14 +0000428 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000429
Tim Peters07e99cb2001-01-14 23:47:14 +0000430 'message_parts' should be a string of selected parts
431 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000432
Tim Peters07e99cb2001-01-14 23:47:14 +0000433 'data' are tuples of message part envelope and data.
434 """
435 name = 'FETCH'
436 typ, dat = self._simple_command(name, message_set, message_parts)
437 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000438
439
Piers Lauder15e5d532001-07-20 10:52:06 +0000440 def getacl(self, mailbox):
441 """Get the ACLs for a mailbox.
442
443 (typ, [data]) = <instance>.getacl(mailbox)
444 """
445 typ, dat = self._simple_command('GETACL', mailbox)
446 return self._untagged_response(typ, dat, 'ACL')
447
448
Piers Lauderd80ef022005-06-01 23:50:52 +0000449 def getannotation(self, mailbox, entry, attribute):
450 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
451 Retrieve ANNOTATIONs."""
452
453 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
454 return self._untagged_response(typ, dat, 'ANNOTATION')
455
456
Piers Lauder3fca2912002-06-17 07:07:20 +0000457 def getquota(self, root):
458 """Get the quota root's resource usage and limits.
459
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000460 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000461
462 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000463 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000464 typ, dat = self._simple_command('GETQUOTA', root)
465 return self._untagged_response(typ, dat, 'QUOTA')
466
467
468 def getquotaroot(self, mailbox):
469 """Get the list of quota roots for the named mailbox.
470
471 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000472 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000473 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000474 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
475 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000476 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000477
478
Tim Peters07e99cb2001-01-14 23:47:14 +0000479 def list(self, directory='""', pattern='*'):
480 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000481
Tim Peters07e99cb2001-01-14 23:47:14 +0000482 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000483
Tim Peters07e99cb2001-01-14 23:47:14 +0000484 'data' is list of LIST responses.
485 """
486 name = 'LIST'
487 typ, dat = self._simple_command(name, directory, pattern)
488 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000489
490
Tim Peters07e99cb2001-01-14 23:47:14 +0000491 def login(self, user, password):
492 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000493
Tim Peters07e99cb2001-01-14 23:47:14 +0000494 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 NB: 'password' will be quoted.
497 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000498 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
499 if typ != 'OK':
500 raise self.error(dat[-1])
501 self.state = 'AUTH'
502 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000503
504
Piers Laudere0273de2002-11-22 05:53:04 +0000505 def login_cram_md5(self, user, password):
506 """ Force use of CRAM-MD5 authentication.
507
508 (typ, [data]) = <instance>.login_cram_md5(user, password)
509 """
510 self.user, self.password = user, password
511 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
512
513
514 def _CRAM_MD5_AUTH(self, challenge):
515 """ Authobject to use with CRAM-MD5 authentication. """
516 import hmac
517 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
518
519
Tim Peters07e99cb2001-01-14 23:47:14 +0000520 def logout(self):
521 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000522
Tim Peters07e99cb2001-01-14 23:47:14 +0000523 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000524
Tim Peters07e99cb2001-01-14 23:47:14 +0000525 Returns server 'BYE' response.
526 """
527 self.state = 'LOGOUT'
528 try: typ, dat = self._simple_command('LOGOUT')
529 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000530 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000531 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000532 return 'BYE', self.untagged_responses['BYE']
533 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000534
535
Tim Peters07e99cb2001-01-14 23:47:14 +0000536 def lsub(self, directory='""', pattern='*'):
537 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000538
Tim Peters07e99cb2001-01-14 23:47:14 +0000539 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000540
Tim Peters07e99cb2001-01-14 23:47:14 +0000541 'data' are tuples of message part envelope and data.
542 """
543 name = 'LSUB'
544 typ, dat = self._simple_command(name, directory, pattern)
545 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000546
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000547 def myrights(self, mailbox):
548 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
549
550 (typ, [data]) = <instance>.myrights(mailbox)
551 """
552 typ,dat = self._simple_command('MYRIGHTS', mailbox)
553 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000554
Piers Lauder15e5d532001-07-20 10:52:06 +0000555 def namespace(self):
556 """ Returns IMAP namespaces ala rfc2342
557
558 (typ, [data, ...]) = <instance>.namespace()
559 """
560 name = 'NAMESPACE'
561 typ, dat = self._simple_command(name)
562 return self._untagged_response(typ, dat, name)
563
564
Tim Peters07e99cb2001-01-14 23:47:14 +0000565 def noop(self):
566 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000567
Piers Laudere0273de2002-11-22 05:53:04 +0000568 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000569 """
570 if __debug__:
571 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000572 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000573 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000574
575
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 def partial(self, message_num, message_part, start, length):
577 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000578
Tim Peters07e99cb2001-01-14 23:47:14 +0000579 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000580
Tim Peters07e99cb2001-01-14 23:47:14 +0000581 'data' is tuple of message part envelope and data.
582 """
583 name = 'PARTIAL'
584 typ, dat = self._simple_command(name, message_num, message_part, start, length)
585 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000586
587
Piers Laudere0273de2002-11-22 05:53:04 +0000588 def proxyauth(self, user):
589 """Assume authentication as "user".
590
591 Allows an authorised administrator to proxy into any user's
592 mailbox.
593
594 (typ, [data]) = <instance>.proxyauth(user)
595 """
596
597 name = 'PROXYAUTH'
598 return self._simple_command('PROXYAUTH', user)
599
600
Tim Peters07e99cb2001-01-14 23:47:14 +0000601 def rename(self, oldmailbox, newmailbox):
602 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000603
Piers Laudere0273de2002-11-22 05:53:04 +0000604 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000605 """
606 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000607
608
Tim Peters07e99cb2001-01-14 23:47:14 +0000609 def search(self, charset, *criteria):
610 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000611
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000612 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000613
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 'data' is space separated list of matching message numbers.
615 """
616 name = 'SEARCH'
617 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000618 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000619 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000620 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000621 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000622
623
Piers Lauder14f39402005-08-31 10:46:29 +0000624 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000625 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000626
Tim Peters07e99cb2001-01-14 23:47:14 +0000627 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000628
Piers Lauder14f39402005-08-31 10:46:29 +0000629 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000630
Tim Peters07e99cb2001-01-14 23:47:14 +0000631 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000632
633 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
634 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000635 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000636 self.untagged_responses = {} # Flush old responses.
637 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000638 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000639 name = 'EXAMINE'
640 else:
641 name = 'SELECT'
642 typ, dat = self._simple_command(name, mailbox)
643 if typ != 'OK':
644 self.state = 'AUTH' # Might have been 'SELECTED'
645 return typ, dat
646 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000647 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000648 and not readonly:
649 if __debug__:
650 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000651 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000652 raise self.readonly('%s is not writable' % mailbox)
653 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000654
655
Piers Lauder15e5d532001-07-20 10:52:06 +0000656 def setacl(self, mailbox, who, what):
657 """Set a mailbox acl.
658
Piers Lauderf167dc32004-03-25 00:12:21 +0000659 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000660 """
661 return self._simple_command('SETACL', mailbox, who, what)
662
663
Piers Lauderd80ef022005-06-01 23:50:52 +0000664 def setannotation(self, *args):
665 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
666 Set ANNOTATIONs."""
667
668 typ, dat = self._simple_command('SETANNOTATION', *args)
669 return self._untagged_response(typ, dat, 'ANNOTATION')
670
671
Piers Lauder3fca2912002-06-17 07:07:20 +0000672 def setquota(self, root, limits):
673 """Set the quota root's resource limits.
674
675 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000676 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000677 typ, dat = self._simple_command('SETQUOTA', root, limits)
678 return self._untagged_response(typ, dat, 'QUOTA')
679
680
Piers Lauder15e5d532001-07-20 10:52:06 +0000681 def sort(self, sort_criteria, charset, *search_criteria):
682 """IMAP4rev1 extension SORT command.
683
684 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
685 """
686 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000687 #if not name in self.capabilities: # Let the server decide!
688 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000689 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000690 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000691 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000692 return self._untagged_response(typ, dat, name)
693
694
Tim Peters07e99cb2001-01-14 23:47:14 +0000695 def status(self, mailbox, names):
696 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000697
Tim Peters07e99cb2001-01-14 23:47:14 +0000698 (typ, [data]) = <instance>.status(mailbox, names)
699 """
700 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000701 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000702 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000703 typ, dat = self._simple_command(name, mailbox, names)
704 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000705
706
Tim Peters07e99cb2001-01-14 23:47:14 +0000707 def store(self, message_set, command, flags):
708 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000709
Tim Peters07e99cb2001-01-14 23:47:14 +0000710 (typ, [data]) = <instance>.store(message_set, command, flags)
711 """
712 if (flags[0],flags[-1]) != ('(',')'):
713 flags = '(%s)' % flags # Avoid quoting the flags
714 typ, dat = self._simple_command('STORE', message_set, command, flags)
715 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000716
717
Tim Peters07e99cb2001-01-14 23:47:14 +0000718 def subscribe(self, mailbox):
719 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000720
Tim Peters07e99cb2001-01-14 23:47:14 +0000721 (typ, [data]) = <instance>.subscribe(mailbox)
722 """
723 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000724
725
Martin v. Löwisd8921372003-11-10 06:44:44 +0000726 def thread(self, threading_algorithm, charset, *search_criteria):
727 """IMAPrev1 extension THREAD command.
728
Mark Dickinsondb69f012009-12-24 16:06:58 +0000729 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000730 """
731 name = 'THREAD'
732 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
733 return self._untagged_response(typ, dat, name)
734
735
Tim Peters07e99cb2001-01-14 23:47:14 +0000736 def uid(self, command, *args):
737 """Execute "command arg ..." with messages identified by UID,
738 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000739
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000741
Tim Peters07e99cb2001-01-14 23:47:14 +0000742 Returns response appropriate to 'command'.
743 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000744 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000745 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000746 raise self.error("Unknown IMAP4 UID command: %s" % command)
747 if self.state not in Commands[command]:
Georg Brandl6c104f62007-03-13 18:24:40 +0000748 raise self.error("command %s illegal in state %s, "
749 "only allowed in states %s" %
750 (command, self.state,
751 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000752 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000753 typ, dat = self._simple_command(name, command, *args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000754 if command in ('SEARCH', 'SORT'):
755 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000756 else:
757 name = 'FETCH'
758 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000759
760
Tim Peters07e99cb2001-01-14 23:47:14 +0000761 def unsubscribe(self, mailbox):
762 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000763
Tim Peters07e99cb2001-01-14 23:47:14 +0000764 (typ, [data]) = <instance>.unsubscribe(mailbox)
765 """
766 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000767
768
Tim Peters07e99cb2001-01-14 23:47:14 +0000769 def xatom(self, name, *args):
770 """Allow simple extension commands
771 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000772
Piers Lauder15e5d532001-07-20 10:52:06 +0000773 Assumes command is legal in current state.
774
Tim Peters07e99cb2001-01-14 23:47:14 +0000775 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000776
777 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000778 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000779 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000780 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000781 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000782 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000783 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000784 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000785
786
787
Tim Peters07e99cb2001-01-14 23:47:14 +0000788 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000789
790
Tim Peters07e99cb2001-01-14 23:47:14 +0000791 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000792
Tim Peters07e99cb2001-01-14 23:47:14 +0000793 if dat is None: dat = ''
794 ur = self.untagged_responses
795 if __debug__:
796 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000797 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000798 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000799 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 ur[typ].append(dat)
801 else:
802 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000803
804
Tim Peters07e99cb2001-01-14 23:47:14 +0000805 def _check_bye(self):
806 bye = self.untagged_responses.get('BYE')
807 if bye:
808 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000809
810
Tim Peters07e99cb2001-01-14 23:47:14 +0000811 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 if self.state not in Commands[name]:
814 self.literal = None
Georg Brandl6c104f62007-03-13 18:24:40 +0000815 raise self.error("command %s illegal in state %s, "
816 "only allowed in states %s" %
817 (name, self.state,
818 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000819
Tim Peters07e99cb2001-01-14 23:47:14 +0000820 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000821 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000823
Raymond Hettinger54f02222002-06-01 14:18:47 +0000824 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 and not self.is_readonly:
826 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000827
Tim Peters07e99cb2001-01-14 23:47:14 +0000828 tag = self._new_tag()
829 data = '%s %s' % (tag, name)
830 for arg in args:
831 if arg is None: continue
832 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000833
Tim Peters07e99cb2001-01-14 23:47:14 +0000834 literal = self.literal
835 if literal is not None:
836 self.literal = None
837 if type(literal) is type(self._command):
838 literator = literal
839 else:
840 literator = None
841 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000842
Tim Peters07e99cb2001-01-14 23:47:14 +0000843 if __debug__:
844 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000845 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000847 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000848
Tim Peters07e99cb2001-01-14 23:47:14 +0000849 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000850 self.send('%s%s' % (data, CRLF))
851 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000852 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000853
Tim Peters07e99cb2001-01-14 23:47:14 +0000854 if literal is None:
855 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000856
Tim Peters07e99cb2001-01-14 23:47:14 +0000857 while 1:
858 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000859
Tim Peters07e99cb2001-01-14 23:47:14 +0000860 while self._get_response():
861 if self.tagged_commands[tag]: # BAD/NO?
862 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000863
Tim Peters07e99cb2001-01-14 23:47:14 +0000864 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000865
Tim Peters07e99cb2001-01-14 23:47:14 +0000866 if literator:
867 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000868
Tim Peters07e99cb2001-01-14 23:47:14 +0000869 if __debug__:
870 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000871 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000872
Tim Peters07e99cb2001-01-14 23:47:14 +0000873 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000874 self.send(literal)
875 self.send(CRLF)
876 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000877 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000878
Tim Peters07e99cb2001-01-14 23:47:14 +0000879 if not literator:
880 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000881
Tim Peters07e99cb2001-01-14 23:47:14 +0000882 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000883
884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 def _command_complete(self, name, tag):
886 self._check_bye()
887 try:
888 typ, data = self._get_tagged_response(tag)
889 except self.abort, val:
890 raise self.abort('command: %s => %s' % (name, val))
891 except self.error, val:
892 raise self.error('command: %s => %s' % (name, val))
893 self._check_bye()
894 if typ == 'BAD':
895 raise self.error('%s command error: %s %s' % (name, typ, data))
896 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000897
898
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 # Read response and store.
902 #
903 # Returns None for continuation responses,
904 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000905
Tim Peters07e99cb2001-01-14 23:47:14 +0000906 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 if self._match(self.tagre, resp):
911 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000912 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000913 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000914
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 typ = self.mo.group('type')
916 dat = self.mo.group('data')
917 self.tagged_commands[tag] = (typ, [dat])
918 else:
919 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000920
Tim Peters07e99cb2001-01-14 23:47:14 +0000921 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 if not self._match(Untagged_response, resp):
924 if self._match(Untagged_status, resp):
925 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 if self.mo is None:
928 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000929
Tim Peters07e99cb2001-01-14 23:47:14 +0000930 if self._match(Continuation, resp):
931 self.continuation_response = self.mo.group('data')
932 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000935
Tim Peters07e99cb2001-01-14 23:47:14 +0000936 typ = self.mo.group('type')
937 dat = self.mo.group('data')
938 if dat is None: dat = '' # Null untagged response
939 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000940
Tim Peters07e99cb2001-01-14 23:47:14 +0000941 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000946
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000947 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000948 if __debug__:
949 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000950 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000951 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000952
Tim Peters07e99cb2001-01-14 23:47:14 +0000953 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000954
Tim Peters07e99cb2001-01-14 23:47:14 +0000955 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000960
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
966 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000967
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 if __debug__:
969 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000970 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000971
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000973
974
Tim Peters07e99cb2001-01-14 23:47:14 +0000975 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000976
Tim Peters07e99cb2001-01-14 23:47:14 +0000977 while 1:
978 result = self.tagged_commands[tag]
979 if result is not None:
980 del self.tagged_commands[tag]
981 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000982
Tim Peters07e99cb2001-01-14 23:47:14 +0000983 # Some have reported "unexpected response" exceptions.
984 # Note that ignoring them here causes loops.
985 # Instead, send me details of the unexpected response and
986 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 try:
989 self._get_response()
990 except self.abort, val:
991 if __debug__:
992 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000993 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000994 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000995
996
Tim Peters07e99cb2001-01-14 23:47:14 +0000997 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000998
Piers Lauder15e5d532001-07-20 10:52:06 +0000999 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001000 if not line:
1001 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 # Protocol mandates all lines terminated by CRLF
R. David Murray93321f32009-12-09 15:15:31 +00001004 if not line.endswith('\r\n'):
1005 raise self.abort('socket error: unterminated line')
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
Bill Janssen426ea0a2007-08-29 22:35:05 +00001115try:
1116 import ssl
1117except ImportError:
1118 pass
1119else:
1120 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001121
Bill Janssen426ea0a2007-08-29 22:35:05 +00001122 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001123
Bill Janssen426ea0a2007-08-29 22:35:05 +00001124 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001125
Bill Janssen426ea0a2007-08-29 22:35:05 +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
Bill Janssen426ea0a2007-08-29 22:35:05 +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
Bill Janssen426ea0a2007-08-29 22:35:05 +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
Bill Janssen426ea0a2007-08-29 22:35:05 +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
Antoine Pitrou52035a02009-05-15 11:50:29 +00001149 self.sock = socket.create_connection((host, port))
Bill Janssen98d19da2007-09-10 21:51:02 +00001150 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001151
1152
Bill Janssen426ea0a2007-08-29 22:35:05 +00001153 def read(self, size):
1154 """Read 'size' bytes from remote."""
1155 # sslobj.read() sometimes returns < size bytes
1156 chunks = []
1157 read = 0
1158 while read < size:
Andrew M. Kuchling1219a802008-02-23 19:02:33 +00001159 data = self.sslobj.read(min(size-read, 16384))
Bill Janssen426ea0a2007-08-29 22:35:05 +00001160 read += len(data)
1161 chunks.append(data)
1162
1163 return ''.join(chunks)
Piers Laudera4f83132002-03-08 01:53:24 +00001164
1165
Bill Janssen426ea0a2007-08-29 22:35:05 +00001166 def readline(self):
1167 """Read line from remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001168 line = []
1169 while 1:
1170 char = self.sslobj.read(1)
1171 line.append(char)
R. David Murray93321f32009-12-09 15:15:31 +00001172 if char in ("\n", ""): return ''.join(line)
Piers Laudera4f83132002-03-08 01:53:24 +00001173
1174
Bill Janssen426ea0a2007-08-29 22:35:05 +00001175 def send(self, data):
1176 """Send data to remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001177 bytes = len(data)
1178 while bytes > 0:
1179 sent = self.sslobj.write(data)
1180 if sent == bytes:
1181 break # avoid copy
1182 data = data[sent:]
1183 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001184
1185
Bill Janssen426ea0a2007-08-29 22:35:05 +00001186 def shutdown(self):
1187 """Close I/O established in "open"."""
1188 self.sock.close()
Piers Laudera4f83132002-03-08 01:53:24 +00001189
Bill Janssen426ea0a2007-08-29 22:35:05 +00001190
1191 def socket(self):
1192 """Return socket instance used to connect to IMAP4 server.
1193
1194 socket = <instance>.socket()
1195 """
1196 return self.sock
1197
1198
1199 def ssl(self):
1200 """Return SSLObject instance used to communicate with the IMAP4 server.
1201
Bill Janssen98d19da2007-09-10 21:51:02 +00001202 ssl = ssl.wrap_socket(<instance>.socket)
Bill Janssen426ea0a2007-08-29 22:35:05 +00001203 """
1204 return self.sslobj
Piers Laudera4f83132002-03-08 01:53:24 +00001205
Thomas Woutersa6900e82007-08-30 21:54:39 +00001206 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001207
1208
Piers Laudere0273de2002-11-22 05:53:04 +00001209class IMAP4_stream(IMAP4):
1210
1211 """IMAP4 client class over a stream
1212
1213 Instantiate with: IMAP4_stream(command)
1214
Georg Brandl36f42142010-01-02 12:35:01 +00001215 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001216
1217 for more documentation see the docstring of the parent class IMAP4.
1218 """
1219
1220
1221 def __init__(self, command):
1222 self.command = command
1223 IMAP4.__init__(self)
1224
1225
1226 def open(self, host = None, port = None):
1227 """Setup a stream connection.
1228 This connection will be used by the routines:
1229 read, readline, send, shutdown.
1230 """
1231 self.host = None # For compatibility with parent class
1232 self.port = None
1233 self.sock = None
1234 self.file = None
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001235 self.process = subprocess.Popen(self.command,
1236 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1237 shell=True, close_fds=True)
1238 self.writefile = self.process.stdin
1239 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001240
1241
1242 def read(self, size):
1243 """Read 'size' bytes from remote."""
1244 return self.readfile.read(size)
1245
1246
1247 def readline(self):
1248 """Read line from remote."""
1249 return self.readfile.readline()
1250
1251
1252 def send(self, data):
1253 """Send data to remote."""
1254 self.writefile.write(data)
1255 self.writefile.flush()
1256
1257
1258 def shutdown(self):
1259 """Close I/O established in "open"."""
1260 self.readfile.close()
1261 self.writefile.close()
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001262 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001263
1264
1265
Guido van Rossumeda960a1998-06-18 14:24:28 +00001266class _Authenticator:
1267
Tim Peters07e99cb2001-01-14 23:47:14 +00001268 """Private class to provide en/decoding
1269 for base64-based authentication conversation.
1270 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001271
Tim Peters07e99cb2001-01-14 23:47:14 +00001272 def __init__(self, mechinst):
1273 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001274
Tim Peters07e99cb2001-01-14 23:47:14 +00001275 def process(self, data):
1276 ret = self.mech(self.decode(data))
1277 if ret is None:
1278 return '*' # Abort conversation
1279 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001280
Tim Peters07e99cb2001-01-14 23:47:14 +00001281 def encode(self, inp):
1282 #
1283 # Invoke binascii.b2a_base64 iteratively with
1284 # short even length buffers, strip the trailing
1285 # line feed from the result and append. "Even"
1286 # means a number that factors to both 6 and 8,
1287 # so when it gets to the end of the 8-bit input
1288 # there's no partial 6-bit output.
1289 #
1290 oup = ''
1291 while inp:
1292 if len(inp) > 48:
1293 t = inp[:48]
1294 inp = inp[48:]
1295 else:
1296 t = inp
1297 inp = ''
1298 e = binascii.b2a_base64(t)
1299 if e:
1300 oup = oup + e[:-1]
1301 return oup
1302
1303 def decode(self, inp):
1304 if not inp:
1305 return ''
1306 return binascii.a2b_base64(inp)
1307
Guido van Rossumeda960a1998-06-18 14:24:28 +00001308
1309
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001310Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001311 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001312
1313def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001314 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001315
Tim Peters07e99cb2001-01-14 23:47:14 +00001316 Returns Python time module tuple.
1317 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001318
Tim Peters07e99cb2001-01-14 23:47:14 +00001319 mo = InternalDate.match(resp)
1320 if not mo:
1321 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 mon = Mon2num[mo.group('mon')]
1324 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001325
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001326 day = int(mo.group('day'))
1327 year = int(mo.group('year'))
1328 hour = int(mo.group('hour'))
1329 min = int(mo.group('min'))
1330 sec = int(mo.group('sec'))
1331 zoneh = int(mo.group('zoneh'))
1332 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001333
Tim Peters07e99cb2001-01-14 23:47:14 +00001334 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001335
Tim Peters07e99cb2001-01-14 23:47:14 +00001336 zone = (zoneh*60 + zonem)*60
1337 if zonen == '-':
1338 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001339
Tim Peters07e99cb2001-01-14 23:47:14 +00001340 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001341
Tim Peters07e99cb2001-01-14 23:47:14 +00001342 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 # Following is necessary because the time module has no 'mkgmtime'.
1345 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001346
Tim Peters07e99cb2001-01-14 23:47:14 +00001347 lt = time.localtime(utc)
1348 if time.daylight and lt[-1]:
1349 zone = zone + time.altzone
1350 else:
1351 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001352
Tim Peters07e99cb2001-01-14 23:47:14 +00001353 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001354
1355
1356
1357def Int2AP(num):
1358
Tim Peters07e99cb2001-01-14 23:47:14 +00001359 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001360
Tim Peters07e99cb2001-01-14 23:47:14 +00001361 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1362 num = int(abs(num))
1363 while num:
1364 num, mod = divmod(num, 16)
1365 val = AP[mod] + val
1366 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001367
1368
1369
1370def ParseFlags(resp):
1371
Tim Peters07e99cb2001-01-14 23:47:14 +00001372 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001373
Tim Peters07e99cb2001-01-14 23:47:14 +00001374 mo = Flags.match(resp)
1375 if not mo:
1376 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001377
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001378 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001379
1380
1381def Time2Internaldate(date_time):
1382
Tim Peters07e99cb2001-01-14 23:47:14 +00001383 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001384
Tim Peters07e99cb2001-01-14 23:47:14 +00001385 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1386 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001387
Fred Drakedb519202002-01-05 17:17:09 +00001388 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001389 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001390 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001391 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001392 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001394 else:
1395 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001396
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1398 if dt[0] == '0':
1399 dt = ' ' + dt[1:]
1400 if time.daylight and tt[-1]:
1401 zone = -time.altzone
1402 else:
1403 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001404 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001405
1406
1407
Guido van Rossum8c062211999-12-13 23:27:45 +00001408if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001409
Piers Laudere0273de2002-11-22 05:53:04 +00001410 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1411 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1412 # to test the IMAP4_stream class
1413
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001414 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001415
Tim Peters07e99cb2001-01-14 23:47:14 +00001416 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001417 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001418 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001419 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001420
Piers Laudere0273de2002-11-22 05:53:04 +00001421 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001422 for opt,val in optlist:
1423 if opt == '-d':
1424 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001425 elif opt == '-s':
1426 stream_command = val
1427 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001428
Tim Peters07e99cb2001-01-14 23:47:14 +00001429 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001430
Tim Peters07e99cb2001-01-14 23:47:14 +00001431 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001432
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001434 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001435
Piers Lauder47404ff2003-04-29 23:40:59 +00001436 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 +00001437 test_seq1 = (
1438 ('login', (USER, PASSWD)),
1439 ('create', ('/tmp/xxx 1',)),
1440 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1441 ('CREATE', ('/tmp/yyz 2',)),
1442 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1443 ('list', ('/tmp', 'yy*')),
1444 ('select', ('/tmp/yyz 2',)),
1445 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001446 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001447 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001448 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001449 ('expunge', ()),
1450 ('recent', ()),
1451 ('close', ()),
1452 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001453
Tim Peters07e99cb2001-01-14 23:47:14 +00001454 test_seq2 = (
1455 ('select', ()),
1456 ('response',('UIDVALIDITY',)),
1457 ('uid', ('SEARCH', 'ALL')),
1458 ('response', ('EXISTS',)),
1459 ('append', (None, None, None, test_mesg)),
1460 ('recent', ()),
1461 ('logout', ()),
1462 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001463
Tim Peters07e99cb2001-01-14 23:47:14 +00001464 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001465 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001466 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001467 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001468 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001469 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001470
Tim Peters07e99cb2001-01-14 23:47:14 +00001471 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001472 if stream_command:
1473 M = IMAP4_stream(stream_command)
1474 else:
1475 M = IMAP4(host)
1476 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001477 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001478 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001479 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001480
Tim Peters07e99cb2001-01-14 23:47:14 +00001481 for cmd,args in test_seq1:
1482 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001483
Tim Peters07e99cb2001-01-14 23:47:14 +00001484 for ml in run('list', ('/tmp/', 'yy%')):
1485 mo = re.match(r'.*"([^"]+)"$', ml)
1486 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001487 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001488 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001489
Tim Peters07e99cb2001-01-14 23:47:14 +00001490 for cmd,args in test_seq2:
1491 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001492
Tim Peters07e99cb2001-01-14 23:47:14 +00001493 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1494 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001495
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001496 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001497 if not uid: continue
1498 run('uid', ('FETCH', '%s' % uid[-1],
1499 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001500
Tim Peters07e99cb2001-01-14 23:47:14 +00001501 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001502
Tim Peters07e99cb2001-01-14 23:47:14 +00001503 except:
1504 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001505
Tim Peters07e99cb2001-01-14 23:47:14 +00001506 if not Debug:
1507 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001508If you would like to see debugging output,
1509try: %s -d5
1510''' % sys.argv[0]
1511
Tim Peters07e99cb2001-01-14 23:47:14 +00001512 raise