1# *************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20# *************************************************************
21
22# Caolan McNamara caolanm@redhat.com
23# a simple email mailmerge component
24
25# manual installation for hackers, not necessary for users
26# cp mailmerge.py /usr/lib/openoffice.org2.0/program
27# cd /usr/lib/openoffice.org2.0/program
28# ./unopkg add --shared mailmerge.py
29# edit ~/.openoffice.org2/user/registry/data/org/openoffice/Office/Writer.xcu
30# and change EMailSupported to as follows...
31#  <prop oor:name="EMailSupported" oor:type="xs:boolean">
32#   <value>true</value>
33#  </prop>
34
35import unohelper
36import uno
37import re
38
39#to implement com::sun::star::mail::XMailServiceProvider
40#and
41#to implement com.sun.star.mail.XMailMessage
42
43from com.sun.star.mail import XMailServiceProvider
44from com.sun.star.mail import XMailService
45from com.sun.star.mail import XSmtpService
46from com.sun.star.mail import XConnectionListener
47from com.sun.star.mail import XAuthenticator
48from com.sun.star.mail import XMailMessage
49from com.sun.star.mail.MailServiceType import SMTP
50from com.sun.star.mail.MailServiceType import POP3
51from com.sun.star.mail.MailServiceType import IMAP
52from com.sun.star.uno import XCurrentContext
53from com.sun.star.lang import IllegalArgumentException
54from com.sun.star.lang import EventObject
55from com.sun.star.mail import SendMailMessageFailedException
56
57from email.MIMEBase import MIMEBase
58from email.Message import Message
59from email import Encoders
60from email.Header import Header
61from email.MIMEMultipart import MIMEMultipart
62from email.Utils import formatdate
63from email.Utils import parseaddr
64from socket import _GLOBAL_DEFAULT_TIMEOUT
65
66import sys, smtplib, imaplib, poplib
67
68dbg = False
69
70class PyMailSMTPService(unohelper.Base, XSmtpService):
71	def __init__( self, ctx ):
72		self.ctx = ctx
73		self.listeners = []
74		self.supportedtypes = ('Insecure', 'Ssl')
75		self.server = None
76		self.connectioncontext = None
77		self.notify = EventObject(self)
78		if dbg:
79			print >> sys.stderr, "PyMailSMPTService init"
80	def addConnectionListener(self, xListener):
81		if dbg:
82			print >> sys.stderr, "PyMailSMPTService addConnectionListener"
83		self.listeners.append(xListener)
84	def removeConnectionListener(self, xListener):
85		if dbg:
86			print >> sys.stderr, "PyMailSMPTService removeConnectionListener"
87		self.listeners.remove(xListener)
88	def getSupportedConnectionTypes(self):
89		if dbg:
90			print >> sys.stderr, "PyMailSMPTService getSupportedConnectionTypes"
91		return self.supportedtypes
92	def connect(self, xConnectionContext, xAuthenticator):
93		self.connectioncontext = xConnectionContext
94		if dbg:
95			print >> sys.stderr, "PyMailSMPTService connect"
96
97		server = xConnectionContext.getValueByName("ServerName")
98		if dbg:
99			print >> sys.stderr, "ServerName: %s" % server
100
101		port = xConnectionContext.getValueByName("Port")
102		if dbg:
103			print >> sys.stderr, "Port: %d" % port
104
105		tout = xConnectionContext.getValueByName("Timeout")
106		if dbg:
107			print >> sys.stderr, isinstance(tout,int)
108		if not isinstance(tout,int):
109			tout = _GLOBAL_DEFAULT_TIMEOUT
110		if dbg:
111			print >> sys.stderr, "Timeout: %s" % str(tout)
112
113		self.server = smtplib.SMTP(server, port,timeout=tout)
114		if dbg:
115			self.server.set_debuglevel(1)
116
117		connectiontype = xConnectionContext.getValueByName("ConnectionType")
118		if dbg:
119			print >> sys.stderr, "ConnectionType: %s" % connectiontype
120
121		if connectiontype.upper() == 'SSL':
122			self.server.ehlo()
123			self.server.starttls()
124			self.server.ehlo()
125
126		user = xAuthenticator.getUserName().encode('ascii')
127		password = xAuthenticator.getPassword().encode('ascii')
128		if user != '':
129			if dbg:
130				print >> sys.stderr, 'Logging in, username of', user
131			self.server.login(user, password)
132
133		for listener in self.listeners:
134			listener.connected(self.notify)
135	def disconnect(self):
136		if dbg:
137			print >> sys.stderr, "PyMailSMPTService disconnect"
138		if self.server:
139			self.server.quit()
140			self.server = None
141		for listener in self.listeners:
142			listener.disconnected(self.notify)
143	def isConnected(self):
144		if dbg:
145			print >> sys.stderr, "PyMailSMPTService isConnected"
146		return self.server != None
147	def getCurrentConnectionContext(self):
148		if dbg:
149			print >> sys.stderr, "PyMailSMPTService getCurrentConnectionContext"
150		return self.connectioncontext
151	def sendMailMessage(self, xMailMessage):
152		COMMASPACE = ', '
153
154		if dbg:
155			print >> sys.stderr, "PyMailSMPTService sendMailMessage"
156		recipients = xMailMessage.getRecipients()
157		sendermail = xMailMessage.SenderAddress
158		sendername = xMailMessage.SenderName
159		subject = xMailMessage.Subject
160		ccrecipients = xMailMessage.getCcRecipients()
161		bccrecipients = xMailMessage.getBccRecipients()
162		if dbg:
163			print >> sys.stderr, "PyMailSMPTService subject", subject
164			print >> sys.stderr, "PyMailSMPTService from", sendername.encode('utf-8')
165			print >> sys.stderr, "PyMailSMTPService from", sendermail
166			print >> sys.stderr, "PyMailSMPTService send to", recipients
167
168		attachments = xMailMessage.getAttachments()
169
170		textmsg = Message()
171
172		content = xMailMessage.Body
173		flavors = content.getTransferDataFlavors()
174		if dbg:
175			print >> sys.stderr, "PyMailSMPTService flavors len", len(flavors)
176
177		#Use first flavor that's sane for an email body
178		for flavor in flavors:
179			if flavor.MimeType.find('text/html') != -1 or flavor.MimeType.find('text/plain') != -1:
180				if dbg:
181					print >> sys.stderr, "PyMailSMPTService mimetype is", flavor.MimeType
182				textbody = content.getTransferData(flavor)
183				try:
184					textbody = textbody.value
185				except:
186					pass
187				textbody = textbody.encode('utf-8')
188
189				if len(textbody):
190					mimeEncoding = re.sub("charset=.*", "charset=UTF-8", flavor.MimeType)
191					if mimeEncoding.find('charset=UTF-8') == -1:
192						mimeEncoding = mimeEncoding + "; charset=UTF-8"
193					textmsg['Content-Type'] = mimeEncoding
194					textmsg['MIME-Version'] = '1.0'
195					textmsg.set_payload(textbody)
196
197				break
198
199		if (len(attachments)):
200			msg = MIMEMultipart()
201			msg.epilogue = ''
202			msg.attach(textmsg)
203		else:
204			msg = textmsg
205
206		hdr = Header(sendername, 'utf-8')
207		hdr.append('<'+sendermail+'>','us-ascii')
208		msg['Subject'] = subject
209		msg['From'] = hdr
210		msg['To'] = COMMASPACE.join(recipients)
211		if len(ccrecipients):
212			msg['Cc'] = COMMASPACE.join(ccrecipients)
213		if xMailMessage.ReplyToAddress != '':
214			msg['Reply-To'] = xMailMessage.ReplyToAddress
215
216		mailerstring = "OpenOffice.org 2.0 via Caolan's mailmerge component"
217		try:
218			ctx = uno.getComponentContext()
219			aConfigProvider = ctx.ServiceManager.createInstance("com.sun.star.configuration.ConfigurationProvider")
220			prop = uno.createUnoStruct('com.sun.star.beans.PropertyValue')
221			prop.Name = "nodepath"
222			prop.Value = "/org.openoffice.Setup/Product"
223			aSettings = aConfigProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
224				(prop,))
225			mailerstring = aSettings.getByName("ooName") + " " + \
226				aSettings.getByName("ooSetupVersion") + " via Caolan's mailmerge component"
227		except:
228			pass
229
230		msg['X-Mailer'] = mailerstring
231		msg['Date'] = formatdate(localtime=True)
232
233		for attachment in attachments:
234			content = attachment.Data
235			flavors = content.getTransferDataFlavors()
236			flavor = flavors[0]
237			ctype = flavor.MimeType
238			maintype, subtype = ctype.split('/', 1)
239			msgattachment = MIMEBase(maintype, subtype)
240			data = content.getTransferData(flavor)
241			msgattachment.set_payload(data)
242			Encoders.encode_base64(msgattachment)
243			fname = attachment.ReadableName
244			try:
245				fname.encode('ascii')
246			except:
247				fname = ('utf-8','',fname.encode('utf-8'))
248			msgattachment.add_header('Content-Disposition', 'attachment', \
249				filename=fname)
250			msg.attach(msgattachment)
251
252		uniquer = {}
253		for key in recipients:
254			uniquer[key] = True
255		if len(ccrecipients):
256			for key in ccrecipients:
257				uniquer[key] = True
258		if len(bccrecipients):
259			for key in bccrecipients:
260				uniquer[key] = True
261		truerecipients = uniquer.keys()
262
263		if dbg:
264			print >> sys.stderr, "PyMailSMPTService recipients are", truerecipients
265
266		self.server.sendmail(sendermail, truerecipients, msg.as_string())
267
268class PyMailIMAPService(unohelper.Base, XMailService):
269	def __init__( self, ctx ):
270		self.ctx = ctx
271		self.listeners = []
272		self.supportedtypes = ('Insecure', 'Ssl')
273		self.server = None
274		self.connectioncontext = None
275		self.notify = EventObject(self)
276		if dbg:
277			print >> sys.stderr, "PyMailIMAPService init"
278	def addConnectionListener(self, xListener):
279		if dbg:
280			print >> sys.stderr, "PyMailIMAPService addConnectionListener"
281		self.listeners.append(xListener)
282	def removeConnectionListener(self, xListener):
283		if dbg:
284			print >> sys.stderr, "PyMailIMAPService removeConnectionListener"
285		self.listeners.remove(xListener)
286	def getSupportedConnectionTypes(self):
287		if dbg:
288			print >> sys.stderr, "PyMailIMAPService getSupportedConnectionTypes"
289		return self.supportedtypes
290	def connect(self, xConnectionContext, xAuthenticator):
291		if dbg:
292			print >> sys.stderr, "PyMailIMAPService connect"
293
294		self.connectioncontext = xConnectionContext
295		server = xConnectionContext.getValueByName("ServerName")
296		if dbg:
297			print >> sys.stderr, server
298		port = xConnectionContext.getValueByName("Port")
299		if dbg:
300			print >> sys.stderr, port
301		connectiontype = xConnectionContext.getValueByName("ConnectionType")
302		if dbg:
303			print >> sys.stderr, connectiontype
304		print >> sys.stderr, "BEFORE"
305		if connectiontype.upper() == 'SSL':
306			self.server = imaplib.IMAP4_SSL(server, port)
307		else:
308			self.server = imaplib.IMAP4(server, port)
309		print >> sys.stderr, "AFTER"
310
311		user = xAuthenticator.getUserName().encode('ascii')
312		password = xAuthenticator.getPassword().encode('ascii')
313		if user != '':
314			if dbg:
315				print >> sys.stderr, 'Logging in, username of', user
316			self.server.login(user, password)
317
318		for listener in self.listeners:
319			listener.connected(self.notify)
320	def disconnect(self):
321		if dbg:
322			print >> sys.stderr, "PyMailIMAPService disconnect"
323		if self.server:
324			self.server.logout()
325			self.server = None
326		for listener in self.listeners:
327			listener.disconnected(self.notify)
328	def isConnected(self):
329		if dbg:
330			print >> sys.stderr, "PyMailIMAPService isConnected"
331		return self.server != None
332	def getCurrentConnectionContext(self):
333		if dbg:
334			print >> sys.stderr, "PyMailIMAPService getCurrentConnectionContext"
335		return self.connectioncontext
336
337class PyMailPOP3Service(unohelper.Base, XMailService):
338	def __init__( self, ctx ):
339		self.ctx = ctx
340		self.listeners = []
341		self.supportedtypes = ('Insecure', 'Ssl')
342		self.server = None
343		self.connectioncontext = None
344		self.notify = EventObject(self)
345		if dbg:
346			print >> sys.stderr, "PyMailPOP3Service init"
347	def addConnectionListener(self, xListener):
348		if dbg:
349			print >> sys.stderr, "PyMailPOP3Service addConnectionListener"
350		self.listeners.append(xListener)
351	def removeConnectionListener(self, xListener):
352		if dbg:
353			print >> sys.stderr, "PyMailPOP3Service removeConnectionListener"
354		self.listeners.remove(xListener)
355	def getSupportedConnectionTypes(self):
356		if dbg:
357			print >> sys.stderr, "PyMailPOP3Service getSupportedConnectionTypes"
358		return self.supportedtypes
359	def connect(self, xConnectionContext, xAuthenticator):
360		if dbg:
361			print >> sys.stderr, "PyMailPOP3Service connect"
362
363		self.connectioncontext = xConnectionContext
364		server = xConnectionContext.getValueByName("ServerName")
365		if dbg:
366			print >> sys.stderr, server
367		port = xConnectionContext.getValueByName("Port")
368		if dbg:
369			print >> sys.stderr, port
370		connectiontype = xConnectionContext.getValueByName("ConnectionType")
371		if dbg:
372			print >> sys.stderr, connectiontype
373		print >> sys.stderr, "BEFORE"
374		if connectiontype.upper() == 'SSL':
375			self.server = poplib.POP3_SSL(server, port)
376		else:
377			tout = xConnectionContext.getValueByName("Timeout")
378			if dbg:
379				print >> sys.stderr, isinstance(tout,int)
380			if not isinstance(tout,int):
381				tout = _GLOBAL_DEFAULT_TIMEOUT
382			if dbg:
383				print >> sys.stderr, "Timeout: %s" % str(tout)
384			self.server = poplib.POP3(server, port, timeout=tout)
385		print >> sys.stderr, "AFTER"
386
387		user = xAuthenticator.getUserName().encode('ascii')
388		password = xAuthenticator.getPassword().encode('ascii')
389		if dbg:
390			print >> sys.stderr, 'Logging in, username of', user
391		self.server.user(user)
392		self.server.pass_(password)
393
394		for listener in self.listeners:
395			listener.connected(self.notify)
396	def disconnect(self):
397		if dbg:
398			print >> sys.stderr, "PyMailPOP3Service disconnect"
399		if self.server:
400			self.server.quit()
401			self.server = None
402		for listener in self.listeners:
403			listener.disconnected(self.notify)
404	def isConnected(self):
405		if dbg:
406			print >> sys.stderr, "PyMailPOP3Service isConnected"
407		return self.server != None
408	def getCurrentConnectionContext(self):
409		if dbg:
410			print >> sys.stderr, "PyMailPOP3Service getCurrentConnectionContext"
411		return self.connectioncontext
412
413class PyMailServiceProvider(unohelper.Base, XMailServiceProvider):
414	def __init__( self, ctx ):
415		if dbg:
416			print >> sys.stderr, "PyMailServiceProvider init"
417		self.ctx = ctx
418	def create(self, aType):
419		if dbg:
420			print >> sys.stderr, "PyMailServiceProvider create with", aType
421		if aType == SMTP:
422			return PyMailSMTPService(self.ctx);
423		elif aType == POP3:
424			return PyMailPOP3Service(self.ctx);
425		elif aType == IMAP:
426			return PyMailIMAPService(self.ctx);
427		else:
428			print >> sys.stderr, "PyMailServiceProvider, unknown TYPE", aType
429
430class PyMailMessage(unohelper.Base, XMailMessage):
431	def __init__( self, ctx, sTo='', sFrom='', Subject='', Body=None, aMailAttachment=None ):
432		if dbg:
433			print >> sys.stderr, "PyMailMessage init"
434		self.ctx = ctx
435
436		self.recipients = [sTo]
437		self.ccrecipients = []
438		self.bccrecipients = []
439		self.aMailAttachments = []
440		if aMailAttachment != None:
441			self.aMailAttachments.append(aMailAttachment)
442
443		self.SenderName, self.SenderAddress = parseaddr(sFrom)
444		self.ReplyToAddress = sFrom
445		self.Subject = Subject
446		self.Body = Body
447		if dbg:
448			print >> sys.stderr, "post PyMailMessage init"
449	def addRecipient( self, recipient ):
450		if dbg:
451			print >> sys.stderr, "PyMailMessage.addRecipient", recipient
452		self.recipients.append(recipient)
453	def addCcRecipient( self, ccrecipient ):
454		if dbg:
455			print >> sys.stderr, "PyMailMessage.addCcRecipient", ccrecipient
456		self.ccrecipients.append(ccrecipient)
457	def addBccRecipient( self, bccrecipient ):
458		if dbg:
459			print >> sys.stderr, "PyMailMessage.addBccRecipient", bccrecipient
460		self.bccrecipients.append(bccrecipient)
461	def getRecipients( self ):
462		if dbg:
463			print >> sys.stderr, "PyMailMessage.getRecipients", self.recipients
464		return tuple(self.recipients)
465	def getCcRecipients( self ):
466		if dbg:
467			print >> sys.stderr, "PyMailMessage.getCcRecipients", self.ccrecipients
468		return tuple(self.ccrecipients)
469	def getBccRecipients( self ):
470		if dbg:
471			print >> sys.stderr, "PyMailMessage.getBccRecipients", self.bccrecipients
472		return tuple(self.bccrecipients)
473	def addAttachment( self, aMailAttachment ):
474		if dbg:
475			print >> sys.stderr, "PyMailMessage.addAttachment"
476		self.aMailAttachments.append(aMailAttachment)
477	def getAttachments( self ):
478		if dbg:
479			print >> sys.stderr, "PyMailMessage.getAttachments"
480		return tuple(self.aMailAttachments)
481
482
483# pythonloader looks for a static g_ImplementationHelper variable
484g_ImplementationHelper = unohelper.ImplementationHelper()
485g_ImplementationHelper.addImplementation( \
486	PyMailServiceProvider, "org.openoffice.pyuno.MailServiceProvider",
487		("com.sun.star.mail.MailServiceProvider",),)
488g_ImplementationHelper.addImplementation( \
489	PyMailMessage, "org.openoffice.pyuno.MailMessage",
490		("com.sun.star.mail.MailMessage",),)
491