blob: 357ba83891d8c493041368e8d9bd66e159a7cb86 [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()
Antoine Pitrou7eaa4482010-11-09 23:15:18 +0000251 self.sock.shutdown(socket.SHUT_RDWR)
Piers Lauder15e5d532001-07-20 10:52:06 +0000252 self.sock.close()
253
254
255 def socket(self):
256 """Return socket instance used to connect to IMAP4 server.
257
258 socket = <instance>.socket()
259 """
260 return self.sock
261
262
263
264 # Utility methods
265
266
Tim Peters07e99cb2001-01-14 23:47:14 +0000267 def recent(self):
268 """Return most recent 'RECENT' responses if any exist,
269 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000270
Tim Peters07e99cb2001-01-14 23:47:14 +0000271 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000272
Tim Peters07e99cb2001-01-14 23:47:14 +0000273 'data' is None if no new messages,
274 else list of RECENT responses, most recent last.
275 """
276 name = 'RECENT'
277 typ, dat = self._untagged_response('OK', [None], name)
278 if dat[-1]:
279 return typ, dat
280 typ, dat = self.noop() # Prod server for response
281 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000282
283
Tim Peters07e99cb2001-01-14 23:47:14 +0000284 def response(self, code):
285 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000286
Tim Peters07e99cb2001-01-14 23:47:14 +0000287 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000288
Tim Peters07e99cb2001-01-14 23:47:14 +0000289 (code, [data]) = <instance>.response(code)
290 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000291 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000292
293
Guido van Rossum26367a01998-09-28 15:34:46 +0000294
Tim Peters07e99cb2001-01-14 23:47:14 +0000295 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000296
297
Tim Peters07e99cb2001-01-14 23:47:14 +0000298 def append(self, mailbox, flags, date_time, message):
299 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000300
Tim Peters07e99cb2001-01-14 23:47:14 +0000301 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000302
Tim Peters07e99cb2001-01-14 23:47:14 +0000303 All args except `message' can be None.
304 """
305 name = 'APPEND'
306 if not mailbox:
307 mailbox = 'INBOX'
308 if flags:
309 if (flags[0],flags[-1]) != ('(',')'):
310 flags = '(%s)' % flags
311 else:
312 flags = None
313 if date_time:
314 date_time = Time2Internaldate(date_time)
315 else:
316 date_time = None
Piers Lauder47404ff2003-04-29 23:40:59 +0000317 self.literal = MapCRLF.sub(CRLF, message)
Tim Peters07e99cb2001-01-14 23:47:14 +0000318 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000319
320
Tim Peters07e99cb2001-01-14 23:47:14 +0000321 def authenticate(self, mechanism, authobject):
322 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000323
Tim Peters07e99cb2001-01-14 23:47:14 +0000324 'mechanism' specifies which authentication mechanism is to
325 be used - it must appear in <instance>.capabilities in the
326 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000327
Tim Peters07e99cb2001-01-14 23:47:14 +0000328 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000329
Tim Peters07e99cb2001-01-14 23:47:14 +0000330 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000331
Tim Peters07e99cb2001-01-14 23:47:14 +0000332 It will be called to process server continuation responses.
333 It should return data that will be encoded and sent to server.
334 It should return None if the client abort response '*' should
335 be sent instead.
336 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000337 mech = mechanism.upper()
Neal Norwitzb2071702003-06-29 04:21:43 +0000338 # XXX: shouldn't this code be removed, not commented out?
339 #cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000340 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000341 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000342 self.literal = _Authenticator(authobject).process
343 typ, dat = self._simple_command('AUTHENTICATE', mech)
344 if typ != 'OK':
345 raise self.error(dat[-1])
346 self.state = 'AUTH'
347 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000348
349
Piers Lauderd80ef022005-06-01 23:50:52 +0000350 def capability(self):
351 """(typ, [data]) = <instance>.capability()
352 Fetch capabilities list from server."""
353
354 name = 'CAPABILITY'
355 typ, dat = self._simple_command(name)
356 return self._untagged_response(typ, dat, name)
357
358
Tim Peters07e99cb2001-01-14 23:47:14 +0000359 def check(self):
360 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000361
Tim Peters07e99cb2001-01-14 23:47:14 +0000362 (typ, [data]) = <instance>.check()
363 """
364 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000365
366
Tim Peters07e99cb2001-01-14 23:47:14 +0000367 def close(self):
368 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000369
Tim Peters07e99cb2001-01-14 23:47:14 +0000370 Deleted messages are removed from writable mailbox.
371 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000372
Tim Peters07e99cb2001-01-14 23:47:14 +0000373 (typ, [data]) = <instance>.close()
374 """
375 try:
376 typ, dat = self._simple_command('CLOSE')
377 finally:
378 self.state = 'AUTH'
379 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000380
381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 def copy(self, message_set, new_mailbox):
383 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
386 """
387 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000388
389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 def create(self, mailbox):
391 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 (typ, [data]) = <instance>.create(mailbox)
394 """
395 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
397
Tim Peters07e99cb2001-01-14 23:47:14 +0000398 def delete(self, mailbox):
399 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000400
Tim Peters07e99cb2001-01-14 23:47:14 +0000401 (typ, [data]) = <instance>.delete(mailbox)
402 """
403 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000404
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000405 def deleteacl(self, mailbox, who):
406 """Delete the ACLs (remove any rights) set for who on mailbox.
407
408 (typ, [data]) = <instance>.deleteacl(mailbox, who)
409 """
410 return self._simple_command('DELETEACL', mailbox, who)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 def expunge(self):
413 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000414
Tim Peters07e99cb2001-01-14 23:47:14 +0000415 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000416
Tim Peters07e99cb2001-01-14 23:47:14 +0000417 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000418
Tim Peters07e99cb2001-01-14 23:47:14 +0000419 'data' is list of 'EXPUNGE'd message numbers in order received.
420 """
421 name = 'EXPUNGE'
422 typ, dat = self._simple_command(name)
423 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000424
425
Tim Peters07e99cb2001-01-14 23:47:14 +0000426 def fetch(self, message_set, message_parts):
427 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000428
Tim Peters07e99cb2001-01-14 23:47:14 +0000429 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000430
Tim Peters07e99cb2001-01-14 23:47:14 +0000431 'message_parts' should be a string of selected parts
432 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000433
Tim Peters07e99cb2001-01-14 23:47:14 +0000434 'data' are tuples of message part envelope and data.
435 """
436 name = 'FETCH'
437 typ, dat = self._simple_command(name, message_set, message_parts)
438 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000439
440
Piers Lauder15e5d532001-07-20 10:52:06 +0000441 def getacl(self, mailbox):
442 """Get the ACLs for a mailbox.
443
444 (typ, [data]) = <instance>.getacl(mailbox)
445 """
446 typ, dat = self._simple_command('GETACL', mailbox)
447 return self._untagged_response(typ, dat, 'ACL')
448
449
Piers Lauderd80ef022005-06-01 23:50:52 +0000450 def getannotation(self, mailbox, entry, attribute):
451 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
452 Retrieve ANNOTATIONs."""
453
454 typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute)
455 return self._untagged_response(typ, dat, 'ANNOTATION')
456
457
Piers Lauder3fca2912002-06-17 07:07:20 +0000458 def getquota(self, root):
459 """Get the quota root's resource usage and limits.
460
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000461 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000462
463 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000464 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000465 typ, dat = self._simple_command('GETQUOTA', root)
466 return self._untagged_response(typ, dat, 'QUOTA')
467
468
469 def getquotaroot(self, mailbox):
470 """Get the list of quota roots for the named mailbox.
471
472 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000473 """
Piers Lauder6a4e6352004-08-10 01:24:54 +0000474 typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000475 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
476 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000477 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000478
479
Tim Peters07e99cb2001-01-14 23:47:14 +0000480 def list(self, directory='""', pattern='*'):
481 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000482
Tim Peters07e99cb2001-01-14 23:47:14 +0000483 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000484
Tim Peters07e99cb2001-01-14 23:47:14 +0000485 'data' is list of LIST responses.
486 """
487 name = 'LIST'
488 typ, dat = self._simple_command(name, directory, pattern)
489 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000490
491
Tim Peters07e99cb2001-01-14 23:47:14 +0000492 def login(self, user, password):
493 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000494
Tim Peters07e99cb2001-01-14 23:47:14 +0000495 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000496
Tim Peters07e99cb2001-01-14 23:47:14 +0000497 NB: 'password' will be quoted.
498 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000499 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
500 if typ != 'OK':
501 raise self.error(dat[-1])
502 self.state = 'AUTH'
503 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000504
505
Piers Laudere0273de2002-11-22 05:53:04 +0000506 def login_cram_md5(self, user, password):
507 """ Force use of CRAM-MD5 authentication.
508
509 (typ, [data]) = <instance>.login_cram_md5(user, password)
510 """
511 self.user, self.password = user, password
512 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
513
514
515 def _CRAM_MD5_AUTH(self, challenge):
516 """ Authobject to use with CRAM-MD5 authentication. """
517 import hmac
518 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
519
520
Tim Peters07e99cb2001-01-14 23:47:14 +0000521 def logout(self):
522 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000523
Tim Peters07e99cb2001-01-14 23:47:14 +0000524 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000525
Tim Peters07e99cb2001-01-14 23:47:14 +0000526 Returns server 'BYE' response.
527 """
528 self.state = 'LOGOUT'
529 try: typ, dat = self._simple_command('LOGOUT')
530 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000531 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000532 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000533 return 'BYE', self.untagged_responses['BYE']
534 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000535
536
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 def lsub(self, directory='""', pattern='*'):
538 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000539
Tim Peters07e99cb2001-01-14 23:47:14 +0000540 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000541
Tim Peters07e99cb2001-01-14 23:47:14 +0000542 'data' are tuples of message part envelope and data.
543 """
544 name = 'LSUB'
545 typ, dat = self._simple_command(name, directory, pattern)
546 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000547
Martin v. Löwis7b9190b2004-07-27 05:07:19 +0000548 def myrights(self, mailbox):
549 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
550
551 (typ, [data]) = <instance>.myrights(mailbox)
552 """
553 typ,dat = self._simple_command('MYRIGHTS', mailbox)
554 return self._untagged_response(typ, dat, 'MYRIGHTS')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000555
Piers Lauder15e5d532001-07-20 10:52:06 +0000556 def namespace(self):
557 """ Returns IMAP namespaces ala rfc2342
558
559 (typ, [data, ...]) = <instance>.namespace()
560 """
561 name = 'NAMESPACE'
562 typ, dat = self._simple_command(name)
563 return self._untagged_response(typ, dat, name)
564
565
Tim Peters07e99cb2001-01-14 23:47:14 +0000566 def noop(self):
567 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000568
Piers Laudere0273de2002-11-22 05:53:04 +0000569 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000570 """
571 if __debug__:
572 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000573 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000574 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000575
576
Tim Peters07e99cb2001-01-14 23:47:14 +0000577 def partial(self, message_num, message_part, start, length):
578 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000579
Tim Peters07e99cb2001-01-14 23:47:14 +0000580 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000581
Tim Peters07e99cb2001-01-14 23:47:14 +0000582 'data' is tuple of message part envelope and data.
583 """
584 name = 'PARTIAL'
585 typ, dat = self._simple_command(name, message_num, message_part, start, length)
586 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000587
588
Piers Laudere0273de2002-11-22 05:53:04 +0000589 def proxyauth(self, user):
590 """Assume authentication as "user".
591
592 Allows an authorised administrator to proxy into any user's
593 mailbox.
594
595 (typ, [data]) = <instance>.proxyauth(user)
596 """
597
598 name = 'PROXYAUTH'
599 return self._simple_command('PROXYAUTH', user)
600
601
Tim Peters07e99cb2001-01-14 23:47:14 +0000602 def rename(self, oldmailbox, newmailbox):
603 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000604
Piers Laudere0273de2002-11-22 05:53:04 +0000605 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000606 """
607 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000608
609
Tim Peters07e99cb2001-01-14 23:47:14 +0000610 def search(self, charset, *criteria):
611 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000612
Martin v. Löwis3ae0f7a2003-03-27 16:59:38 +0000613 (typ, [data]) = <instance>.search(charset, criterion, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000614
Tim Peters07e99cb2001-01-14 23:47:14 +0000615 'data' is space separated list of matching message numbers.
616 """
617 name = 'SEARCH'
618 if charset:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000619 typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000620 else:
Guido van Rossum68468eb2003-02-27 20:14:51 +0000621 typ, dat = self._simple_command(name, *criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000622 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000623
624
Piers Lauder14f39402005-08-31 10:46:29 +0000625 def select(self, mailbox='INBOX', readonly=False):
Tim Peters07e99cb2001-01-14 23:47:14 +0000626 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000627
Tim Peters07e99cb2001-01-14 23:47:14 +0000628 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000629
Piers Lauder14f39402005-08-31 10:46:29 +0000630 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000631
Tim Peters07e99cb2001-01-14 23:47:14 +0000632 'data' is count of messages in mailbox ('EXISTS' response).
Piers Lauder6a4e6352004-08-10 01:24:54 +0000633
634 Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
635 other responses should be obtained via <instance>.response('FLAGS') etc.
Tim Peters07e99cb2001-01-14 23:47:14 +0000636 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000637 self.untagged_responses = {} # Flush old responses.
638 self.is_readonly = readonly
Piers Lauder14f39402005-08-31 10:46:29 +0000639 if readonly:
Tim Peters07e99cb2001-01-14 23:47:14 +0000640 name = 'EXAMINE'
641 else:
642 name = 'SELECT'
643 typ, dat = self._simple_command(name, mailbox)
644 if typ != 'OK':
645 self.state = 'AUTH' # Might have been 'SELECTED'
646 return typ, dat
647 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000648 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 and not readonly:
650 if __debug__:
651 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000652 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000653 raise self.readonly('%s is not writable' % mailbox)
654 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000655
656
Piers Lauder15e5d532001-07-20 10:52:06 +0000657 def setacl(self, mailbox, who, what):
658 """Set a mailbox acl.
659
Piers Lauderf167dc32004-03-25 00:12:21 +0000660 (typ, [data]) = <instance>.setacl(mailbox, who, what)
Piers Lauder15e5d532001-07-20 10:52:06 +0000661 """
662 return self._simple_command('SETACL', mailbox, who, what)
663
664
Piers Lauderd80ef022005-06-01 23:50:52 +0000665 def setannotation(self, *args):
666 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
667 Set ANNOTATIONs."""
668
669 typ, dat = self._simple_command('SETANNOTATION', *args)
670 return self._untagged_response(typ, dat, 'ANNOTATION')
671
672
Piers Lauder3fca2912002-06-17 07:07:20 +0000673 def setquota(self, root, limits):
674 """Set the quota root's resource limits.
675
676 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000677 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000678 typ, dat = self._simple_command('SETQUOTA', root, limits)
679 return self._untagged_response(typ, dat, 'QUOTA')
680
681
Piers Lauder15e5d532001-07-20 10:52:06 +0000682 def sort(self, sort_criteria, charset, *search_criteria):
683 """IMAP4rev1 extension SORT command.
684
685 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
686 """
687 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000688 #if not name in self.capabilities: # Let the server decide!
689 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000690 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000691 sort_criteria = '(%s)' % sort_criteria
Guido van Rossum68468eb2003-02-27 20:14:51 +0000692 typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
Piers Lauder15e5d532001-07-20 10:52:06 +0000693 return self._untagged_response(typ, dat, name)
694
695
Tim Peters07e99cb2001-01-14 23:47:14 +0000696 def status(self, mailbox, names):
697 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000698
Tim Peters07e99cb2001-01-14 23:47:14 +0000699 (typ, [data]) = <instance>.status(mailbox, names)
700 """
701 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000702 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000703 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000704 typ, dat = self._simple_command(name, mailbox, names)
705 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000706
707
Tim Peters07e99cb2001-01-14 23:47:14 +0000708 def store(self, message_set, command, flags):
709 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000710
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 (typ, [data]) = <instance>.store(message_set, command, flags)
712 """
713 if (flags[0],flags[-1]) != ('(',')'):
714 flags = '(%s)' % flags # Avoid quoting the flags
715 typ, dat = self._simple_command('STORE', message_set, command, flags)
716 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000717
718
Tim Peters07e99cb2001-01-14 23:47:14 +0000719 def subscribe(self, mailbox):
720 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000721
Tim Peters07e99cb2001-01-14 23:47:14 +0000722 (typ, [data]) = <instance>.subscribe(mailbox)
723 """
724 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000725
726
Martin v. Löwisd8921372003-11-10 06:44:44 +0000727 def thread(self, threading_algorithm, charset, *search_criteria):
728 """IMAPrev1 extension THREAD command.
729
Mark Dickinsondb69f012009-12-24 16:06:58 +0000730 (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...)
Martin v. Löwisd8921372003-11-10 06:44:44 +0000731 """
732 name = 'THREAD'
733 typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
734 return self._untagged_response(typ, dat, name)
735
736
Tim Peters07e99cb2001-01-14 23:47:14 +0000737 def uid(self, command, *args):
738 """Execute "command arg ..." with messages identified by UID,
739 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000740
Tim Peters07e99cb2001-01-14 23:47:14 +0000741 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000742
Tim Peters07e99cb2001-01-14 23:47:14 +0000743 Returns response appropriate to 'command'.
744 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000745 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000746 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 raise self.error("Unknown IMAP4 UID command: %s" % command)
748 if self.state not in Commands[command]:
Georg Brandl6c104f62007-03-13 18:24:40 +0000749 raise self.error("command %s illegal in state %s, "
750 "only allowed in states %s" %
751 (command, self.state,
752 ', '.join(Commands[command])))
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 name = 'UID'
Guido van Rossum68468eb2003-02-27 20:14:51 +0000754 typ, dat = self._simple_command(name, command, *args)
Georg Brandl004c74d2010-08-01 19:06:51 +0000755 if command in ('SEARCH', 'SORT', 'THREAD'):
Piers Lauder15e5d532001-07-20 10:52:06 +0000756 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000757 else:
758 name = 'FETCH'
759 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000760
761
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 def unsubscribe(self, mailbox):
763 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000764
Tim Peters07e99cb2001-01-14 23:47:14 +0000765 (typ, [data]) = <instance>.unsubscribe(mailbox)
766 """
767 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000768
769
Tim Peters07e99cb2001-01-14 23:47:14 +0000770 def xatom(self, name, *args):
771 """Allow simple extension commands
772 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000773
Piers Lauder15e5d532001-07-20 10:52:06 +0000774 Assumes command is legal in current state.
775
Tim Peters07e99cb2001-01-14 23:47:14 +0000776 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000777
778 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000779 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000780 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000781 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000782 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000783 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000784 Commands[name] = (self.state,)
Guido van Rossum68468eb2003-02-27 20:14:51 +0000785 return self._simple_command(name, *args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000786
787
788
Tim Peters07e99cb2001-01-14 23:47:14 +0000789 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000790
791
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 if dat is None: dat = ''
795 ur = self.untagged_responses
796 if __debug__:
797 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000798 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000799 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000800 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000801 ur[typ].append(dat)
802 else:
803 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000804
805
Tim Peters07e99cb2001-01-14 23:47:14 +0000806 def _check_bye(self):
807 bye = self.untagged_responses.get('BYE')
808 if bye:
809 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000810
811
Tim Peters07e99cb2001-01-14 23:47:14 +0000812 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000813
Tim Peters07e99cb2001-01-14 23:47:14 +0000814 if self.state not in Commands[name]:
815 self.literal = None
Georg Brandl6c104f62007-03-13 18:24:40 +0000816 raise self.error("command %s illegal in state %s, "
817 "only allowed in states %s" %
818 (name, self.state,
819 ', '.join(Commands[name])))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000820
Tim Peters07e99cb2001-01-14 23:47:14 +0000821 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000822 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000823 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000824
Raymond Hettinger54f02222002-06-01 14:18:47 +0000825 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000826 and not self.is_readonly:
827 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000828
Tim Peters07e99cb2001-01-14 23:47:14 +0000829 tag = self._new_tag()
830 data = '%s %s' % (tag, name)
831 for arg in args:
832 if arg is None: continue
833 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000834
Tim Peters07e99cb2001-01-14 23:47:14 +0000835 literal = self.literal
836 if literal is not None:
837 self.literal = None
838 if type(literal) is type(self._command):
839 literator = literal
840 else:
841 literator = None
842 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000843
Tim Peters07e99cb2001-01-14 23:47:14 +0000844 if __debug__:
845 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000846 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000847 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000848 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000851 self.send('%s%s' % (data, CRLF))
852 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000853 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000854
Tim Peters07e99cb2001-01-14 23:47:14 +0000855 if literal is None:
856 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000857
Tim Peters07e99cb2001-01-14 23:47:14 +0000858 while 1:
859 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 while self._get_response():
862 if self.tagged_commands[tag]: # BAD/NO?
863 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000864
Tim Peters07e99cb2001-01-14 23:47:14 +0000865 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000866
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 if literator:
868 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 if __debug__:
871 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000872 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000873
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000875 self.send(literal)
876 self.send(CRLF)
877 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000878 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000879
Tim Peters07e99cb2001-01-14 23:47:14 +0000880 if not literator:
881 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000884
885
Tim Peters07e99cb2001-01-14 23:47:14 +0000886 def _command_complete(self, name, tag):
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000887 # BYE is expected after LOGOUT
888 if name != 'LOGOUT':
889 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000890 try:
891 typ, data = self._get_tagged_response(tag)
892 except self.abort, val:
893 raise self.abort('command: %s => %s' % (name, val))
894 except self.error, val:
895 raise self.error('command: %s => %s' % (name, val))
Antoine Pitrou1d519cd2010-11-10 00:20:18 +0000896 if name != 'LOGOUT':
897 self._check_bye()
Tim Peters07e99cb2001-01-14 23:47:14 +0000898 if typ == 'BAD':
899 raise self.error('%s command error: %s %s' % (name, typ, data))
900 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000901
902
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 # Read response and store.
906 #
907 # Returns None for continuation responses,
908 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000909
Tim Peters07e99cb2001-01-14 23:47:14 +0000910 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000911
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000913
Tim Peters07e99cb2001-01-14 23:47:14 +0000914 if self._match(self.tagre, resp):
915 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000916 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000918
Tim Peters07e99cb2001-01-14 23:47:14 +0000919 typ = self.mo.group('type')
920 dat = self.mo.group('data')
921 self.tagged_commands[tag] = (typ, [dat])
922 else:
923 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000924
Tim Peters07e99cb2001-01-14 23:47:14 +0000925 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000926
Tim Peters07e99cb2001-01-14 23:47:14 +0000927 if not self._match(Untagged_response, resp):
928 if self._match(Untagged_status, resp):
929 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000930
Tim Peters07e99cb2001-01-14 23:47:14 +0000931 if self.mo is None:
932 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000933
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 if self._match(Continuation, resp):
935 self.continuation_response = self.mo.group('data')
936 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000937
Tim Peters07e99cb2001-01-14 23:47:14 +0000938 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000939
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 typ = self.mo.group('type')
941 dat = self.mo.group('data')
942 if dat is None: dat = '' # Null untagged response
943 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000946
Tim Peters07e99cb2001-01-14 23:47:14 +0000947 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000948
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000950
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000951 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000952 if __debug__:
953 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000954 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000955 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000956
Tim Peters07e99cb2001-01-14 23:47:14 +0000957 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000960
Tim Peters07e99cb2001-01-14 23:47:14 +0000961 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000962
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
Tim Peters07e99cb2001-01-14 23:47:14 +0000965 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000966
Tim Peters07e99cb2001-01-14 23:47:14 +0000967 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000968
Tim Peters07e99cb2001-01-14 23:47:14 +0000969 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
970 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000971
Tim Peters07e99cb2001-01-14 23:47:14 +0000972 if __debug__:
973 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000974 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000975
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000977
978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000980
Tim Peters07e99cb2001-01-14 23:47:14 +0000981 while 1:
982 result = self.tagged_commands[tag]
983 if result is not None:
984 del self.tagged_commands[tag]
985 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000986
Tim Peters07e99cb2001-01-14 23:47:14 +0000987 # Some have reported "unexpected response" exceptions.
988 # Note that ignoring them here causes loops.
989 # Instead, send me details of the unexpected response and
990 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000991
Tim Peters07e99cb2001-01-14 23:47:14 +0000992 try:
993 self._get_response()
994 except self.abort, val:
995 if __debug__:
996 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000997 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
1000
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Piers Lauder15e5d532001-07-20 10:52:06 +00001003 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +00001004 if not line:
1005 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001006
Tim Peters07e99cb2001-01-14 23:47:14 +00001007 # Protocol mandates all lines terminated by CRLF
R. David Murray93321f32009-12-09 15:15:31 +00001008 if not line.endswith('\r\n'):
1009 raise self.abort('socket error: unterminated line')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001010
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 line = line[:-2]
1012 if __debug__:
1013 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001014 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001015 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001016 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +00001017 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001018
1019
Tim Peters07e99cb2001-01-14 23:47:14 +00001020 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001021
Tim Peters07e99cb2001-01-14 23:47:14 +00001022 # Run compiled regular expression match method on 's'.
1023 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001024
Tim Peters07e99cb2001-01-14 23:47:14 +00001025 self.mo = cre.match(s)
1026 if __debug__:
1027 if self.mo is not None and self.debug >= 5:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001028 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
Tim Peters07e99cb2001-01-14 23:47:14 +00001029 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001030
1031
Tim Peters07e99cb2001-01-14 23:47:14 +00001032 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001033
Tim Peters07e99cb2001-01-14 23:47:14 +00001034 tag = '%s%s' % (self.tagpre, self.tagnum)
1035 self.tagnum = self.tagnum + 1
1036 self.tagged_commands[tag] = None
1037 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001038
1039
Tim Peters07e99cb2001-01-14 23:47:14 +00001040 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001041
Tim Peters07e99cb2001-01-14 23:47:14 +00001042 # Must quote command args if non-alphanumeric chars present,
1043 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +00001044
Tim Peters07e99cb2001-01-14 23:47:14 +00001045 if type(arg) is not type(''):
1046 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001047 if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
Tim Peters07e99cb2001-01-14 23:47:14 +00001048 return arg
Piers Lauderc09acfd2004-10-08 04:05:39 +00001049 if arg and self.mustquote.search(arg) is None:
Tim Peters07e99cb2001-01-14 23:47:14 +00001050 return arg
1051 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +00001052
1053
Tim Peters07e99cb2001-01-14 23:47:14 +00001054 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +00001055
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001056 arg = arg.replace('\\', '\\\\')
1057 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +00001058
Tim Peters07e99cb2001-01-14 23:47:14 +00001059 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +00001060
1061
Tim Peters07e99cb2001-01-14 23:47:14 +00001062 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001063
Guido van Rossum68468eb2003-02-27 20:14:51 +00001064 return self._command_complete(name, self._command(name, *args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001065
1066
Tim Peters07e99cb2001-01-14 23:47:14 +00001067 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001068
Tim Peters07e99cb2001-01-14 23:47:14 +00001069 if typ == 'NO':
1070 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001071 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001072 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001073 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001074 if __debug__:
1075 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001076 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001077 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001078
1079
Piers Lauderf2d7d152002-02-22 01:15:17 +00001080 if __debug__:
1081
1082 def _mesg(self, s, secs=None):
1083 if secs is None:
1084 secs = time.time()
1085 tm = time.strftime('%M:%S', time.localtime(secs))
1086 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1087 sys.stderr.flush()
1088
1089 def _dump_ur(self, dict):
1090 # Dump untagged responses (in `dict').
1091 l = dict.items()
1092 if not l: return
1093 t = '\n\t\t'
1094 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1095 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1096
1097 def _log(self, line):
1098 # Keep log of last `_cmd_log_len' interactions for debugging.
1099 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1100 self._cmd_log_idx += 1
1101 if self._cmd_log_idx >= self._cmd_log_len:
1102 self._cmd_log_idx = 0
1103
1104 def print_log(self):
1105 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1106 i, n = self._cmd_log_idx, self._cmd_log_len
1107 while n:
1108 try:
Guido van Rossum68468eb2003-02-27 20:14:51 +00001109 self._mesg(*self._cmd_log[i])
Piers Lauderf2d7d152002-02-22 01:15:17 +00001110 except:
1111 pass
1112 i += 1
1113 if i >= self._cmd_log_len:
1114 i = 0
1115 n -= 1
1116
1117
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001118
Bill Janssen426ea0a2007-08-29 22:35:05 +00001119try:
1120 import ssl
1121except ImportError:
1122 pass
1123else:
1124 class IMAP4_SSL(IMAP4):
Piers Laudera4f83132002-03-08 01:53:24 +00001125
Bill Janssen426ea0a2007-08-29 22:35:05 +00001126 """IMAP4 client class over SSL connection
Piers Laudera4f83132002-03-08 01:53:24 +00001127
Bill Janssen426ea0a2007-08-29 22:35:05 +00001128 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001129
Bill Janssen426ea0a2007-08-29 22:35:05 +00001130 host - host's name (default: localhost);
1131 port - port number (default: standard IMAP4 SSL port).
1132 keyfile - PEM formatted file that contains your private key (default: None);
1133 certfile - PEM formatted certificate chain file (default: None);
Piers Laudera4f83132002-03-08 01:53:24 +00001134
Bill Janssen426ea0a2007-08-29 22:35:05 +00001135 for more documentation see the docstring of the parent class IMAP4.
Piers Laudera4f83132002-03-08 01:53:24 +00001136 """
Piers Laudera4f83132002-03-08 01:53:24 +00001137
1138
Bill Janssen426ea0a2007-08-29 22:35:05 +00001139 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1140 self.keyfile = keyfile
1141 self.certfile = certfile
1142 IMAP4.__init__(self, host, port)
Piers Laudera4f83132002-03-08 01:53:24 +00001143
1144
Bill Janssen426ea0a2007-08-29 22:35:05 +00001145 def open(self, host = '', port = IMAP4_SSL_PORT):
1146 """Setup connection to remote server on "host:port".
1147 (default: localhost:standard IMAP4 SSL port).
1148 This connection will be used by the routines:
1149 read, readline, send, shutdown.
1150 """
1151 self.host = host
1152 self.port = port
Antoine Pitrou52035a02009-05-15 11:50:29 +00001153 self.sock = socket.create_connection((host, port))
Bill Janssen98d19da2007-09-10 21:51:02 +00001154 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001155
1156
Bill Janssen426ea0a2007-08-29 22:35:05 +00001157 def read(self, size):
1158 """Read 'size' bytes from remote."""
1159 # sslobj.read() sometimes returns < size bytes
1160 chunks = []
1161 read = 0
1162 while read < size:
Andrew M. Kuchling1219a802008-02-23 19:02:33 +00001163 data = self.sslobj.read(min(size-read, 16384))
Bill Janssen426ea0a2007-08-29 22:35:05 +00001164 read += len(data)
1165 chunks.append(data)
1166
1167 return ''.join(chunks)
Piers Laudera4f83132002-03-08 01:53:24 +00001168
1169
Bill Janssen426ea0a2007-08-29 22:35:05 +00001170 def readline(self):
1171 """Read line from remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001172 line = []
1173 while 1:
1174 char = self.sslobj.read(1)
1175 line.append(char)
R. David Murray93321f32009-12-09 15:15:31 +00001176 if char in ("\n", ""): return ''.join(line)
Piers Laudera4f83132002-03-08 01:53:24 +00001177
1178
Bill Janssen426ea0a2007-08-29 22:35:05 +00001179 def send(self, data):
1180 """Send data to remote."""
Bill Janssen426ea0a2007-08-29 22:35:05 +00001181 bytes = len(data)
1182 while bytes > 0:
1183 sent = self.sslobj.write(data)
1184 if sent == bytes:
1185 break # avoid copy
1186 data = data[sent:]
1187 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001188
1189
Bill Janssen426ea0a2007-08-29 22:35:05 +00001190 def shutdown(self):
1191 """Close I/O established in "open"."""
1192 self.sock.close()
Piers Laudera4f83132002-03-08 01:53:24 +00001193
Bill Janssen426ea0a2007-08-29 22:35:05 +00001194
1195 def socket(self):
1196 """Return socket instance used to connect to IMAP4 server.
1197
1198 socket = <instance>.socket()
1199 """
1200 return self.sock
1201
1202
1203 def ssl(self):
1204 """Return SSLObject instance used to communicate with the IMAP4 server.
1205
Bill Janssen98d19da2007-09-10 21:51:02 +00001206 ssl = ssl.wrap_socket(<instance>.socket)
Bill Janssen426ea0a2007-08-29 22:35:05 +00001207 """
1208 return self.sslobj
Piers Laudera4f83132002-03-08 01:53:24 +00001209
Thomas Woutersa6900e82007-08-30 21:54:39 +00001210 __all__.append("IMAP4_SSL")
Piers Laudera4f83132002-03-08 01:53:24 +00001211
1212
Piers Laudere0273de2002-11-22 05:53:04 +00001213class IMAP4_stream(IMAP4):
1214
1215 """IMAP4 client class over a stream
1216
1217 Instantiate with: IMAP4_stream(command)
1218
Georg Brandl36f42142010-01-02 12:35:01 +00001219 where "command" is a string that can be passed to subprocess.Popen()
Piers Laudere0273de2002-11-22 05:53:04 +00001220
1221 for more documentation see the docstring of the parent class IMAP4.
1222 """
1223
1224
1225 def __init__(self, command):
1226 self.command = command
1227 IMAP4.__init__(self)
1228
1229
1230 def open(self, host = None, port = None):
1231 """Setup a stream connection.
1232 This connection will be used by the routines:
1233 read, readline, send, shutdown.
1234 """
1235 self.host = None # For compatibility with parent class
1236 self.port = None
1237 self.sock = None
1238 self.file = None
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001239 self.process = subprocess.Popen(self.command,
1240 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1241 shell=True, close_fds=True)
1242 self.writefile = self.process.stdin
1243 self.readfile = self.process.stdout
Piers Laudere0273de2002-11-22 05:53:04 +00001244
1245
1246 def read(self, size):
1247 """Read 'size' bytes from remote."""
1248 return self.readfile.read(size)
1249
1250
1251 def readline(self):
1252 """Read line from remote."""
1253 return self.readfile.readline()
1254
1255
1256 def send(self, data):
1257 """Send data to remote."""
1258 self.writefile.write(data)
1259 self.writefile.flush()
1260
1261
1262 def shutdown(self):
1263 """Close I/O established in "open"."""
1264 self.readfile.close()
1265 self.writefile.close()
Benjamin Peterson1a635e42010-01-02 02:43:04 +00001266 self.process.wait()
Piers Laudere0273de2002-11-22 05:53:04 +00001267
1268
1269
Guido van Rossumeda960a1998-06-18 14:24:28 +00001270class _Authenticator:
1271
Tim Peters07e99cb2001-01-14 23:47:14 +00001272 """Private class to provide en/decoding
1273 for base64-based authentication conversation.
1274 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001275
Tim Peters07e99cb2001-01-14 23:47:14 +00001276 def __init__(self, mechinst):
1277 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001278
Tim Peters07e99cb2001-01-14 23:47:14 +00001279 def process(self, data):
1280 ret = self.mech(self.decode(data))
1281 if ret is None:
1282 return '*' # Abort conversation
1283 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001284
Tim Peters07e99cb2001-01-14 23:47:14 +00001285 def encode(self, inp):
1286 #
1287 # Invoke binascii.b2a_base64 iteratively with
1288 # short even length buffers, strip the trailing
1289 # line feed from the result and append. "Even"
1290 # means a number that factors to both 6 and 8,
1291 # so when it gets to the end of the 8-bit input
1292 # there's no partial 6-bit output.
1293 #
1294 oup = ''
1295 while inp:
1296 if len(inp) > 48:
1297 t = inp[:48]
1298 inp = inp[48:]
1299 else:
1300 t = inp
1301 inp = ''
1302 e = binascii.b2a_base64(t)
1303 if e:
1304 oup = oup + e[:-1]
1305 return oup
1306
1307 def decode(self, inp):
1308 if not inp:
1309 return ''
1310 return binascii.a2b_base64(inp)
1311
Guido van Rossumeda960a1998-06-18 14:24:28 +00001312
1313
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001314Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001315 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001316
1317def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001318 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001319
Tim Peters07e99cb2001-01-14 23:47:14 +00001320 Returns Python time module tuple.
1321 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001322
Tim Peters07e99cb2001-01-14 23:47:14 +00001323 mo = InternalDate.match(resp)
1324 if not mo:
1325 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001326
Tim Peters07e99cb2001-01-14 23:47:14 +00001327 mon = Mon2num[mo.group('mon')]
1328 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001329
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001330 day = int(mo.group('day'))
1331 year = int(mo.group('year'))
1332 hour = int(mo.group('hour'))
1333 min = int(mo.group('min'))
1334 sec = int(mo.group('sec'))
1335 zoneh = int(mo.group('zoneh'))
1336 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001337
Tim Peters07e99cb2001-01-14 23:47:14 +00001338 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001339
Tim Peters07e99cb2001-01-14 23:47:14 +00001340 zone = (zoneh*60 + zonem)*60
1341 if zonen == '-':
1342 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001343
Tim Peters07e99cb2001-01-14 23:47:14 +00001344 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001345
Tim Peters07e99cb2001-01-14 23:47:14 +00001346 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001347
Tim Peters07e99cb2001-01-14 23:47:14 +00001348 # Following is necessary because the time module has no 'mkgmtime'.
1349 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001350
Tim Peters07e99cb2001-01-14 23:47:14 +00001351 lt = time.localtime(utc)
1352 if time.daylight and lt[-1]:
1353 zone = zone + time.altzone
1354 else:
1355 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001356
Tim Peters07e99cb2001-01-14 23:47:14 +00001357 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001358
1359
1360
1361def Int2AP(num):
1362
Tim Peters07e99cb2001-01-14 23:47:14 +00001363 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001364
Tim Peters07e99cb2001-01-14 23:47:14 +00001365 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1366 num = int(abs(num))
1367 while num:
1368 num, mod = divmod(num, 16)
1369 val = AP[mod] + val
1370 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001371
1372
1373
1374def ParseFlags(resp):
1375
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001377
Tim Peters07e99cb2001-01-14 23:47:14 +00001378 mo = Flags.match(resp)
1379 if not mo:
1380 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001381
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001382 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001383
1384
1385def Time2Internaldate(date_time):
1386
Tim Peters07e99cb2001-01-14 23:47:14 +00001387 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001388
Tim Peters07e99cb2001-01-14 23:47:14 +00001389 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1390 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001391
Fred Drakedb519202002-01-05 17:17:09 +00001392 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001393 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001394 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001395 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001396 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001397 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001398 else:
1399 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001400
Tim Peters07e99cb2001-01-14 23:47:14 +00001401 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1402 if dt[0] == '0':
1403 dt = ' ' + dt[1:]
1404 if time.daylight and tt[-1]:
1405 zone = -time.altzone
1406 else:
1407 zone = -time.timezone
Raymond Hettingerffdb8bb2004-09-27 15:29:05 +00001408 return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001409
1410
1411
Guido van Rossum8c062211999-12-13 23:27:45 +00001412if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001413
Piers Laudere0273de2002-11-22 05:53:04 +00001414 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1415 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1416 # to test the IMAP4_stream class
1417
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001418 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001419
Tim Peters07e99cb2001-01-14 23:47:14 +00001420 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001421 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001422 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001423 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001424
Piers Laudere0273de2002-11-22 05:53:04 +00001425 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001426 for opt,val in optlist:
1427 if opt == '-d':
1428 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001429 elif opt == '-s':
1430 stream_command = val
1431 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001432
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001434
Tim Peters07e99cb2001-01-14 23:47:14 +00001435 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001436
Tim Peters07e99cb2001-01-14 23:47:14 +00001437 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001438 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001439
Piers Lauder47404ff2003-04-29 23:40:59 +00001440 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 +00001441 test_seq1 = (
1442 ('login', (USER, PASSWD)),
1443 ('create', ('/tmp/xxx 1',)),
1444 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1445 ('CREATE', ('/tmp/yyz 2',)),
1446 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1447 ('list', ('/tmp', 'yy*')),
1448 ('select', ('/tmp/yyz 2',)),
1449 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001450 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001451 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001452 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001453 ('expunge', ()),
1454 ('recent', ()),
1455 ('close', ()),
1456 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001457
Tim Peters07e99cb2001-01-14 23:47:14 +00001458 test_seq2 = (
1459 ('select', ()),
1460 ('response',('UIDVALIDITY',)),
1461 ('uid', ('SEARCH', 'ALL')),
1462 ('response', ('EXISTS',)),
1463 ('append', (None, None, None, test_mesg)),
1464 ('recent', ()),
1465 ('logout', ()),
1466 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001467
Tim Peters07e99cb2001-01-14 23:47:14 +00001468 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001469 M._mesg('%s %s' % (cmd, args))
Guido van Rossum68468eb2003-02-27 20:14:51 +00001470 typ, dat = getattr(M, cmd)(*args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001471 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001472 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001473 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001474
Tim Peters07e99cb2001-01-14 23:47:14 +00001475 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001476 if stream_command:
1477 M = IMAP4_stream(stream_command)
1478 else:
1479 M = IMAP4(host)
1480 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001481 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001482 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001483 M._mesg('CAPABILITIES = %r' % (M.capabilities,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001484
Tim Peters07e99cb2001-01-14 23:47:14 +00001485 for cmd,args in test_seq1:
1486 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001487
Tim Peters07e99cb2001-01-14 23:47:14 +00001488 for ml in run('list', ('/tmp/', 'yy%')):
1489 mo = re.match(r'.*"([^"]+)"$', ml)
1490 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001491 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001492 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001493
Tim Peters07e99cb2001-01-14 23:47:14 +00001494 for cmd,args in test_seq2:
1495 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001496
Tim Peters07e99cb2001-01-14 23:47:14 +00001497 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1498 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001499
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001500 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001501 if not uid: continue
1502 run('uid', ('FETCH', '%s' % uid[-1],
1503 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001504
Tim Peters07e99cb2001-01-14 23:47:14 +00001505 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001506
Tim Peters07e99cb2001-01-14 23:47:14 +00001507 except:
1508 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001509
Tim Peters07e99cb2001-01-14 23:47:14 +00001510 if not Debug:
1511 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001512If you would like to see debugging output,
1513try: %s -d5
1514''' % sys.argv[0]
1515
Tim Peters07e99cb2001-01-14 23:47:14 +00001516 raise