| 
									
										
										
										
											1998-06-25 02:22:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | """IMAP4 client.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Based on RFC 2060. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Author: Piers Lauder <piers@cs.su.oz.au> December 1997. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | Public class:		IMAP4 | 
					
						
							|  |  |  | Public variable:	Debug | 
					
						
							|  |  |  | Public functions:	Internaldate2tuple | 
					
						
							|  |  |  | 			Int2AP | 
					
						
							|  |  |  | 			ParseFlags | 
					
						
							|  |  |  | 			Time2Internaldate | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											1998-06-25 02:22:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | __version__ = "2.15" | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | import binascii, re, socket, string, time, random, sys | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #	Globals | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CRLF = '\r\n' | 
					
						
							|  |  |  | Debug = 0 | 
					
						
							|  |  |  | IMAP4_PORT = 143 | 
					
						
							| 
									
										
										
										
											1998-04-11 01:22:34 +00:00
										 |  |  | AllowedVersions = ('IMAP4REV1', 'IMAP4')	# Most recent first | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #	Commands | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Commands = { | 
					
						
							|  |  |  | 	# name		  valid states | 
					
						
							|  |  |  | 	'APPEND':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'AUTHENTICATE':	('NONAUTH',), | 
					
						
							|  |  |  | 	'CAPABILITY':	('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), | 
					
						
							|  |  |  | 	'CHECK':	('SELECTED',), | 
					
						
							|  |  |  | 	'CLOSE':	('SELECTED',), | 
					
						
							|  |  |  | 	'COPY':		('SELECTED',), | 
					
						
							|  |  |  | 	'CREATE':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'DELETE':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'EXAMINE':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'EXPUNGE':	('SELECTED',), | 
					
						
							|  |  |  | 	'FETCH':	('SELECTED',), | 
					
						
							|  |  |  | 	'LIST':		('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'LOGIN':	('NONAUTH',), | 
					
						
							|  |  |  | 	'LOGOUT':	('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), | 
					
						
							|  |  |  | 	'LSUB':		('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'NOOP':		('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 	'PARTIAL':	('SELECTED',), | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 	'RENAME':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'SEARCH':	('SELECTED',), | 
					
						
							|  |  |  | 	'SELECT':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'STATUS':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'STORE':	('SELECTED',), | 
					
						
							|  |  |  | 	'SUBSCRIBE':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	'UID':		('SELECTED',), | 
					
						
							|  |  |  | 	'UNSUBSCRIBE':	('AUTH', 'SELECTED'), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #	Patterns to match server responses | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | Continuation = re.compile(r'\+( (?P<data>.*))?') | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)') | 
					
						
							|  |  |  | InternalDate = re.compile(r'.*INTERNALDATE "' | 
					
						
							|  |  |  | 	r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])' | 
					
						
							|  |  |  | 	r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])' | 
					
						
							|  |  |  | 	r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])' | 
					
						
							|  |  |  | 	r'"') | 
					
						
							|  |  |  | Literal = re.compile(r'(?P<data>.*) {(?P<size>\d+)}$') | 
					
						
							|  |  |  | Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]') | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?') | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class IMAP4: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"""IMAP4 client class.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Instantiate with: IMAP4([host[, port]]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		host - host's name (default: localhost); | 
					
						
							|  |  |  | 		port - port number (default: standard IMAP4 port). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	All IMAP4rev1 commands are supported by methods of the same | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 	name (in lower-case). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	All arguments to commands are converted to strings, except for | 
					
						
							| 
									
										
										
										
											1998-06-25 02:22:16 +00:00
										 |  |  | 	AUTHENTICATE, and the last argument to APPEND which is passed as | 
					
						
							|  |  |  | 	an IMAP4 literal.  If necessary (the string contains | 
					
						
							|  |  |  | 	white-space and isn't enclosed with either parentheses or | 
					
						
							|  |  |  | 	double quotes) each string is quoted. | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	Each command returns a tuple: (type, [data, ...]) where 'type' | 
					
						
							|  |  |  | 	is usually 'OK' or 'NO', and 'data' is either the text from the | 
					
						
							|  |  |  | 	tagged response, or untagged results from command. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	Errors raise the exception class <instance>.error("<reason>"). | 
					
						
							|  |  |  | 	IMAP4 server errors raise <instance>.abort("<reason>"), | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 	which is a sub-class of 'error'. Mailbox status changes | 
					
						
							|  |  |  | 	from READ-WRITE to READ-ONLY raise the exception class | 
					
						
							|  |  |  | 	<instance>.readonly("<reason>"), which is a sub-class of 'abort'. | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	Note: to use this module, you must read the RFCs pertaining | 
					
						
							|  |  |  | 	to the IMAP4 protocol, as the semantics of the arguments to | 
					
						
							|  |  |  | 	each IMAP4 command are left to the invoker, not to mention | 
					
						
							|  |  |  | 	the results. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 	"""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	class error(Exception): pass	# Logical errors - debug required | 
					
						
							|  |  |  | 	class abort(error): pass	# Service errors - close and retry | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 	class readonly(abort): pass	# Mailbox status changed to READ-ONLY | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def __init__(self, host = '', port = IMAP4_PORT): | 
					
						
							|  |  |  | 		self.host = host | 
					
						
							|  |  |  | 		self.port = port | 
					
						
							|  |  |  | 		self.debug = Debug | 
					
						
							|  |  |  | 		self.state = 'LOGOUT' | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 		self.literal = None		# A literal argument to a command | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		self.tagged_commands = {}	# Tagged commands awaiting response | 
					
						
							|  |  |  | 		self.untagged_responses = {}	# {typ: [data, ...], ...} | 
					
						
							|  |  |  | 		self.continuation_response = ''	# Last continuation response | 
					
						
							|  |  |  | 		self.tagnum = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		# Open socket to server. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		self.open(host, port) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		# Create unique tag for this session, | 
					
						
							|  |  |  | 		# and compile tagged response matcher. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 		self.tagpre = Int2AP(random.randint(0, 31999)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		self.tagre = re.compile(r'(?P<tag>' | 
					
						
							|  |  |  | 				+ self.tagpre | 
					
						
							|  |  |  | 				+ r'\d+) (?P<type>[A-Z]+) (?P<data>.*)') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		# Get server welcome message, | 
					
						
							|  |  |  | 		# request and store CAPABILITY response. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if __debug__ and self.debug >= 1: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			_mesg('new IMAP4 connection, tag=%s' % self.tagpre) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		self.welcome = self._get_response() | 
					
						
							|  |  |  | 		if self.untagged_responses.has_key('PREAUTH'): | 
					
						
							|  |  |  | 			self.state = 'AUTH' | 
					
						
							|  |  |  | 		elif self.untagged_responses.has_key('OK'): | 
					
						
							|  |  |  | 			self.state = 'NONAUTH' | 
					
						
							|  |  |  | #		elif self.untagged_responses.has_key('BYE'): | 
					
						
							|  |  |  | 		else: | 
					
						
							|  |  |  | 			raise self.error(self.welcome) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		cap = 'CAPABILITY' | 
					
						
							|  |  |  | 		self._simple_command(cap) | 
					
						
							|  |  |  | 		if not self.untagged_responses.has_key(cap): | 
					
						
							|  |  |  | 			raise self.error('no CAPABILITY response from server') | 
					
						
							| 
									
										
										
										
											1998-10-21 22:06:56 +00:00
										 |  |  | 		self.capabilities = tuple(string.split(string.upper(self.untagged_responses[cap][-1]))) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if __debug__ and self.debug >= 3: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			_mesg('CAPABILITIES: %s' % `self.capabilities`) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-11 01:22:34 +00:00
										 |  |  | 		for version in AllowedVersions: | 
					
						
							|  |  |  | 			if not version in self.capabilities: | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			self.PROTOCOL_VERSION = version | 
					
						
							| 
									
										
										
										
											1998-06-25 02:22:16 +00:00
										 |  |  | 			return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		raise self.error('server not IMAP4 compliant') | 
					
						
							| 
									
										
										
										
											1998-04-11 01:22:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 	def __getattr__(self, attr): | 
					
						
							|  |  |  | 		#	Allow UPPERCASE variants of IMAP4 command methods. | 
					
						
							|  |  |  | 		if Commands.has_key(attr): | 
					
						
							|  |  |  | 			return eval("self.%s" % string.lower(attr)) | 
					
						
							|  |  |  | 		raise AttributeError("Unknown IMAP4 command: '%s'" % attr) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	#	Public methods | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 	def open(self, host, port): | 
					
						
							|  |  |  | 		"""Setup 'self.sock' and 'self.file'.""" | 
					
						
							|  |  |  | 		self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  | 		self.sock.connect(self.host, self.port) | 
					
						
							|  |  |  | 		self.file = self.sock.makefile('r') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 	def recent(self): | 
					
						
							|  |  |  | 		"""Return most recent 'RECENT' responses if any exist,
 | 
					
						
							|  |  |  | 		else prompt server for an update using the 'NOOP' command. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		(typ, [data]) = <instance>.recent() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'data' is None if no new messages, | 
					
						
							|  |  |  | 		else list of RECENT responses, most recent last. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'RECENT' | 
					
						
							|  |  |  | 		typ, dat = self._untagged_response('OK', [None], name) | 
					
						
							|  |  |  | 		if dat[-1]: | 
					
						
							|  |  |  | 			return typ, dat | 
					
						
							|  |  |  | 		typ, dat = self.noop()	# Prod server for response | 
					
						
							|  |  |  | 		return self._untagged_response(typ, dat, name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def response(self, code): | 
					
						
							|  |  |  | 		"""Return data for response 'code' if received, or None.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Old value for response 'code' is cleared. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(code, [data]) = <instance>.response(code) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self._untagged_response(code, [None], string.upper(code)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def socket(self): | 
					
						
							|  |  |  | 		"""Return socket instance used to connect to IMAP4 server.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		socket = <instance>.socket() | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self.sock | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	#	IMAP4 commands | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def append(self, mailbox, flags, date_time, message): | 
					
						
							|  |  |  | 		"""Append message to named mailbox.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.append(mailbox, flags, date_time, message) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'APPEND' | 
					
						
							|  |  |  | 		if flags: | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			if (flags[0],flags[-1]) != ('(',')'): | 
					
						
							|  |  |  | 				flags = '(%s)' % flags | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		else: | 
					
						
							|  |  |  | 			flags = None | 
					
						
							|  |  |  | 		if date_time: | 
					
						
							|  |  |  | 			date_time = Time2Internaldate(date_time) | 
					
						
							|  |  |  | 		else: | 
					
						
							|  |  |  | 			date_time = None | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 		self.literal = message | 
					
						
							|  |  |  | 		return self._simple_command(name, mailbox, flags, date_time) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 	def authenticate(self, mechanism, authobject): | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		"""Authenticate command - requires response processing.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		'mechanism' specifies which authentication mechanism is to | 
					
						
							|  |  |  | 		be used - it must appear in <instance>.capabilities in the | 
					
						
							|  |  |  | 		form AUTH=<mechanism>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'authobject' must be a callable object: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			data = authobject(response) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		It will be called to process server continuation responses. | 
					
						
							|  |  |  | 		It should return data that will be encoded and sent to server. | 
					
						
							|  |  |  | 		It should return None if the client abort response '*' should | 
					
						
							|  |  |  | 		be sent instead. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		"""
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		mech = string.upper(mechanism) | 
					
						
							|  |  |  | 		cap = 'AUTH=%s' % mech | 
					
						
							|  |  |  | 		if not cap in self.capabilities: | 
					
						
							|  |  |  | 			raise self.error("Server doesn't allow %s authentication." % mech) | 
					
						
							|  |  |  | 		self.literal = _Authenticator(authobject).process | 
					
						
							|  |  |  | 		typ, dat = self._simple_command('AUTHENTICATE', mech) | 
					
						
							|  |  |  | 		if typ != 'OK': | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			raise self.error(dat[-1]) | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		self.state = 'AUTH' | 
					
						
							|  |  |  | 		return typ, dat | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def check(self): | 
					
						
							|  |  |  | 		"""Checkpoint mailbox on server.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.check() | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self._simple_command('CHECK') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def close(self): | 
					
						
							|  |  |  | 		"""Close currently selected mailbox.
 | 
					
						
							| 
									
										
										
										
											1998-04-09 14:20:31 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		Deleted messages are removed from writable mailbox. | 
					
						
							|  |  |  | 		This is the recommended command before 'LOGOUT'. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.close() | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		try: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			typ, dat = self._simple_command('CLOSE') | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		finally: | 
					
						
							|  |  |  | 			self.state = 'AUTH' | 
					
						
							|  |  |  | 		return typ, dat | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def copy(self, message_set, new_mailbox): | 
					
						
							|  |  |  | 		"""Copy 'message_set' messages onto end of 'new_mailbox'.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.copy(message_set, new_mailbox) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self._simple_command('COPY', message_set, new_mailbox) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def create(self, mailbox): | 
					
						
							|  |  |  | 		"""Create new mailbox.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.create(mailbox) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self._simple_command('CREATE', mailbox) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def delete(self, mailbox): | 
					
						
							|  |  |  | 		"""Delete old mailbox.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.delete(mailbox) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self._simple_command('DELETE', mailbox) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def expunge(self): | 
					
						
							|  |  |  | 		"""Permanently remove deleted items from selected mailbox.
 | 
					
						
							| 
									
										
										
										
											1998-04-09 14:20:31 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		Generates 'EXPUNGE' response for each deleted message. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.expunge() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'data' is list of 'EXPUNGE'd message numbers in order received. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'EXPUNGE' | 
					
						
							|  |  |  | 		typ, dat = self._simple_command(name) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, name) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def fetch(self, message_set, message_parts): | 
					
						
							|  |  |  | 		"""Fetch (parts of) messages.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data, ...]) = <instance>.fetch(message_set, message_parts) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'data' are tuples of message part envelope and data. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'FETCH' | 
					
						
							|  |  |  | 		typ, dat = self._simple_command(name, message_set, message_parts) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, name) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def list(self, directory='""', pattern='*'): | 
					
						
							|  |  |  | 		"""List mailbox names in directory matching pattern.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.list(directory='""', pattern='*') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'data' is list of LIST responses. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'LIST' | 
					
						
							|  |  |  | 		typ, dat = self._simple_command(name, directory, pattern) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, name) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def login(self, user, password): | 
					
						
							|  |  |  | 		"""Identify client using plaintext password.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.list(user, password) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		typ, dat = self._simple_command('LOGIN', user, password) | 
					
						
							|  |  |  | 		if typ != 'OK': | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			raise self.error(dat[-1]) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		self.state = 'AUTH' | 
					
						
							|  |  |  | 		return typ, dat | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def logout(self): | 
					
						
							|  |  |  | 		"""Shutdown connection to server.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.logout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Returns server 'BYE' response. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		self.state = 'LOGOUT' | 
					
						
							|  |  |  | 		try: typ, dat = self._simple_command('LOGOUT') | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]] | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		self.file.close() | 
					
						
							|  |  |  | 		self.sock.close() | 
					
						
							|  |  |  | 		if self.untagged_responses.has_key('BYE'): | 
					
						
							|  |  |  | 			return 'BYE', self.untagged_responses['BYE'] | 
					
						
							|  |  |  | 		return typ, dat | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def lsub(self, directory='""', pattern='*'): | 
					
						
							|  |  |  | 		"""List 'subscribed' mailbox names in directory matching pattern.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'data' are tuples of message part envelope and data. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'LSUB' | 
					
						
							|  |  |  | 		typ, dat = self._simple_command(name, directory, pattern) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, name) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 	def noop(self): | 
					
						
							|  |  |  | 		"""Send NOOP command.
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 		(typ, data) = <instance>.noop() | 
					
						
							|  |  |  | 		"""
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		if __debug__ and self.debug >= 3: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			_dump_ur(self.untagged_responses) | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 		return self._simple_command('NOOP') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 	def partial(self, message_num, message_part, start, length): | 
					
						
							|  |  |  | 		"""Fetch truncated part of a message.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'data' is tuple of message part envelope and data. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'PARTIAL' | 
					
						
							|  |  |  | 		typ, dat = self._simple_command(name, message_num, message_part, start, length) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, 'FETCH') | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def rename(self, oldmailbox, newmailbox): | 
					
						
							|  |  |  | 		"""Rename old mailbox name to new.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, data) = <instance>.rename(oldmailbox, newmailbox) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self._simple_command('RENAME', oldmailbox, newmailbox) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def search(self, charset, criteria): | 
					
						
							|  |  |  | 		"""Search mailbox for matching messages.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.search(charset, criteria) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'data' is space separated list of matching message numbers. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'SEARCH' | 
					
						
							|  |  |  | 		if charset: | 
					
						
							|  |  |  | 			charset = 'CHARSET ' + charset | 
					
						
							|  |  |  | 		typ, dat = self._simple_command(name, charset, criteria) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, name) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def select(self, mailbox='INBOX', readonly=None): | 
					
						
							|  |  |  | 		"""Select a mailbox.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		Flush all untagged responses. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		(typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'data' is count of messages in mailbox ('EXISTS' response). | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		# Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY') | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		self.untagged_responses = {}	# Flush old responses. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		if readonly: | 
					
						
							|  |  |  | 			name = 'EXAMINE' | 
					
						
							|  |  |  | 		else: | 
					
						
							|  |  |  | 			name = 'SELECT' | 
					
						
							|  |  |  | 		typ, dat = self._simple_command(name, mailbox) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		if typ != 'OK': | 
					
						
							|  |  |  | 			self.state = 'AUTH'	# Might have been 'SELECTED' | 
					
						
							|  |  |  | 			return typ, dat | 
					
						
							|  |  |  | 		self.state = 'SELECTED' | 
					
						
							|  |  |  | 		if not self.untagged_responses.has_key('READ-WRITE') \ | 
					
						
							|  |  |  | 			and not readonly: | 
					
						
							|  |  |  | 			if __debug__ and self.debug >= 1: _dump_ur(self.untagged_responses) | 
					
						
							|  |  |  | 			raise self.readonly('%s is not writable' % mailbox) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		return typ, self.untagged_responses.get('EXISTS', [None]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def status(self, mailbox, names): | 
					
						
							|  |  |  | 		"""Request named status conditions for mailbox.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.status(mailbox, names) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		name = 'STATUS' | 
					
						
							| 
									
										
										
										
											1998-04-11 03:11:51 +00:00
										 |  |  | 		if self.PROTOCOL_VERSION == 'IMAP4': | 
					
						
							|  |  |  | 			raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		typ, dat = self._simple_command(name, mailbox, names) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, name) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def store(self, message_set, command, flag_list): | 
					
						
							|  |  |  | 		"""Alters flag dispositions for messages in mailbox.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.store(message_set, command, flag_list) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		typ, dat = self._simple_command('STORE', message_set, command, flag_list) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, 'FETCH') | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def subscribe(self, mailbox): | 
					
						
							|  |  |  | 		"""Subscribe to new mailbox.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.subscribe(mailbox) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self._simple_command('SUBSCRIBE', mailbox) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 	def uid(self, command, *args): | 
					
						
							|  |  |  | 		"""Execute "command arg ..." with messages identified by UID,
 | 
					
						
							| 
									
										
										
										
											1998-04-11 01:22:34 +00:00
										 |  |  | 			rather than message number. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		(typ, [data]) = <instance>.uid(command, arg1, arg2, ...) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		Returns response appropriate to 'command'. | 
					
						
							|  |  |  | 		"""
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		command = string.upper(command) | 
					
						
							|  |  |  | 		if not Commands.has_key(command): | 
					
						
							|  |  |  | 			raise self.error("Unknown IMAP4 UID command: %s" % command) | 
					
						
							|  |  |  | 		if self.state not in Commands[command]: | 
					
						
							|  |  |  | 			raise self.error('command %s illegal in state %s' | 
					
						
							|  |  |  | 						% (command, self.state)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		name = 'UID' | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		typ, dat = apply(self._simple_command, (name, command) + args) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		if command == 'SEARCH': | 
					
						
							|  |  |  | 			name = 'SEARCH' | 
					
						
							|  |  |  | 		else: | 
					
						
							|  |  |  | 			name = 'FETCH' | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		return self._untagged_response(typ, dat, name) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def unsubscribe(self, mailbox): | 
					
						
							|  |  |  | 		"""Unsubscribe from old mailbox.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		(typ, [data]) = <instance>.unsubscribe(mailbox) | 
					
						
							|  |  |  | 		"""
 | 
					
						
							|  |  |  | 		return self._simple_command('UNSUBSCRIBE', mailbox) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 	def xatom(self, name, *args): | 
					
						
							| 
									
										
										
										
											1998-04-11 01:22:34 +00:00
										 |  |  | 		"""Allow simple extension commands
 | 
					
						
							|  |  |  | 			notified by server in CAPABILITY response. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		(typ, [data]) = <instance>.xatom(name, arg, ...) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		"""
 | 
					
						
							|  |  |  | 		if name[0] != 'X' or not name in self.capabilities: | 
					
						
							|  |  |  | 			raise self.error('unknown extension command: %s' % name) | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		return apply(self._simple_command, (name,) + args) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	#	Private methods | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def _append_untagged(self, typ, dat): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		ur = self.untagged_responses | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		if __debug__ and self.debug >= 5: | 
					
						
							|  |  |  | 			_mesg('untagged_responses[%s] %s += %s' % | 
					
						
							|  |  |  | 				(typ, len(ur.get(typ,'')), dat)) | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		if ur.has_key(typ): | 
					
						
							|  |  |  | 			ur[typ].append(dat) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		else: | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			ur[typ] = [dat] | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 	def _command(self, name, *args): | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if self.state not in Commands[name]: | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 			self.literal = None | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 			raise self.error( | 
					
						
							|  |  |  | 			'command %s illegal in state %s' % (name, self.state)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		for typ in ('OK', 'NO', 'BAD'): | 
					
						
							|  |  |  | 			if self.untagged_responses.has_key(typ): | 
					
						
							|  |  |  | 				del self.untagged_responses[typ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if self.untagged_responses.has_key('READ-WRITE') \ | 
					
						
							|  |  |  | 		and self.untagged_responses.has_key('READ-ONLY'): | 
					
						
							|  |  |  | 			del self.untagged_responses['READ-WRITE'] | 
					
						
							|  |  |  | 			raise self.readonly('mailbox status changed to READ-ONLY') | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		tag = self._new_tag() | 
					
						
							|  |  |  | 		data = '%s %s' % (tag, name) | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 		for d in args: | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 			if d is None: continue | 
					
						
							|  |  |  | 			if type(d) is type(''): | 
					
						
							|  |  |  | 				l = len(string.split(d)) | 
					
						
							|  |  |  | 			else: | 
					
						
							|  |  |  | 				l = 1 | 
					
						
							|  |  |  | 			if l == 0 or l > 1 and (d[0],d[-1]) not in (('(',')'),('"','"')): | 
					
						
							|  |  |  | 				data = '%s "%s"' % (data, d) | 
					
						
							|  |  |  | 			else: | 
					
						
							|  |  |  | 				data = '%s %s' % (data, d) | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		literal = self.literal | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		if literal is not None: | 
					
						
							| 
									
										
										
										
											1998-05-29 13:34:03 +00:00
										 |  |  | 			self.literal = None | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			if type(literal) is type(self._command): | 
					
						
							|  |  |  | 				literator = literal | 
					
						
							|  |  |  | 			else: | 
					
						
							|  |  |  | 				literator = None | 
					
						
							|  |  |  | 				data = '%s {%s}' % (data, len(literal)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		try: | 
					
						
							|  |  |  | 			self.sock.send('%s%s' % (data, CRLF)) | 
					
						
							|  |  |  | 		except socket.error, val: | 
					
						
							|  |  |  | 			raise self.abort('socket error: %s' % val) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if __debug__ and self.debug >= 4: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			_mesg('> %s' % data) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if literal is None: | 
					
						
							|  |  |  | 			return tag | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		while 1: | 
					
						
							|  |  |  | 			# Wait for continuation response | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			while self._get_response(): | 
					
						
							|  |  |  | 				if self.tagged_commands[tag]:	# BAD/NO? | 
					
						
							|  |  |  | 					return tag | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			# Send literal | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			if literator: | 
					
						
							|  |  |  | 				literal = literator(self.continuation_response) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			if __debug__ and self.debug >= 4: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 				_mesg('write literal size %s' % len(literal)) | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			try: | 
					
						
							|  |  |  | 				self.sock.send(literal) | 
					
						
							|  |  |  | 				self.sock.send(CRLF) | 
					
						
							|  |  |  | 			except socket.error, val: | 
					
						
							|  |  |  | 				raise self.abort('socket error: %s' % val) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if not literator: | 
					
						
							|  |  |  | 				break | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return tag | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def _command_complete(self, name, tag): | 
					
						
							|  |  |  | 		try: | 
					
						
							|  |  |  | 			typ, data = self._get_tagged_response(tag) | 
					
						
							|  |  |  | 		except self.abort, val: | 
					
						
							|  |  |  | 			raise self.abort('command: %s => %s' % (name, val)) | 
					
						
							|  |  |  | 		except self.error, val: | 
					
						
							|  |  |  | 			raise self.error('command: %s => %s' % (name, val)) | 
					
						
							|  |  |  | 		if self.untagged_responses.has_key('BYE') and name != 'LOGOUT': | 
					
						
							|  |  |  | 			raise self.abort(self.untagged_responses['BYE'][-1]) | 
					
						
							|  |  |  | 		if typ == 'BAD': | 
					
						
							|  |  |  | 			raise self.error('%s command error: %s %s' % (name, typ, data)) | 
					
						
							|  |  |  | 		return typ, data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def _get_response(self): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		# Read response and store. | 
					
						
							|  |  |  | 		# | 
					
						
							|  |  |  | 		# Returns None for continuation responses, | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		# otherwise first response line received. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		resp = self._get_line() | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		# Command completion response? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if self._match(self.tagre, resp): | 
					
						
							|  |  |  | 			tag = self.mo.group('tag') | 
					
						
							|  |  |  | 			if not self.tagged_commands.has_key(tag): | 
					
						
							|  |  |  | 				raise self.abort('unexpected tagged response: %s' % resp) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			typ = self.mo.group('type') | 
					
						
							|  |  |  | 			dat = self.mo.group('data') | 
					
						
							|  |  |  | 			self.tagged_commands[tag] = (typ, [dat]) | 
					
						
							|  |  |  | 		else: | 
					
						
							|  |  |  | 			dat2 = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			# '*' (untagged) responses? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if not self._match(Untagged_response, resp): | 
					
						
							|  |  |  | 				if self._match(Untagged_status, resp): | 
					
						
							|  |  |  | 					dat2 = self.mo.group('data2') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if self.mo is None: | 
					
						
							|  |  |  | 				# Only other possibility is '+' (continuation) rsponse... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if self._match(Continuation, resp): | 
					
						
							|  |  |  | 					self.continuation_response = self.mo.group('data') | 
					
						
							|  |  |  | 					return None	# NB: indicates continuation | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 				raise self.abort("unexpected response: '%s'" % resp) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			typ = self.mo.group('type') | 
					
						
							|  |  |  | 			dat = self.mo.group('data') | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			if dat is None: dat = ''	# Null untagged response | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 			if dat2: dat = dat + ' ' + dat2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			# Is there a literal to come? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			while self._match(Literal, dat): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				# Read literal direct from connection. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				size = string.atoi(self.mo.group('size')) | 
					
						
							|  |  |  | 				if __debug__ and self.debug >= 4: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 					_mesg('read literal size %s' % size) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 				data = self.file.read(size) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				# Store response with literal as tuple | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				self._append_untagged(typ, (dat, data)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				# Read trailer - possibly containing another literal | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 				dat = self._get_line() | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			self._append_untagged(typ, dat) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		# Bracketed response information? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat): | 
					
						
							|  |  |  | 			self._append_untagged(self.mo.group('type'), self.mo.group('data')) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		if __debug__ and self.debug >= 1 and typ in ('NO', 'BAD'): | 
					
						
							|  |  |  | 			_mesg('%s response: %s' % (typ, dat)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		return resp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def _get_tagged_response(self, tag): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		while 1: | 
					
						
							|  |  |  | 			result = self.tagged_commands[tag] | 
					
						
							|  |  |  | 			if result is not None: | 
					
						
							|  |  |  | 				del self.tagged_commands[tag] | 
					
						
							|  |  |  | 				return result | 
					
						
							|  |  |  | 			self._get_response() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def _get_line(self): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		line = self.file.readline() | 
					
						
							|  |  |  | 		if not line: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			raise self.abort('socket error: EOF') | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		# Protocol mandates all lines terminated by CRLF | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		line = line[:-2] | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		if __debug__ and self.debug >= 4: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			_mesg('< %s' % line) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		return line | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def _match(self, cre, s): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		# Run compiled regular expression match method on 's'. | 
					
						
							|  |  |  | 		# Save result, return success. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		self.mo = cre.match(s) | 
					
						
							|  |  |  | 		if __debug__ and self.mo is not None and self.debug >= 5: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			_mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		return self.mo is not None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def _new_tag(self): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		tag = '%s%s' % (self.tagpre, self.tagnum) | 
					
						
							|  |  |  | 		self.tagnum = self.tagnum + 1 | 
					
						
							|  |  |  | 		self.tagged_commands[tag] = None | 
					
						
							|  |  |  | 		return tag | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 	def _simple_command(self, name, *args): | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		return self._command_complete(name, apply(self._command, (name,) + args)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 	def _untagged_response(self, typ, dat, name): | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		if typ == 'NO': | 
					
						
							|  |  |  | 			return typ, dat | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		if not self.untagged_responses.has_key(name): | 
					
						
							|  |  |  | 			return typ, [None] | 
					
						
							|  |  |  | 		data = self.untagged_responses[name] | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		if __debug__ and self.debug >= 5: | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 			_mesg('untagged_responses[%s] => %s' % (name, data)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		del self.untagged_responses[name] | 
					
						
							|  |  |  | 		return typ, data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | class _Authenticator: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"""Private class to provide en/decoding
 | 
					
						
							|  |  |  | 		for base64-based authentication conversation. | 
					
						
							|  |  |  | 	"""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def __init__(self, mechinst): | 
					
						
							|  |  |  | 		self.mech = mechinst	# Callable object to provide/process data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def process(self, data): | 
					
						
							|  |  |  | 		ret = self.mech(self.decode(data)) | 
					
						
							|  |  |  | 		if ret is None: | 
					
						
							|  |  |  | 			return '*'	# Abort conversation | 
					
						
							|  |  |  | 		return self.encode(ret) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def encode(self, inp): | 
					
						
							|  |  |  | 		# | 
					
						
							|  |  |  | 		#  Invoke binascii.b2a_base64 iteratively with | 
					
						
							|  |  |  | 		#  short even length buffers, strip the trailing | 
					
						
							|  |  |  | 		#  line feed from the result and append.  "Even" | 
					
						
							|  |  |  | 		#  means a number that factors to both 6 and 8, | 
					
						
							|  |  |  | 		#  so when it gets to the end of the 8-bit input | 
					
						
							|  |  |  | 		#  there's no partial 6-bit output. | 
					
						
							|  |  |  | 		# | 
					
						
							|  |  |  | 		oup = '' | 
					
						
							|  |  |  | 		while inp: | 
					
						
							|  |  |  | 			if len(inp) > 48: | 
					
						
							|  |  |  | 				t = inp[:48] | 
					
						
							|  |  |  | 				inp = inp[48:] | 
					
						
							|  |  |  | 			else: | 
					
						
							|  |  |  | 				t = inp | 
					
						
							|  |  |  | 				inp = '' | 
					
						
							|  |  |  | 			e = binascii.b2a_base64(t) | 
					
						
							|  |  |  | 			if e: | 
					
						
							|  |  |  | 				oup = oup + e[:-1] | 
					
						
							|  |  |  | 		return oup | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  | 	def decode(self, inp): | 
					
						
							|  |  |  | 		if not inp: | 
					
						
							|  |  |  | 			return '' | 
					
						
							|  |  |  | 		return binascii.a2b_base64(inp) | 
					
						
							|  |  |  |   | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, | 
					
						
							|  |  |  | 	'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def Internaldate2tuple(resp): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 14:20:31 +00:00
										 |  |  | 	"""Convert IMAP4 INTERNALDATE to UT.
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 14:20:31 +00:00
										 |  |  | 	Returns Python time module tuple. | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 	"""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mo = InternalDate.match(resp) | 
					
						
							|  |  |  | 	if not mo: | 
					
						
							|  |  |  | 		return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mon = Mon2num[mo.group('mon')] | 
					
						
							|  |  |  | 	zonen = mo.group('zonen') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for name in ('day', 'year', 'hour', 'min', 'sec', 'zoneh', 'zonem'): | 
					
						
							|  |  |  | 		exec "%s = string.atoi(mo.group('%s'))" % (name, name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	# INTERNALDATE timezone must be subtracted to get UT | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	zone = (zoneh*60 + zonem)*60 | 
					
						
							|  |  |  | 	if zonen == '-': | 
					
						
							|  |  |  | 		zone = -zone | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tt = (year, mon, day, hour, min, sec, -1, -1, -1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	utc = time.mktime(tt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	# Following is necessary because the time module has no 'mkgmtime'. | 
					
						
							|  |  |  | 	# 'mktime' assumes arg in local timezone, so adds timezone/altzone. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lt = time.localtime(utc) | 
					
						
							|  |  |  | 	if time.daylight and lt[-1]: | 
					
						
							|  |  |  | 		zone = zone + time.altzone | 
					
						
							|  |  |  | 	else: | 
					
						
							|  |  |  | 		zone = zone + time.timezone | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return time.localtime(utc - zone) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def Int2AP(num): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 14:20:31 +00:00
										 |  |  | 	"""Convert integer to A-P string representation.""" | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	val = ''; AP = 'ABCDEFGHIJKLMNOP' | 
					
						
							| 
									
										
										
										
											1998-04-09 14:20:31 +00:00
										 |  |  | 	num = int(abs(num)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 	while num: | 
					
						
							|  |  |  | 		num, mod = divmod(num, 16) | 
					
						
							|  |  |  | 		val = AP[mod] + val | 
					
						
							|  |  |  | 	return val | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def ParseFlags(resp): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 14:20:31 +00:00
										 |  |  | 	"""Convert IMAP4 flags response to python tuple.""" | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	mo = Flags.match(resp) | 
					
						
							|  |  |  | 	if not mo: | 
					
						
							|  |  |  | 		return () | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return tuple(string.split(mo.group('flags'))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def Time2Internaldate(date_time): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"""Convert 'date_time' to IMAP4 INTERNALDATE representation.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"' | 
					
						
							|  |  |  | 	"""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dttype = type(date_time) | 
					
						
							|  |  |  | 	if dttype is type(1): | 
					
						
							|  |  |  | 		tt = time.localtime(date_time) | 
					
						
							|  |  |  | 	elif dttype is type(()): | 
					
						
							|  |  |  | 		tt = date_time | 
					
						
							|  |  |  | 	elif dttype is type(""): | 
					
						
							|  |  |  | 		return date_time	# Assume in correct format | 
					
						
							|  |  |  | 	else: raise ValueError | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dt = time.strftime("%d-%b-%Y %H:%M:%S", tt) | 
					
						
							|  |  |  | 	if dt[0] == '0': | 
					
						
							|  |  |  | 		dt = ' ' + dt[1:] | 
					
						
							|  |  |  | 	if time.daylight and tt[-1]: | 
					
						
							|  |  |  | 		zone = -time.altzone | 
					
						
							|  |  |  | 	else: | 
					
						
							|  |  |  | 		zone = -time.timezone | 
					
						
							|  |  |  | 	return '"' + dt + " %+02d%02d" % divmod(zone/60, 60) + '"' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | if __debug__: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 	def _mesg(s): | 
					
						
							|  |  |  | #		if len(s) > 70: s = '%.70s..' % s | 
					
						
							|  |  |  | 		sys.stderr.write('\t'+s+'\n') | 
					
						
							|  |  |  | 		sys.stderr.flush() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def _dump_ur(dict): | 
					
						
							|  |  |  | 		# Dump untagged responses (in `dict'). | 
					
						
							|  |  |  | 		l = dict.items() | 
					
						
							|  |  |  | 		if not l: return | 
					
						
							|  |  |  | 		t = '\n\t\t' | 
					
						
							|  |  |  | 		j = string.join | 
					
						
							|  |  |  | 		l = map(lambda x,j=j:'%s: "%s"' % (x[0], x[1][0] and j(x[1], '" "') or ''), l) | 
					
						
							|  |  |  | 		_mesg('untagged responses dump:%s%s' % (t, j(l, t))) | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | if __debug__ and __name__ == '__main__': | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-25 02:22:16 +00:00
										 |  |  | 	import getpass, sys | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-05-29 18:08:48 +00:00
										 |  |  | 	host = '' | 
					
						
							| 
									
										
										
										
											1998-06-25 02:22:16 +00:00
										 |  |  | 	if sys.argv[1:]: host = sys.argv[1] | 
					
						
							| 
									
										
										
										
											1998-05-29 18:08:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 	USER = getpass.getuser() | 
					
						
							| 
									
										
										
										
											1998-06-25 02:22:16 +00:00
										 |  |  | 	PASSWD = getpass.getpass("IMAP password for %s: " % (host or "localhost")) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	test_seq1 = ( | 
					
						
							|  |  |  | 	('login', (USER, PASSWD)), | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 	('create', ('/tmp/xxx 1',)), | 
					
						
							|  |  |  | 	('rename', ('/tmp/xxx 1', '/tmp/yyy')), | 
					
						
							|  |  |  | 	('CREATE', ('/tmp/yyz 2',)), | 
					
						
							|  |  |  | 	('append', ('/tmp/yyz 2', None, None, 'From: anon@x.y.z\n\ndata...')), | 
					
						
							|  |  |  | 	('select', ('/tmp/yyz 2',)), | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 	('search', (None, '(TO zork)')), | 
					
						
							|  |  |  | 	('partial', ('1', 'RFC822', 1, 1024)), | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 	('store', ('1', 'FLAGS', '(\Deleted)')), | 
					
						
							|  |  |  | 	('expunge', ()), | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 	('recent', ()), | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 	('close', ()), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	test_seq2 = ( | 
					
						
							|  |  |  | 	('select', ()), | 
					
						
							|  |  |  | 	('response',('UIDVALIDITY',)), | 
					
						
							|  |  |  | 	('uid', ('SEARCH', 'ALL')), | 
					
						
							|  |  |  | 	('response', ('EXISTS',)), | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 	('recent', ()), | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 	('logout', ()), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	def run(cmd, args): | 
					
						
							|  |  |  | 		typ, dat = apply(eval('M.%s' % cmd), args) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 		_mesg(' %s %s\n  => %s %s' % (cmd, args, typ, dat)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 		return dat | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 	Debug = 5 | 
					
						
							| 
									
										
										
										
											1998-05-29 18:08:48 +00:00
										 |  |  | 	M = IMAP4(host) | 
					
						
							| 
									
										
										
										
											1998-09-28 15:34:46 +00:00
										 |  |  | 	_mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for cmd,args in test_seq1: | 
					
						
							|  |  |  | 		run(cmd, args) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-11 01:22:34 +00:00
										 |  |  | 	for ml in run('list', ('/tmp/', 'yy%')): | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		mo = re.match(r'.*"([^"]+)"$', ml) | 
					
						
							|  |  |  | 		if mo: path = mo.group(1) | 
					
						
							|  |  |  | 		else: path = string.split(ml)[-1] | 
					
						
							| 
									
										
										
										
											1998-04-11 01:22:34 +00:00
										 |  |  | 		run('delete', (path,)) | 
					
						
							| 
									
										
										
										
											1998-04-09 13:51:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for cmd,args in test_seq2: | 
					
						
							|  |  |  | 		dat = run(cmd, args) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-04-11 01:22:34 +00:00
										 |  |  | 		if (cmd,args) != ('uid', ('SEARCH', 'ALL')): | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 		uid = string.split(dat[-1])[-1] | 
					
						
							| 
									
										
										
										
											1998-05-18 14:39:42 +00:00
										 |  |  | 		run('uid', ('FETCH', '%s' % uid, | 
					
						
							| 
									
										
										
										
											1998-06-18 14:24:28 +00:00
										 |  |  | 			'(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)')) |