blob: 6a27a67e12c926a05bd9738d2616082ac8295672 [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.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000021
Piers Laudere0273de2002-11-22 05:53:04 +000022__version__ = "2.54"
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000023
Piers Laudere0273de2002-11-22 05:53:04 +000024import binascii, os, random, re, socket, sys, time
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000025
Raymond Hettingerd8ea2e02002-10-30 06:20:37 +000026__all__ = ["IMAP4", "IMAP4_SSL", "Internaldate2tuple",
Barry Warsawf4493912001-01-24 04:16:09 +000027 "Int2AP", "ParseFlags", "Time2Internaldate"]
Skip Montanaro2dd42762001-01-23 15:35:05 +000028
Tim Peters07e99cb2001-01-14 23:47:14 +000029# Globals
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000030
31CRLF = '\r\n'
32Debug = 0
33IMAP4_PORT = 143
Piers Lauder95f84952002-03-08 09:05:12 +000034IMAP4_SSL_PORT = 993
Tim Peters07e99cb2001-01-14 23:47:14 +000035AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000036
Tim Peters07e99cb2001-01-14 23:47:14 +000037# Commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000038
39Commands = {
Tim Peters07e99cb2001-01-14 23:47:14 +000040 # name valid states
41 'APPEND': ('AUTH', 'SELECTED'),
42 'AUTHENTICATE': ('NONAUTH',),
43 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
44 'CHECK': ('SELECTED',),
45 'CLOSE': ('SELECTED',),
46 'COPY': ('SELECTED',),
47 'CREATE': ('AUTH', 'SELECTED'),
48 'DELETE': ('AUTH', 'SELECTED'),
49 'EXAMINE': ('AUTH', 'SELECTED'),
50 'EXPUNGE': ('SELECTED',),
51 'FETCH': ('SELECTED',),
Piers Lauder15e5d532001-07-20 10:52:06 +000052 'GETACL': ('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000053 'GETQUOTA': ('AUTH', 'SELECTED'),
54 'GETQUOTAROOT': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000055 'LIST': ('AUTH', 'SELECTED'),
56 'LOGIN': ('NONAUTH',),
57 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
58 'LSUB': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000059 'NAMESPACE': ('AUTH', 'SELECTED'),
Tim Peters07e99cb2001-01-14 23:47:14 +000060 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
Piers Lauderf2d7d152002-02-22 01:15:17 +000061 'PARTIAL': ('SELECTED',), # NB: obsolete
Piers Laudere0273de2002-11-22 05:53:04 +000062 'PROXYAUTH': ('AUTH',),
Tim Peters07e99cb2001-01-14 23:47:14 +000063 'RENAME': ('AUTH', 'SELECTED'),
64 'SEARCH': ('SELECTED',),
65 'SELECT': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000066 'SETACL': ('AUTH', 'SELECTED'),
Piers Lauder3fca2912002-06-17 07:07:20 +000067 'SETQUOTA': ('AUTH', 'SELECTED'),
Piers Lauder15e5d532001-07-20 10:52:06 +000068 'SORT': ('SELECTED',),
Tim Peters07e99cb2001-01-14 23:47:14 +000069 'STATUS': ('AUTH', 'SELECTED'),
70 'STORE': ('SELECTED',),
71 'SUBSCRIBE': ('AUTH', 'SELECTED'),
72 'UID': ('SELECTED',),
73 'UNSUBSCRIBE': ('AUTH', 'SELECTED'),
74 }
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000075
Tim Peters07e99cb2001-01-14 23:47:14 +000076# Patterns to match server responses
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000077
Guido van Rossumeda960a1998-06-18 14:24:28 +000078Continuation = re.compile(r'\+( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000079Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
80InternalDate = re.compile(r'.*INTERNALDATE "'
Tim Peters07e99cb2001-01-14 23:47:14 +000081 r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
82 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
83 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
84 r'"')
Guido van Rossumf36b1822000-02-17 17:12:39 +000085Literal = re.compile(r'.*{(?P<size>\d+)}$')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000086Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
Guido van Rossumeda960a1998-06-18 14:24:28 +000087Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000088Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
89
90
91
92class IMAP4:
93
Tim Peters07e99cb2001-01-14 23:47:14 +000094 """IMAP4 client class.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000095
Tim Peters07e99cb2001-01-14 23:47:14 +000096 Instantiate with: IMAP4([host[, port]])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +000097
Tim Peters07e99cb2001-01-14 23:47:14 +000098 host - host's name (default: localhost);
99 port - port number (default: standard IMAP4 port).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000100
Tim Peters07e99cb2001-01-14 23:47:14 +0000101 All IMAP4rev1 commands are supported by methods of the same
102 name (in lower-case).
Guido van Rossum6884af71998-05-29 13:34:03 +0000103
Tim Peters07e99cb2001-01-14 23:47:14 +0000104 All arguments to commands are converted to strings, except for
105 AUTHENTICATE, and the last argument to APPEND which is passed as
106 an IMAP4 literal. If necessary (the string contains any
107 non-printing characters or white-space and isn't enclosed with
108 either parentheses or double quotes) each string is quoted.
109 However, the 'password' argument to the LOGIN command is always
110 quoted. If you want to avoid having an argument string quoted
111 (eg: the 'flags' argument to STORE) then enclose the string in
112 parentheses (eg: "(\Deleted)").
Guido van Rossum6884af71998-05-29 13:34:03 +0000113
Tim Peters07e99cb2001-01-14 23:47:14 +0000114 Each command returns a tuple: (type, [data, ...]) where 'type'
115 is usually 'OK' or 'NO', and 'data' is either the text from the
Piers Laudere0273de2002-11-22 05:53:04 +0000116 tagged response, or untagged results from command. Each 'data'
117 is either a string, or a tuple. If a tuple, then the first part
118 is the header of the response, and the second part contains
119 the data (ie: 'literal' value).
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000120
Tim Peters07e99cb2001-01-14 23:47:14 +0000121 Errors raise the exception class <instance>.error("<reason>").
122 IMAP4 server errors raise <instance>.abort("<reason>"),
123 which is a sub-class of 'error'. Mailbox status changes
124 from READ-WRITE to READ-ONLY raise the exception class
125 <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000126
Tim Peters07e99cb2001-01-14 23:47:14 +0000127 "error" exceptions imply a program error.
128 "abort" exceptions imply the connection should be reset, and
129 the command re-tried.
130 "readonly" exceptions imply the command should be re-tried.
Guido van Rossum8c062211999-12-13 23:27:45 +0000131
Tim Peters07e99cb2001-01-14 23:47:14 +0000132 Note: to use this module, you must read the RFCs pertaining
133 to the IMAP4 protocol, as the semantics of the arguments to
134 each IMAP4 command are left to the invoker, not to mention
135 the results.
136 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000137
Tim Peters07e99cb2001-01-14 23:47:14 +0000138 class error(Exception): pass # Logical errors - debug required
139 class abort(error): pass # Service errors - close and retry
140 class readonly(abort): pass # Mailbox status changed to READ-ONLY
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000141
Tim Peters07e99cb2001-01-14 23:47:14 +0000142 mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000143
Tim Peters07e99cb2001-01-14 23:47:14 +0000144 def __init__(self, host = '', port = IMAP4_PORT):
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 self.debug = Debug
146 self.state = 'LOGOUT'
147 self.literal = None # A literal argument to a command
148 self.tagged_commands = {} # Tagged commands awaiting response
149 self.untagged_responses = {} # {typ: [data, ...], ...}
150 self.continuation_response = '' # Last continuation response
151 self.is_readonly = None # READ-ONLY desired state
152 self.tagnum = 0
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 # Open socket to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000155
Tim Peters07e99cb2001-01-14 23:47:14 +0000156 self.open(host, port)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000157
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 # Create unique tag for this session,
159 # and compile tagged response matcher.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000160
Tim Peters07e99cb2001-01-14 23:47:14 +0000161 self.tagpre = Int2AP(random.randint(0, 31999))
162 self.tagre = re.compile(r'(?P<tag>'
163 + self.tagpre
164 + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000165
Tim Peters07e99cb2001-01-14 23:47:14 +0000166 # Get server welcome message,
167 # request and store CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000168
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 if __debug__:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000170 self._cmd_log_len = 10
171 self._cmd_log_idx = 0
172 self._cmd_log = {} # Last `_cmd_log_len' interactions
Tim Peters07e99cb2001-01-14 23:47:14 +0000173 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000174 self._mesg('imaplib version %s' % __version__)
175 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000176
Tim Peters07e99cb2001-01-14 23:47:14 +0000177 self.welcome = self._get_response()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000178 if 'PREAUTH' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000179 self.state = 'AUTH'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000180 elif 'OK' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000181 self.state = 'NONAUTH'
182 else:
183 raise self.error(self.welcome)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000184
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 cap = 'CAPABILITY'
186 self._simple_command(cap)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000187 if not cap in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000188 raise self.error('no CAPABILITY response from server')
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000189 self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000190
Tim Peters07e99cb2001-01-14 23:47:14 +0000191 if __debug__:
192 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000193 self._mesg('CAPABILITIES: %s' % `self.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000194
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 for version in AllowedVersions:
196 if not version in self.capabilities:
197 continue
198 self.PROTOCOL_VERSION = version
199 return
Guido van Rossumb1f08121998-06-25 02:22:16 +0000200
Tim Peters07e99cb2001-01-14 23:47:14 +0000201 raise self.error('server not IMAP4 compliant')
Guido van Rossum38d8f4e1998-04-11 01:22:34 +0000202
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000203
Tim Peters07e99cb2001-01-14 23:47:14 +0000204 def __getattr__(self, attr):
205 # Allow UPPERCASE variants of IMAP4 command methods.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000206 if attr in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000207 return getattr(self, attr.lower())
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
Guido van Rossum26367a01998-09-28 15:34:46 +0000209
210
211
Piers Lauder15e5d532001-07-20 10:52:06 +0000212 # Overridable methods
Guido van Rossum26367a01998-09-28 15:34:46 +0000213
214
Piers Lauderf97b2d72002-06-05 22:31:57 +0000215 def open(self, host = '', port = IMAP4_PORT):
216 """Setup connection to remote server on "host:port"
217 (default: localhost:standard IMAP4 port).
Piers Lauder15e5d532001-07-20 10:52:06 +0000218 This connection will be used by the routines:
219 read, readline, send, shutdown.
220 """
Piers Lauderf97b2d72002-06-05 22:31:57 +0000221 self.host = host
222 self.port = port
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +0000224 self.sock.connect((host, port))
Guido van Rossumc0f1bfe2001-10-15 13:47:08 +0000225 self.file = self.sock.makefile('rb')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000226
227
Piers Lauder15e5d532001-07-20 10:52:06 +0000228 def read(self, size):
229 """Read 'size' bytes from remote."""
230 return self.file.read(size)
231
232
233 def readline(self):
234 """Read line from remote."""
235 return self.file.readline()
236
237
238 def send(self, data):
239 """Send data to remote."""
Martin v. Löwise12454f2002-02-16 23:06:19 +0000240 self.sock.sendall(data)
Piers Lauder15e5d532001-07-20 10:52:06 +0000241
Piers Lauderf2d7d152002-02-22 01:15:17 +0000242
Piers Lauder15e5d532001-07-20 10:52:06 +0000243 def shutdown(self):
244 """Close I/O established in "open"."""
245 self.file.close()
246 self.sock.close()
247
248
249 def socket(self):
250 """Return socket instance used to connect to IMAP4 server.
251
252 socket = <instance>.socket()
253 """
254 return self.sock
255
256
257
258 # Utility methods
259
260
Tim Peters07e99cb2001-01-14 23:47:14 +0000261 def recent(self):
262 """Return most recent 'RECENT' responses if any exist,
263 else prompt server for an update using the 'NOOP' command.
Guido van Rossum26367a01998-09-28 15:34:46 +0000264
Tim Peters07e99cb2001-01-14 23:47:14 +0000265 (typ, [data]) = <instance>.recent()
Guido van Rossum26367a01998-09-28 15:34:46 +0000266
Tim Peters07e99cb2001-01-14 23:47:14 +0000267 'data' is None if no new messages,
268 else list of RECENT responses, most recent last.
269 """
270 name = 'RECENT'
271 typ, dat = self._untagged_response('OK', [None], name)
272 if dat[-1]:
273 return typ, dat
274 typ, dat = self.noop() # Prod server for response
275 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000276
277
Tim Peters07e99cb2001-01-14 23:47:14 +0000278 def response(self, code):
279 """Return data for response 'code' if received, or None.
Guido van Rossum26367a01998-09-28 15:34:46 +0000280
Tim Peters07e99cb2001-01-14 23:47:14 +0000281 Old value for response 'code' is cleared.
Guido van Rossum26367a01998-09-28 15:34:46 +0000282
Tim Peters07e99cb2001-01-14 23:47:14 +0000283 (code, [data]) = <instance>.response(code)
284 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000285 return self._untagged_response(code, [None], code.upper())
Guido van Rossum26367a01998-09-28 15:34:46 +0000286
287
Guido van Rossum26367a01998-09-28 15:34:46 +0000288
Tim Peters07e99cb2001-01-14 23:47:14 +0000289 # IMAP4 commands
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000290
291
Tim Peters07e99cb2001-01-14 23:47:14 +0000292 def append(self, mailbox, flags, date_time, message):
293 """Append message to named mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000294
Tim Peters07e99cb2001-01-14 23:47:14 +0000295 (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
Guido van Rossum8c062211999-12-13 23:27:45 +0000296
Tim Peters07e99cb2001-01-14 23:47:14 +0000297 All args except `message' can be None.
298 """
299 name = 'APPEND'
300 if not mailbox:
301 mailbox = 'INBOX'
302 if flags:
303 if (flags[0],flags[-1]) != ('(',')'):
304 flags = '(%s)' % flags
305 else:
306 flags = None
307 if date_time:
308 date_time = Time2Internaldate(date_time)
309 else:
310 date_time = None
311 self.literal = message
312 return self._simple_command(name, mailbox, flags, date_time)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000313
314
Tim Peters07e99cb2001-01-14 23:47:14 +0000315 def authenticate(self, mechanism, authobject):
316 """Authenticate command - requires response processing.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000317
Tim Peters07e99cb2001-01-14 23:47:14 +0000318 'mechanism' specifies which authentication mechanism is to
319 be used - it must appear in <instance>.capabilities in the
320 form AUTH=<mechanism>.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000321
Tim Peters07e99cb2001-01-14 23:47:14 +0000322 'authobject' must be a callable object:
Guido van Rossumeda960a1998-06-18 14:24:28 +0000323
Tim Peters07e99cb2001-01-14 23:47:14 +0000324 data = authobject(response)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000325
Tim Peters07e99cb2001-01-14 23:47:14 +0000326 It will be called to process server continuation responses.
327 It should return data that will be encoded and sent to server.
328 It should return None if the client abort response '*' should
329 be sent instead.
330 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000331 mech = mechanism.upper()
Tim Peters07e99cb2001-01-14 23:47:14 +0000332 cap = 'AUTH=%s' % mech
Tim Peters77c06fb2002-11-24 02:35:35 +0000333 #if not cap in self.capabilities: # Let the server decide!
Piers Laudere0273de2002-11-22 05:53:04 +0000334 # raise self.error("Server doesn't allow %s authentication." % mech)
Tim Peters07e99cb2001-01-14 23:47:14 +0000335 self.literal = _Authenticator(authobject).process
336 typ, dat = self._simple_command('AUTHENTICATE', mech)
337 if typ != 'OK':
338 raise self.error(dat[-1])
339 self.state = 'AUTH'
340 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000341
342
Tim Peters07e99cb2001-01-14 23:47:14 +0000343 def check(self):
344 """Checkpoint mailbox on server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000345
Tim Peters07e99cb2001-01-14 23:47:14 +0000346 (typ, [data]) = <instance>.check()
347 """
348 return self._simple_command('CHECK')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000349
350
Tim Peters07e99cb2001-01-14 23:47:14 +0000351 def close(self):
352 """Close currently selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000353
Tim Peters07e99cb2001-01-14 23:47:14 +0000354 Deleted messages are removed from writable mailbox.
355 This is the recommended command before 'LOGOUT'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000356
Tim Peters07e99cb2001-01-14 23:47:14 +0000357 (typ, [data]) = <instance>.close()
358 """
359 try:
360 typ, dat = self._simple_command('CLOSE')
361 finally:
362 self.state = 'AUTH'
363 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000364
365
Tim Peters07e99cb2001-01-14 23:47:14 +0000366 def copy(self, message_set, new_mailbox):
367 """Copy 'message_set' messages onto end of 'new_mailbox'.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000368
Tim Peters07e99cb2001-01-14 23:47:14 +0000369 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
370 """
371 return self._simple_command('COPY', message_set, new_mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000372
373
Tim Peters07e99cb2001-01-14 23:47:14 +0000374 def create(self, mailbox):
375 """Create new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000376
Tim Peters07e99cb2001-01-14 23:47:14 +0000377 (typ, [data]) = <instance>.create(mailbox)
378 """
379 return self._simple_command('CREATE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000380
381
Tim Peters07e99cb2001-01-14 23:47:14 +0000382 def delete(self, mailbox):
383 """Delete old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000384
Tim Peters07e99cb2001-01-14 23:47:14 +0000385 (typ, [data]) = <instance>.delete(mailbox)
386 """
387 return self._simple_command('DELETE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000388
389
Tim Peters07e99cb2001-01-14 23:47:14 +0000390 def expunge(self):
391 """Permanently remove deleted items from selected mailbox.
Guido van Rossumeeec0af1998-04-09 14:20:31 +0000392
Tim Peters07e99cb2001-01-14 23:47:14 +0000393 Generates 'EXPUNGE' response for each deleted message.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000394
Tim Peters07e99cb2001-01-14 23:47:14 +0000395 (typ, [data]) = <instance>.expunge()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000396
Tim Peters07e99cb2001-01-14 23:47:14 +0000397 'data' is list of 'EXPUNGE'd message numbers in order received.
398 """
399 name = 'EXPUNGE'
400 typ, dat = self._simple_command(name)
401 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000402
403
Tim Peters07e99cb2001-01-14 23:47:14 +0000404 def fetch(self, message_set, message_parts):
405 """Fetch (parts of) messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000406
Tim Peters07e99cb2001-01-14 23:47:14 +0000407 (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000408
Tim Peters07e99cb2001-01-14 23:47:14 +0000409 'message_parts' should be a string of selected parts
410 enclosed in parentheses, eg: "(UID BODY[TEXT])".
Fred Drakefd267d92000-05-25 03:25:26 +0000411
Tim Peters07e99cb2001-01-14 23:47:14 +0000412 'data' are tuples of message part envelope and data.
413 """
414 name = 'FETCH'
415 typ, dat = self._simple_command(name, message_set, message_parts)
416 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000417
418
Piers Lauder15e5d532001-07-20 10:52:06 +0000419 def getacl(self, mailbox):
420 """Get the ACLs for a mailbox.
421
422 (typ, [data]) = <instance>.getacl(mailbox)
423 """
424 typ, dat = self._simple_command('GETACL', mailbox)
425 return self._untagged_response(typ, dat, 'ACL')
426
427
Piers Lauder3fca2912002-06-17 07:07:20 +0000428 def getquota(self, root):
429 """Get the quota root's resource usage and limits.
430
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000431 Part of the IMAP4 QUOTA extension defined in rfc2087.
Piers Lauder3fca2912002-06-17 07:07:20 +0000432
433 (typ, [data]) = <instance>.getquota(root)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000434 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000435 typ, dat = self._simple_command('GETQUOTA', root)
436 return self._untagged_response(typ, dat, 'QUOTA')
437
438
439 def getquotaroot(self, mailbox):
440 """Get the list of quota roots for the named mailbox.
441
442 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000443 """
Guido van Rossum41b71b22003-01-13 15:04:26 +0000444 typ, dat = self._simple_command('GETQUOTA', mailbox)
Piers Lauder3fca2912002-06-17 07:07:20 +0000445 typ, quota = self._untagged_response(typ, dat, 'QUOTA')
446 typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000447 return typ, [quotaroot, quota]
Piers Lauder3fca2912002-06-17 07:07:20 +0000448
449
Tim Peters07e99cb2001-01-14 23:47:14 +0000450 def list(self, directory='""', pattern='*'):
451 """List mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000452
Tim Peters07e99cb2001-01-14 23:47:14 +0000453 (typ, [data]) = <instance>.list(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000454
Tim Peters07e99cb2001-01-14 23:47:14 +0000455 'data' is list of LIST responses.
456 """
457 name = 'LIST'
458 typ, dat = self._simple_command(name, directory, pattern)
459 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000460
461
Tim Peters07e99cb2001-01-14 23:47:14 +0000462 def login(self, user, password):
463 """Identify client using plaintext password.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000464
Tim Peters07e99cb2001-01-14 23:47:14 +0000465 (typ, [data]) = <instance>.login(user, password)
Guido van Rossum8c062211999-12-13 23:27:45 +0000466
Tim Peters07e99cb2001-01-14 23:47:14 +0000467 NB: 'password' will be quoted.
468 """
Tim Peters07e99cb2001-01-14 23:47:14 +0000469 typ, dat = self._simple_command('LOGIN', user, self._quote(password))
470 if typ != 'OK':
471 raise self.error(dat[-1])
472 self.state = 'AUTH'
473 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000474
475
Piers Laudere0273de2002-11-22 05:53:04 +0000476 def login_cram_md5(self, user, password):
477 """ Force use of CRAM-MD5 authentication.
478
479 (typ, [data]) = <instance>.login_cram_md5(user, password)
480 """
481 self.user, self.password = user, password
482 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
483
484
485 def _CRAM_MD5_AUTH(self, challenge):
486 """ Authobject to use with CRAM-MD5 authentication. """
487 import hmac
488 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
489
490
Tim Peters07e99cb2001-01-14 23:47:14 +0000491 def logout(self):
492 """Shutdown connection to server.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000493
Tim Peters07e99cb2001-01-14 23:47:14 +0000494 (typ, [data]) = <instance>.logout()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000495
Tim Peters07e99cb2001-01-14 23:47:14 +0000496 Returns server 'BYE' response.
497 """
498 self.state = 'LOGOUT'
499 try: typ, dat = self._simple_command('LOGOUT')
500 except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
Piers Lauder15e5d532001-07-20 10:52:06 +0000501 self.shutdown()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000502 if 'BYE' in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000503 return 'BYE', self.untagged_responses['BYE']
504 return typ, dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000505
506
Tim Peters07e99cb2001-01-14 23:47:14 +0000507 def lsub(self, directory='""', pattern='*'):
508 """List 'subscribed' mailbox names in directory matching pattern.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000509
Tim Peters07e99cb2001-01-14 23:47:14 +0000510 (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000511
Tim Peters07e99cb2001-01-14 23:47:14 +0000512 'data' are tuples of message part envelope and data.
513 """
514 name = 'LSUB'
515 typ, dat = self._simple_command(name, directory, pattern)
516 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000517
518
Piers Lauder15e5d532001-07-20 10:52:06 +0000519 def namespace(self):
520 """ Returns IMAP namespaces ala rfc2342
521
522 (typ, [data, ...]) = <instance>.namespace()
523 """
524 name = 'NAMESPACE'
525 typ, dat = self._simple_command(name)
526 return self._untagged_response(typ, dat, name)
527
528
Tim Peters07e99cb2001-01-14 23:47:14 +0000529 def noop(self):
530 """Send NOOP command.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000531
Piers Laudere0273de2002-11-22 05:53:04 +0000532 (typ, [data]) = <instance>.noop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000533 """
534 if __debug__:
535 if self.debug >= 3:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000536 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000537 return self._simple_command('NOOP')
Guido van Rossum6884af71998-05-29 13:34:03 +0000538
539
Tim Peters07e99cb2001-01-14 23:47:14 +0000540 def partial(self, message_num, message_part, start, length):
541 """Fetch truncated part of a message.
Guido van Rossumeda960a1998-06-18 14:24:28 +0000542
Tim Peters07e99cb2001-01-14 23:47:14 +0000543 (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000544
Tim Peters07e99cb2001-01-14 23:47:14 +0000545 'data' is tuple of message part envelope and data.
546 """
547 name = 'PARTIAL'
548 typ, dat = self._simple_command(name, message_num, message_part, start, length)
549 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000550
551
Piers Laudere0273de2002-11-22 05:53:04 +0000552 def proxyauth(self, user):
553 """Assume authentication as "user".
554
555 Allows an authorised administrator to proxy into any user's
556 mailbox.
557
558 (typ, [data]) = <instance>.proxyauth(user)
559 """
560
561 name = 'PROXYAUTH'
562 return self._simple_command('PROXYAUTH', user)
563
564
Tim Peters07e99cb2001-01-14 23:47:14 +0000565 def rename(self, oldmailbox, newmailbox):
566 """Rename old mailbox name to new.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000567
Piers Laudere0273de2002-11-22 05:53:04 +0000568 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
Tim Peters07e99cb2001-01-14 23:47:14 +0000569 """
570 return self._simple_command('RENAME', oldmailbox, newmailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000571
572
Tim Peters07e99cb2001-01-14 23:47:14 +0000573 def search(self, charset, *criteria):
574 """Search mailbox for matching messages.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000575
Tim Peters07e99cb2001-01-14 23:47:14 +0000576 (typ, [data]) = <instance>.search(charset, criterium, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000577
Tim Peters07e99cb2001-01-14 23:47:14 +0000578 'data' is space separated list of matching message numbers.
579 """
580 name = 'SEARCH'
581 if charset:
Piers Lauder15e5d532001-07-20 10:52:06 +0000582 typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria)
583 else:
584 typ, dat = apply(self._simple_command, (name,) + criteria)
Tim Peters07e99cb2001-01-14 23:47:14 +0000585 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000586
587
Tim Peters07e99cb2001-01-14 23:47:14 +0000588 def select(self, mailbox='INBOX', readonly=None):
589 """Select a mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000590
Tim Peters07e99cb2001-01-14 23:47:14 +0000591 Flush all untagged responses.
Guido van Rossum46586821998-05-18 14:39:42 +0000592
Tim Peters07e99cb2001-01-14 23:47:14 +0000593 (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000594
Tim Peters07e99cb2001-01-14 23:47:14 +0000595 'data' is count of messages in mailbox ('EXISTS' response).
596 """
597 # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY')
598 self.untagged_responses = {} # Flush old responses.
599 self.is_readonly = readonly
Raymond Hettinger936654b2002-06-01 03:06:31 +0000600 if readonly is not None:
Tim Peters07e99cb2001-01-14 23:47:14 +0000601 name = 'EXAMINE'
602 else:
603 name = 'SELECT'
604 typ, dat = self._simple_command(name, mailbox)
605 if typ != 'OK':
606 self.state = 'AUTH' # Might have been 'SELECTED'
607 return typ, dat
608 self.state = 'SELECTED'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000609 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000610 and not readonly:
611 if __debug__:
612 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000613 self._dump_ur(self.untagged_responses)
Tim Peters07e99cb2001-01-14 23:47:14 +0000614 raise self.readonly('%s is not writable' % mailbox)
615 return typ, self.untagged_responses.get('EXISTS', [None])
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000616
617
Piers Lauder15e5d532001-07-20 10:52:06 +0000618 def setacl(self, mailbox, who, what):
619 """Set a mailbox acl.
620
621 (typ, [data]) = <instance>.create(mailbox, who, what)
622 """
623 return self._simple_command('SETACL', mailbox, who, what)
624
625
Piers Lauder3fca2912002-06-17 07:07:20 +0000626 def setquota(self, root, limits):
627 """Set the quota root's resource limits.
628
629 (typ, [data]) = <instance>.setquota(root, limits)
Neal Norwitz1ed564a2002-06-17 12:43:20 +0000630 """
Piers Lauder3fca2912002-06-17 07:07:20 +0000631 typ, dat = self._simple_command('SETQUOTA', root, limits)
632 return self._untagged_response(typ, dat, 'QUOTA')
633
634
Piers Lauder15e5d532001-07-20 10:52:06 +0000635 def sort(self, sort_criteria, charset, *search_criteria):
636 """IMAP4rev1 extension SORT command.
637
638 (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
639 """
640 name = 'SORT'
Tim Peters87cc0c32001-07-21 01:41:30 +0000641 #if not name in self.capabilities: # Let the server decide!
642 # raise self.error('unimplemented extension command: %s' % name)
Piers Lauder15e5d532001-07-20 10:52:06 +0000643 if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
Tim Peters87cc0c32001-07-21 01:41:30 +0000644 sort_criteria = '(%s)' % sort_criteria
Piers Lauder15e5d532001-07-20 10:52:06 +0000645 typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria)
646 return self._untagged_response(typ, dat, name)
647
648
Tim Peters07e99cb2001-01-14 23:47:14 +0000649 def status(self, mailbox, names):
650 """Request named status conditions for mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000651
Tim Peters07e99cb2001-01-14 23:47:14 +0000652 (typ, [data]) = <instance>.status(mailbox, names)
653 """
654 name = 'STATUS'
Tim Peters87cc0c32001-07-21 01:41:30 +0000655 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000656 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
Tim Peters07e99cb2001-01-14 23:47:14 +0000657 typ, dat = self._simple_command(name, mailbox, names)
658 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000659
660
Tim Peters07e99cb2001-01-14 23:47:14 +0000661 def store(self, message_set, command, flags):
662 """Alters flag dispositions for messages in mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000663
Tim Peters07e99cb2001-01-14 23:47:14 +0000664 (typ, [data]) = <instance>.store(message_set, command, flags)
665 """
666 if (flags[0],flags[-1]) != ('(',')'):
667 flags = '(%s)' % flags # Avoid quoting the flags
668 typ, dat = self._simple_command('STORE', message_set, command, flags)
669 return self._untagged_response(typ, dat, 'FETCH')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000670
671
Tim Peters07e99cb2001-01-14 23:47:14 +0000672 def subscribe(self, mailbox):
673 """Subscribe to new mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000674
Tim Peters07e99cb2001-01-14 23:47:14 +0000675 (typ, [data]) = <instance>.subscribe(mailbox)
676 """
677 return self._simple_command('SUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000678
679
Tim Peters07e99cb2001-01-14 23:47:14 +0000680 def uid(self, command, *args):
681 """Execute "command arg ..." with messages identified by UID,
682 rather than message number.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000683
Tim Peters07e99cb2001-01-14 23:47:14 +0000684 (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000685
Tim Peters07e99cb2001-01-14 23:47:14 +0000686 Returns response appropriate to 'command'.
687 """
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000688 command = command.upper()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000689 if not command in Commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000690 raise self.error("Unknown IMAP4 UID command: %s" % command)
691 if self.state not in Commands[command]:
692 raise self.error('command %s illegal in state %s'
693 % (command, self.state))
694 name = 'UID'
695 typ, dat = apply(self._simple_command, (name, command) + args)
Piers Lauder15e5d532001-07-20 10:52:06 +0000696 if command in ('SEARCH', 'SORT'):
697 name = command
Tim Peters07e99cb2001-01-14 23:47:14 +0000698 else:
699 name = 'FETCH'
700 return self._untagged_response(typ, dat, name)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000701
702
Tim Peters07e99cb2001-01-14 23:47:14 +0000703 def unsubscribe(self, mailbox):
704 """Unsubscribe from old mailbox.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000705
Tim Peters07e99cb2001-01-14 23:47:14 +0000706 (typ, [data]) = <instance>.unsubscribe(mailbox)
707 """
708 return self._simple_command('UNSUBSCRIBE', mailbox)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000709
710
Tim Peters07e99cb2001-01-14 23:47:14 +0000711 def xatom(self, name, *args):
712 """Allow simple extension commands
713 notified by server in CAPABILITY response.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000714
Piers Lauder15e5d532001-07-20 10:52:06 +0000715 Assumes command is legal in current state.
716
Tim Peters07e99cb2001-01-14 23:47:14 +0000717 (typ, [data]) = <instance>.xatom(name, arg, ...)
Piers Lauder15e5d532001-07-20 10:52:06 +0000718
719 Returns response appropriate to extension command `name'.
Tim Peters07e99cb2001-01-14 23:47:14 +0000720 """
Piers Lauder15e5d532001-07-20 10:52:06 +0000721 name = name.upper()
Tim Peters87cc0c32001-07-21 01:41:30 +0000722 #if not name in self.capabilities: # Let the server decide!
Piers Lauder15e5d532001-07-20 10:52:06 +0000723 # raise self.error('unknown extension command: %s' % name)
Raymond Hettinger54f02222002-06-01 14:18:47 +0000724 if not name in Commands:
Piers Lauder15e5d532001-07-20 10:52:06 +0000725 Commands[name] = (self.state,)
Tim Peters07e99cb2001-01-14 23:47:14 +0000726 return apply(self._simple_command, (name,) + args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000727
728
729
Tim Peters07e99cb2001-01-14 23:47:14 +0000730 # Private methods
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000731
732
Tim Peters07e99cb2001-01-14 23:47:14 +0000733 def _append_untagged(self, typ, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000734
Tim Peters07e99cb2001-01-14 23:47:14 +0000735 if dat is None: dat = ''
736 ur = self.untagged_responses
737 if __debug__:
738 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000739 self._mesg('untagged_responses[%s] %s += ["%s"]' %
Tim Peters07e99cb2001-01-14 23:47:14 +0000740 (typ, len(ur.get(typ,'')), dat))
Raymond Hettinger54f02222002-06-01 14:18:47 +0000741 if typ in ur:
Tim Peters07e99cb2001-01-14 23:47:14 +0000742 ur[typ].append(dat)
743 else:
744 ur[typ] = [dat]
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000745
746
Tim Peters07e99cb2001-01-14 23:47:14 +0000747 def _check_bye(self):
748 bye = self.untagged_responses.get('BYE')
749 if bye:
750 raise self.abort(bye[-1])
Guido van Rossum8c062211999-12-13 23:27:45 +0000751
752
Tim Peters07e99cb2001-01-14 23:47:14 +0000753 def _command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000754
Tim Peters07e99cb2001-01-14 23:47:14 +0000755 if self.state not in Commands[name]:
756 self.literal = None
757 raise self.error(
758 'command %s illegal in state %s' % (name, self.state))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000759
Tim Peters07e99cb2001-01-14 23:47:14 +0000760 for typ in ('OK', 'NO', 'BAD'):
Raymond Hettinger54f02222002-06-01 14:18:47 +0000761 if typ in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +0000762 del self.untagged_responses[typ]
Guido van Rossum26367a01998-09-28 15:34:46 +0000763
Raymond Hettinger54f02222002-06-01 14:18:47 +0000764 if 'READ-ONLY' in self.untagged_responses \
Tim Peters07e99cb2001-01-14 23:47:14 +0000765 and not self.is_readonly:
766 raise self.readonly('mailbox status changed to READ-ONLY')
Guido van Rossumeda960a1998-06-18 14:24:28 +0000767
Tim Peters07e99cb2001-01-14 23:47:14 +0000768 tag = self._new_tag()
769 data = '%s %s' % (tag, name)
770 for arg in args:
771 if arg is None: continue
772 data = '%s %s' % (data, self._checkquote(arg))
Guido van Rossum6884af71998-05-29 13:34:03 +0000773
Tim Peters07e99cb2001-01-14 23:47:14 +0000774 literal = self.literal
775 if literal is not None:
776 self.literal = None
777 if type(literal) is type(self._command):
778 literator = literal
779 else:
780 literator = None
781 data = '%s {%s}' % (data, len(literal))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000782
Tim Peters07e99cb2001-01-14 23:47:14 +0000783 if __debug__:
784 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000785 self._mesg('> %s' % data)
Tim Peters07e99cb2001-01-14 23:47:14 +0000786 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000787 self._log('> %s' % data)
Guido van Rossum8c062211999-12-13 23:27:45 +0000788
Tim Peters07e99cb2001-01-14 23:47:14 +0000789 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000790 self.send('%s%s' % (data, CRLF))
791 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000792 raise self.abort('socket error: %s' % val)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000793
Tim Peters07e99cb2001-01-14 23:47:14 +0000794 if literal is None:
795 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000796
Tim Peters07e99cb2001-01-14 23:47:14 +0000797 while 1:
798 # Wait for continuation response
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000799
Tim Peters07e99cb2001-01-14 23:47:14 +0000800 while self._get_response():
801 if self.tagged_commands[tag]: # BAD/NO?
802 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000803
Tim Peters07e99cb2001-01-14 23:47:14 +0000804 # Send literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000805
Tim Peters07e99cb2001-01-14 23:47:14 +0000806 if literator:
807 literal = literator(self.continuation_response)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000808
Tim Peters07e99cb2001-01-14 23:47:14 +0000809 if __debug__:
810 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000811 self._mesg('write literal size %s' % len(literal))
Guido van Rossumeda960a1998-06-18 14:24:28 +0000812
Tim Peters07e99cb2001-01-14 23:47:14 +0000813 try:
Piers Lauder15e5d532001-07-20 10:52:06 +0000814 self.send(literal)
815 self.send(CRLF)
816 except (socket.error, OSError), val:
Tim Peters07e99cb2001-01-14 23:47:14 +0000817 raise self.abort('socket error: %s' % val)
Guido van Rossumeda960a1998-06-18 14:24:28 +0000818
Tim Peters07e99cb2001-01-14 23:47:14 +0000819 if not literator:
820 break
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000821
Tim Peters07e99cb2001-01-14 23:47:14 +0000822 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000823
824
Tim Peters07e99cb2001-01-14 23:47:14 +0000825 def _command_complete(self, name, tag):
826 self._check_bye()
827 try:
828 typ, data = self._get_tagged_response(tag)
829 except self.abort, val:
830 raise self.abort('command: %s => %s' % (name, val))
831 except self.error, val:
832 raise self.error('command: %s => %s' % (name, val))
833 self._check_bye()
834 if typ == 'BAD':
835 raise self.error('%s command error: %s %s' % (name, typ, data))
836 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000837
838
Tim Peters07e99cb2001-01-14 23:47:14 +0000839 def _get_response(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000840
Tim Peters07e99cb2001-01-14 23:47:14 +0000841 # Read response and store.
842 #
843 # Returns None for continuation responses,
844 # otherwise first response line received.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000845
Tim Peters07e99cb2001-01-14 23:47:14 +0000846 resp = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000847
Tim Peters07e99cb2001-01-14 23:47:14 +0000848 # Command completion response?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000849
Tim Peters07e99cb2001-01-14 23:47:14 +0000850 if self._match(self.tagre, resp):
851 tag = self.mo.group('tag')
Raymond Hettinger54f02222002-06-01 14:18:47 +0000852 if not tag in self.tagged_commands:
Tim Peters07e99cb2001-01-14 23:47:14 +0000853 raise self.abort('unexpected tagged response: %s' % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000854
Tim Peters07e99cb2001-01-14 23:47:14 +0000855 typ = self.mo.group('type')
856 dat = self.mo.group('data')
857 self.tagged_commands[tag] = (typ, [dat])
858 else:
859 dat2 = None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000860
Tim Peters07e99cb2001-01-14 23:47:14 +0000861 # '*' (untagged) responses?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000862
Tim Peters07e99cb2001-01-14 23:47:14 +0000863 if not self._match(Untagged_response, resp):
864 if self._match(Untagged_status, resp):
865 dat2 = self.mo.group('data2')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000866
Tim Peters07e99cb2001-01-14 23:47:14 +0000867 if self.mo is None:
868 # Only other possibility is '+' (continuation) response...
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000869
Tim Peters07e99cb2001-01-14 23:47:14 +0000870 if self._match(Continuation, resp):
871 self.continuation_response = self.mo.group('data')
872 return None # NB: indicates continuation
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000873
Tim Peters07e99cb2001-01-14 23:47:14 +0000874 raise self.abort("unexpected response: '%s'" % resp)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000875
Tim Peters07e99cb2001-01-14 23:47:14 +0000876 typ = self.mo.group('type')
877 dat = self.mo.group('data')
878 if dat is None: dat = '' # Null untagged response
879 if dat2: dat = dat + ' ' + dat2
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000880
Tim Peters07e99cb2001-01-14 23:47:14 +0000881 # Is there a literal to come?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000882
Tim Peters07e99cb2001-01-14 23:47:14 +0000883 while self._match(Literal, dat):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000884
Tim Peters07e99cb2001-01-14 23:47:14 +0000885 # Read literal direct from connection.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000886
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000887 size = int(self.mo.group('size'))
Tim Peters07e99cb2001-01-14 23:47:14 +0000888 if __debug__:
889 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000890 self._mesg('read literal size %s' % size)
Piers Lauder15e5d532001-07-20 10:52:06 +0000891 data = self.read(size)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000892
Tim Peters07e99cb2001-01-14 23:47:14 +0000893 # Store response with literal as tuple
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000894
Tim Peters07e99cb2001-01-14 23:47:14 +0000895 self._append_untagged(typ, (dat, data))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000896
Tim Peters07e99cb2001-01-14 23:47:14 +0000897 # Read trailer - possibly containing another literal
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000898
Tim Peters07e99cb2001-01-14 23:47:14 +0000899 dat = self._get_line()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000900
Tim Peters07e99cb2001-01-14 23:47:14 +0000901 self._append_untagged(typ, dat)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000902
Tim Peters07e99cb2001-01-14 23:47:14 +0000903 # Bracketed response information?
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000904
Tim Peters07e99cb2001-01-14 23:47:14 +0000905 if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
906 self._append_untagged(self.mo.group('type'), self.mo.group('data'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000907
Tim Peters07e99cb2001-01-14 23:47:14 +0000908 if __debug__:
909 if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
Piers Lauderf2d7d152002-02-22 01:15:17 +0000910 self._mesg('%s response: %s' % (typ, dat))
Guido van Rossum26367a01998-09-28 15:34:46 +0000911
Tim Peters07e99cb2001-01-14 23:47:14 +0000912 return resp
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000913
914
Tim Peters07e99cb2001-01-14 23:47:14 +0000915 def _get_tagged_response(self, tag):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000916
Tim Peters07e99cb2001-01-14 23:47:14 +0000917 while 1:
918 result = self.tagged_commands[tag]
919 if result is not None:
920 del self.tagged_commands[tag]
921 return result
Guido van Rossumf36b1822000-02-17 17:12:39 +0000922
Tim Peters07e99cb2001-01-14 23:47:14 +0000923 # Some have reported "unexpected response" exceptions.
924 # Note that ignoring them here causes loops.
925 # Instead, send me details of the unexpected response and
926 # I'll update the code in `_get_response()'.
Guido van Rossumf36b1822000-02-17 17:12:39 +0000927
Tim Peters07e99cb2001-01-14 23:47:14 +0000928 try:
929 self._get_response()
930 except self.abort, val:
931 if __debug__:
932 if self.debug >= 1:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000933 self.print_log()
Tim Peters07e99cb2001-01-14 23:47:14 +0000934 raise
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000935
936
Tim Peters07e99cb2001-01-14 23:47:14 +0000937 def _get_line(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000938
Piers Lauder15e5d532001-07-20 10:52:06 +0000939 line = self.readline()
Tim Peters07e99cb2001-01-14 23:47:14 +0000940 if not line:
941 raise self.abort('socket error: EOF')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000942
Tim Peters07e99cb2001-01-14 23:47:14 +0000943 # Protocol mandates all lines terminated by CRLF
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000944
Tim Peters07e99cb2001-01-14 23:47:14 +0000945 line = line[:-2]
946 if __debug__:
947 if self.debug >= 4:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000948 self._mesg('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000949 else:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000950 self._log('< %s' % line)
Tim Peters07e99cb2001-01-14 23:47:14 +0000951 return line
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000952
953
Tim Peters07e99cb2001-01-14 23:47:14 +0000954 def _match(self, cre, s):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000955
Tim Peters07e99cb2001-01-14 23:47:14 +0000956 # Run compiled regular expression match method on 's'.
957 # Save result, return success.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000958
Tim Peters07e99cb2001-01-14 23:47:14 +0000959 self.mo = cre.match(s)
960 if __debug__:
961 if self.mo is not None and self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +0000962 self._mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
Tim Peters07e99cb2001-01-14 23:47:14 +0000963 return self.mo is not None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000964
965
Tim Peters07e99cb2001-01-14 23:47:14 +0000966 def _new_tag(self):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000967
Tim Peters07e99cb2001-01-14 23:47:14 +0000968 tag = '%s%s' % (self.tagpre, self.tagnum)
969 self.tagnum = self.tagnum + 1
970 self.tagged_commands[tag] = None
971 return tag
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000972
973
Tim Peters07e99cb2001-01-14 23:47:14 +0000974 def _checkquote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000975
Tim Peters07e99cb2001-01-14 23:47:14 +0000976 # Must quote command args if non-alphanumeric chars present,
977 # and not already quoted.
Guido van Rossum8c062211999-12-13 23:27:45 +0000978
Tim Peters07e99cb2001-01-14 23:47:14 +0000979 if type(arg) is not type(''):
980 return arg
981 if (arg[0],arg[-1]) in (('(',')'),('"','"')):
982 return arg
983 if self.mustquote.search(arg) is None:
984 return arg
985 return self._quote(arg)
Guido van Rossum8c062211999-12-13 23:27:45 +0000986
987
Tim Peters07e99cb2001-01-14 23:47:14 +0000988 def _quote(self, arg):
Guido van Rossum8c062211999-12-13 23:27:45 +0000989
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +0000990 arg = arg.replace('\\', '\\\\')
991 arg = arg.replace('"', '\\"')
Guido van Rossum8c062211999-12-13 23:27:45 +0000992
Tim Peters07e99cb2001-01-14 23:47:14 +0000993 return '"%s"' % arg
Guido van Rossum8c062211999-12-13 23:27:45 +0000994
995
Tim Peters07e99cb2001-01-14 23:47:14 +0000996 def _simple_command(self, name, *args):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000997
Tim Peters07e99cb2001-01-14 23:47:14 +0000998 return self._command_complete(name, apply(self._command, (name,) + args))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +0000999
1000
Tim Peters07e99cb2001-01-14 23:47:14 +00001001 def _untagged_response(self, typ, dat, name):
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001002
Tim Peters07e99cb2001-01-14 23:47:14 +00001003 if typ == 'NO':
1004 return typ, dat
Raymond Hettinger54f02222002-06-01 14:18:47 +00001005 if not name in self.untagged_responses:
Tim Peters07e99cb2001-01-14 23:47:14 +00001006 return typ, [None]
Raymond Hettinger46ac8eb2002-06-30 03:39:14 +00001007 data = self.untagged_responses.pop(name)
Tim Peters07e99cb2001-01-14 23:47:14 +00001008 if __debug__:
1009 if self.debug >= 5:
Piers Lauderf2d7d152002-02-22 01:15:17 +00001010 self._mesg('untagged_responses[%s] => %s' % (name, data))
Tim Peters07e99cb2001-01-14 23:47:14 +00001011 return typ, data
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001012
1013
Piers Lauderf2d7d152002-02-22 01:15:17 +00001014 if __debug__:
1015
1016 def _mesg(self, s, secs=None):
1017 if secs is None:
1018 secs = time.time()
1019 tm = time.strftime('%M:%S', time.localtime(secs))
1020 sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
1021 sys.stderr.flush()
1022
1023 def _dump_ur(self, dict):
1024 # Dump untagged responses (in `dict').
1025 l = dict.items()
1026 if not l: return
1027 t = '\n\t\t'
1028 l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1029 self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1030
1031 def _log(self, line):
1032 # Keep log of last `_cmd_log_len' interactions for debugging.
1033 self._cmd_log[self._cmd_log_idx] = (line, time.time())
1034 self._cmd_log_idx += 1
1035 if self._cmd_log_idx >= self._cmd_log_len:
1036 self._cmd_log_idx = 0
1037
1038 def print_log(self):
1039 self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1040 i, n = self._cmd_log_idx, self._cmd_log_len
1041 while n:
1042 try:
1043 apply(self._mesg, self._cmd_log[i])
1044 except:
1045 pass
1046 i += 1
1047 if i >= self._cmd_log_len:
1048 i = 0
1049 n -= 1
1050
1051
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001052
Piers Laudera4f83132002-03-08 01:53:24 +00001053class IMAP4_SSL(IMAP4):
1054
1055 """IMAP4 client class over SSL connection
1056
Piers Lauder95f84952002-03-08 09:05:12 +00001057 Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
Piers Laudera4f83132002-03-08 01:53:24 +00001058
1059 host - host's name (default: localhost);
1060 port - port number (default: standard IMAP4 SSL port).
1061 keyfile - PEM formatted file that contains your private key (default: None);
1062 certfile - PEM formatted certificate chain file (default: None);
1063
1064 for more documentation see the docstring of the parent class IMAP4.
1065 """
1066
1067
1068 def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1069 self.keyfile = keyfile
1070 self.certfile = certfile
1071 IMAP4.__init__(self, host, port)
1072
1073
Piers Lauderf97b2d72002-06-05 22:31:57 +00001074 def open(self, host = '', port = IMAP4_SSL_PORT):
Piers Laudera4f83132002-03-08 01:53:24 +00001075 """Setup connection to remote server on "host:port".
Piers Lauderf97b2d72002-06-05 22:31:57 +00001076 (default: localhost:standard IMAP4 SSL port).
Piers Laudera4f83132002-03-08 01:53:24 +00001077 This connection will be used by the routines:
1078 read, readline, send, shutdown.
1079 """
Piers Lauderf97b2d72002-06-05 22:31:57 +00001080 self.host = host
1081 self.port = port
Piers Laudera4f83132002-03-08 01:53:24 +00001082 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Piers Lauderf97b2d72002-06-05 22:31:57 +00001083 self.sock.connect((host, port))
1084 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
Piers Laudera4f83132002-03-08 01:53:24 +00001085
1086
1087 def read(self, size):
1088 """Read 'size' bytes from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001089 # sslobj.read() sometimes returns < size bytes
1090 data = self.sslobj.read(size)
1091 while len(data) < size:
Tim Petersc411dba2002-07-16 21:35:23 +00001092 data += self.sslobj.read(size-len(data))
Piers Lauder0c092932002-06-23 10:47:13 +00001093
1094 return data
Piers Laudera4f83132002-03-08 01:53:24 +00001095
1096
1097 def readline(self):
1098 """Read line from remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001099 # NB: socket.ssl needs a "readline" method, or perhaps a "makefile" method.
Piers Laudera4f83132002-03-08 01:53:24 +00001100 line = ""
1101 while 1:
1102 char = self.sslobj.read(1)
1103 line += char
1104 if char == "\n": return line
1105
1106
1107 def send(self, data):
1108 """Send data to remote."""
Piers Lauder0c092932002-06-23 10:47:13 +00001109 # NB: socket.ssl needs a "sendall" method to match socket objects.
1110 bytes = len(data)
1111 while bytes > 0:
1112 sent = self.sslobj.write(data)
1113 if sent == bytes:
1114 break # avoid copy
1115 data = data[sent:]
1116 bytes = bytes - sent
Piers Laudera4f83132002-03-08 01:53:24 +00001117
1118
1119 def shutdown(self):
1120 """Close I/O established in "open"."""
1121 self.sock.close()
1122
1123
1124 def socket(self):
1125 """Return socket instance used to connect to IMAP4 server.
1126
1127 socket = <instance>.socket()
1128 """
1129 return self.sock
1130
1131
1132 def ssl(self):
1133 """Return SSLObject instance used to communicate with the IMAP4 server.
1134
1135 ssl = <instance>.socket.ssl()
1136 """
1137 return self.sslobj
1138
1139
1140
Piers Laudere0273de2002-11-22 05:53:04 +00001141class IMAP4_stream(IMAP4):
1142
1143 """IMAP4 client class over a stream
1144
1145 Instantiate with: IMAP4_stream(command)
1146
1147 where "command" is a string that can be passed to os.popen2()
1148
1149 for more documentation see the docstring of the parent class IMAP4.
1150 """
1151
1152
1153 def __init__(self, command):
1154 self.command = command
1155 IMAP4.__init__(self)
1156
1157
1158 def open(self, host = None, port = None):
1159 """Setup a stream connection.
1160 This connection will be used by the routines:
1161 read, readline, send, shutdown.
1162 """
1163 self.host = None # For compatibility with parent class
1164 self.port = None
1165 self.sock = None
1166 self.file = None
1167 self.writefile, self.readfile = os.popen2(self.command)
1168
1169
1170 def read(self, size):
1171 """Read 'size' bytes from remote."""
1172 return self.readfile.read(size)
1173
1174
1175 def readline(self):
1176 """Read line from remote."""
1177 return self.readfile.readline()
1178
1179
1180 def send(self, data):
1181 """Send data to remote."""
1182 self.writefile.write(data)
1183 self.writefile.flush()
1184
1185
1186 def shutdown(self):
1187 """Close I/O established in "open"."""
1188 self.readfile.close()
1189 self.writefile.close()
1190
1191
1192
Guido van Rossumeda960a1998-06-18 14:24:28 +00001193class _Authenticator:
1194
Tim Peters07e99cb2001-01-14 23:47:14 +00001195 """Private class to provide en/decoding
1196 for base64-based authentication conversation.
1197 """
Guido van Rossumeda960a1998-06-18 14:24:28 +00001198
Tim Peters07e99cb2001-01-14 23:47:14 +00001199 def __init__(self, mechinst):
1200 self.mech = mechinst # Callable object to provide/process data
Guido van Rossumeda960a1998-06-18 14:24:28 +00001201
Tim Peters07e99cb2001-01-14 23:47:14 +00001202 def process(self, data):
1203 ret = self.mech(self.decode(data))
1204 if ret is None:
1205 return '*' # Abort conversation
1206 return self.encode(ret)
Guido van Rossumeda960a1998-06-18 14:24:28 +00001207
Tim Peters07e99cb2001-01-14 23:47:14 +00001208 def encode(self, inp):
1209 #
1210 # Invoke binascii.b2a_base64 iteratively with
1211 # short even length buffers, strip the trailing
1212 # line feed from the result and append. "Even"
1213 # means a number that factors to both 6 and 8,
1214 # so when it gets to the end of the 8-bit input
1215 # there's no partial 6-bit output.
1216 #
1217 oup = ''
1218 while inp:
1219 if len(inp) > 48:
1220 t = inp[:48]
1221 inp = inp[48:]
1222 else:
1223 t = inp
1224 inp = ''
1225 e = binascii.b2a_base64(t)
1226 if e:
1227 oup = oup + e[:-1]
1228 return oup
1229
1230 def decode(self, inp):
1231 if not inp:
1232 return ''
1233 return binascii.a2b_base64(inp)
1234
Guido van Rossumeda960a1998-06-18 14:24:28 +00001235
1236
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001237Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
Tim Peters07e99cb2001-01-14 23:47:14 +00001238 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001239
1240def Internaldate2tuple(resp):
Tim Peters07e99cb2001-01-14 23:47:14 +00001241 """Convert IMAP4 INTERNALDATE to UT.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001242
Tim Peters07e99cb2001-01-14 23:47:14 +00001243 Returns Python time module tuple.
1244 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001245
Tim Peters07e99cb2001-01-14 23:47:14 +00001246 mo = InternalDate.match(resp)
1247 if not mo:
1248 return None
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001249
Tim Peters07e99cb2001-01-14 23:47:14 +00001250 mon = Mon2num[mo.group('mon')]
1251 zonen = mo.group('zonen')
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001252
Jeremy Hyltonf5d3ea02001-02-22 13:24:27 +00001253 day = int(mo.group('day'))
1254 year = int(mo.group('year'))
1255 hour = int(mo.group('hour'))
1256 min = int(mo.group('min'))
1257 sec = int(mo.group('sec'))
1258 zoneh = int(mo.group('zoneh'))
1259 zonem = int(mo.group('zonem'))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001260
Tim Peters07e99cb2001-01-14 23:47:14 +00001261 # INTERNALDATE timezone must be subtracted to get UT
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001262
Tim Peters07e99cb2001-01-14 23:47:14 +00001263 zone = (zoneh*60 + zonem)*60
1264 if zonen == '-':
1265 zone = -zone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001266
Tim Peters07e99cb2001-01-14 23:47:14 +00001267 tt = (year, mon, day, hour, min, sec, -1, -1, -1)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001268
Tim Peters07e99cb2001-01-14 23:47:14 +00001269 utc = time.mktime(tt)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001270
Tim Peters07e99cb2001-01-14 23:47:14 +00001271 # Following is necessary because the time module has no 'mkgmtime'.
1272 # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001273
Tim Peters07e99cb2001-01-14 23:47:14 +00001274 lt = time.localtime(utc)
1275 if time.daylight and lt[-1]:
1276 zone = zone + time.altzone
1277 else:
1278 zone = zone + time.timezone
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001279
Tim Peters07e99cb2001-01-14 23:47:14 +00001280 return time.localtime(utc - zone)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001281
1282
1283
1284def Int2AP(num):
1285
Tim Peters07e99cb2001-01-14 23:47:14 +00001286 """Convert integer to A-P string representation."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001287
Tim Peters07e99cb2001-01-14 23:47:14 +00001288 val = ''; AP = 'ABCDEFGHIJKLMNOP'
1289 num = int(abs(num))
1290 while num:
1291 num, mod = divmod(num, 16)
1292 val = AP[mod] + val
1293 return val
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001294
1295
1296
1297def ParseFlags(resp):
1298
Tim Peters07e99cb2001-01-14 23:47:14 +00001299 """Convert IMAP4 flags response to python tuple."""
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001300
Tim Peters07e99cb2001-01-14 23:47:14 +00001301 mo = Flags.match(resp)
1302 if not mo:
1303 return ()
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001304
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001305 return tuple(mo.group('flags').split())
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001306
1307
1308def Time2Internaldate(date_time):
1309
Tim Peters07e99cb2001-01-14 23:47:14 +00001310 """Convert 'date_time' to IMAP4 INTERNALDATE representation.
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001311
Tim Peters07e99cb2001-01-14 23:47:14 +00001312 Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1313 """
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001314
Fred Drakedb519202002-01-05 17:17:09 +00001315 if isinstance(date_time, (int, float)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001316 tt = time.localtime(date_time)
Fred Drakedb519202002-01-05 17:17:09 +00001317 elif isinstance(date_time, (tuple, time.struct_time)):
Tim Peters07e99cb2001-01-14 23:47:14 +00001318 tt = date_time
Piers Lauder3fca2912002-06-17 07:07:20 +00001319 elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
Tim Peters07e99cb2001-01-14 23:47:14 +00001320 return date_time # Assume in correct format
Fred Drakedb519202002-01-05 17:17:09 +00001321 else:
1322 raise ValueError("date_time not of a known type")
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001323
Tim Peters07e99cb2001-01-14 23:47:14 +00001324 dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1325 if dt[0] == '0':
1326 dt = ' ' + dt[1:]
1327 if time.daylight and tt[-1]:
1328 zone = -time.altzone
1329 else:
1330 zone = -time.timezone
Martin v. Löwisea752fb2002-01-05 11:31:49 +00001331 return '"' + dt + " %+03d%02d" % divmod(zone/60, 60) + '"'
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001332
1333
1334
Guido van Rossum8c062211999-12-13 23:27:45 +00001335if __name__ == '__main__':
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001336
Piers Laudere0273de2002-11-22 05:53:04 +00001337 # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1338 # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1339 # to test the IMAP4_stream class
1340
Guido van Rossuma79bbac2001-08-13 15:34:41 +00001341 import getopt, getpass
Guido van Rossumd6596931998-05-29 18:08:48 +00001342
Tim Peters07e99cb2001-01-14 23:47:14 +00001343 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001344 optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
Tim Peters07e99cb2001-01-14 23:47:14 +00001345 except getopt.error, val:
Piers Laudere0273de2002-11-22 05:53:04 +00001346 optlist, args = (), ()
Guido van Rossum66d45132000-03-28 20:20:53 +00001347
Piers Laudere0273de2002-11-22 05:53:04 +00001348 stream_command = None
Tim Peters07e99cb2001-01-14 23:47:14 +00001349 for opt,val in optlist:
1350 if opt == '-d':
1351 Debug = int(val)
Piers Laudere0273de2002-11-22 05:53:04 +00001352 elif opt == '-s':
1353 stream_command = val
1354 if not args: args = (stream_command,)
Guido van Rossum66d45132000-03-28 20:20:53 +00001355
Tim Peters07e99cb2001-01-14 23:47:14 +00001356 if not args: args = ('',)
Guido van Rossum66d45132000-03-28 20:20:53 +00001357
Tim Peters07e99cb2001-01-14 23:47:14 +00001358 host = args[0]
Guido van Rossumb1f08121998-06-25 02:22:16 +00001359
Tim Peters07e99cb2001-01-14 23:47:14 +00001360 USER = getpass.getuser()
Piers Lauder15e5d532001-07-20 10:52:06 +00001361 PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001362
Piers Laudere02f9042001-08-05 10:43:03 +00001363 test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)sdata...%(lf)s' % {'user':USER, 'lf':CRLF}
Tim Peters07e99cb2001-01-14 23:47:14 +00001364 test_seq1 = (
1365 ('login', (USER, PASSWD)),
1366 ('create', ('/tmp/xxx 1',)),
1367 ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1368 ('CREATE', ('/tmp/yyz 2',)),
1369 ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1370 ('list', ('/tmp', 'yy*')),
1371 ('select', ('/tmp/yyz 2',)),
1372 ('search', (None, 'SUBJECT', 'test')),
Piers Lauderf2d7d152002-02-22 01:15:17 +00001373 ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
Tim Peters07e99cb2001-01-14 23:47:14 +00001374 ('store', ('1', 'FLAGS', '(\Deleted)')),
Piers Lauder15e5d532001-07-20 10:52:06 +00001375 ('namespace', ()),
Tim Peters07e99cb2001-01-14 23:47:14 +00001376 ('expunge', ()),
1377 ('recent', ()),
1378 ('close', ()),
1379 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001380
Tim Peters07e99cb2001-01-14 23:47:14 +00001381 test_seq2 = (
1382 ('select', ()),
1383 ('response',('UIDVALIDITY',)),
1384 ('uid', ('SEARCH', 'ALL')),
1385 ('response', ('EXISTS',)),
1386 ('append', (None, None, None, test_mesg)),
1387 ('recent', ()),
1388 ('logout', ()),
1389 )
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001390
Tim Peters07e99cb2001-01-14 23:47:14 +00001391 def run(cmd, args):
Piers Lauderf2d7d152002-02-22 01:15:17 +00001392 M._mesg('%s %s' % (cmd, args))
Piers Lauder15e5d532001-07-20 10:52:06 +00001393 typ, dat = apply(getattr(M, cmd), args)
Piers Lauderf2d7d152002-02-22 01:15:17 +00001394 M._mesg('%s => %s %s' % (cmd, typ, dat))
Piers Laudere0273de2002-11-22 05:53:04 +00001395 if typ == 'NO': raise dat[0]
Tim Peters07e99cb2001-01-14 23:47:14 +00001396 return dat
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001397
Tim Peters07e99cb2001-01-14 23:47:14 +00001398 try:
Piers Laudere0273de2002-11-22 05:53:04 +00001399 if stream_command:
1400 M = IMAP4_stream(stream_command)
1401 else:
1402 M = IMAP4(host)
1403 if M.state == 'AUTH':
Tim Peters77c06fb2002-11-24 02:35:35 +00001404 test_seq1 = test_seq1[1:] # Login not needed
Piers Lauderf2d7d152002-02-22 01:15:17 +00001405 M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1406 M._mesg('CAPABILITIES = %s' % `M.capabilities`)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001407
Tim Peters07e99cb2001-01-14 23:47:14 +00001408 for cmd,args in test_seq1:
1409 run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001410
Tim Peters07e99cb2001-01-14 23:47:14 +00001411 for ml in run('list', ('/tmp/', 'yy%')):
1412 mo = re.match(r'.*"([^"]+)"$', ml)
1413 if mo: path = mo.group(1)
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001414 else: path = ml.split()[-1]
Tim Peters07e99cb2001-01-14 23:47:14 +00001415 run('delete', (path,))
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001416
Tim Peters07e99cb2001-01-14 23:47:14 +00001417 for cmd,args in test_seq2:
1418 dat = run(cmd, args)
Guido van Rossumc2c07fa1998-04-09 13:51:46 +00001419
Tim Peters07e99cb2001-01-14 23:47:14 +00001420 if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1421 continue
Guido van Rossum38d8f4e1998-04-11 01:22:34 +00001422
Eric S. Raymond25a0cbc2001-02-09 06:50:21 +00001423 uid = dat[-1].split()
Tim Peters07e99cb2001-01-14 23:47:14 +00001424 if not uid: continue
1425 run('uid', ('FETCH', '%s' % uid[-1],
1426 '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
Guido van Rossum66d45132000-03-28 20:20:53 +00001427
Tim Peters07e99cb2001-01-14 23:47:14 +00001428 print '\nAll tests OK.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001429
Tim Peters07e99cb2001-01-14 23:47:14 +00001430 except:
1431 print '\nTests failed.'
Guido van Rossum66d45132000-03-28 20:20:53 +00001432
Tim Peters07e99cb2001-01-14 23:47:14 +00001433 if not Debug:
1434 print '''
Guido van Rossum66d45132000-03-28 20:20:53 +00001435If you would like to see debugging output,
1436try: %s -d5
1437''' % sys.argv[0]
1438
Tim Peters07e99cb2001-01-14 23:47:14 +00001439 raise