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
69out = sys.stderr
70
71class PyMailSMTPService(unohelper.Base, XSmtpService):
72    def __init__( self, ctx ):
73        self.ctx = ctx
74        self.listeners = []
75        self.supportedtypes = ('Insecure', 'Ssl')
76        self.server = None
77        self.connectioncontext = None
78        self.notify = EventObject(self)
79        if dbg:
80            out.write("PyMailSMPTService init\n")
81    def addConnectionListener(self, xListener):
82        if dbg:
83            out.write("PyMailSMPTService addConnectionListener\n")
84        self.listeners.append(xListener)
85    def removeConnectionListener(self, xListener):
86        if dbg:
87            out.write("PyMailSMPTService removeConnectionListener\n")
88        self.listeners.remove(xListener)
89    def getSupportedConnectionTypes(self):
90        if dbg:
91            out.write("PyMailSMPTService getSupportedConnectionTypes\n")
92        return self.supportedtypes
93    def connect(self, xConnectionContext, xAuthenticator):
94        self.connectioncontext = xConnectionContext
95        if dbg:
96            out.write("PyMailSMPTService connect\n")
97
98        server = xConnectionContext.getValueByName("ServerName")
99        if dbg:
100            out.write("ServerName: %s\n" % server)
101
102        port = xConnectionContext.getValueByName("Port")
103        if dbg:
104            out.write("Port: %d\n" % port)
105
106        tout = xConnectionContext.getValueByName("Timeout")
107        if dbg:
108            out.write("Timeout is instance of int? %s\n" % isinstance(tout,int))
109        if not isinstance(tout,int):
110            tout = _GLOBAL_DEFAULT_TIMEOUT
111        if dbg:
112            out.write("Timeout: %s\n" % str(tout))
113
114        self.server = smtplib.SMTP(server, port,timeout=tout)
115        if dbg:
116            self.server.set_debuglevel(1)
117
118        connectiontype = xConnectionContext.getValueByName("ConnectionType")
119        if dbg:
120            out.write("ConnectionType: %s\n" % str(connectiontype))
121
122        if connectiontype.upper() == 'SSL':
123            self.server.ehlo()
124            self.server.starttls()
125            self.server.ehlo()
126
127        user = xAuthenticator.getUserName().encode('ascii')
128        password = xAuthenticator.getPassword().encode('ascii')
129        if user != '':
130            if dbg:
131                out.write('Logging in, username of %s\n' % user)
132            self.server.login(user, password)
133
134        for listener in self.listeners:
135            listener.connected(self.notify)
136    def disconnect(self):
137        if dbg:
138            out.write("PyMailSMPTService disconnect\n")
139        if self.server:
140            self.server.quit()
141            self.server = None
142        for listener in self.listeners:
143            listener.disconnected(self.notify)
144    def isConnected(self):
145        if dbg:
146            out.write("PyMailSMPTService isConnected\n")
147        return self.server != None
148    def getCurrentConnectionContext(self):
149        if dbg:
150            out.write("PyMailSMPTService getCurrentConnectionContext\n")
151        return self.connectioncontext
152    def sendMailMessage(self, xMailMessage):
153        COMMASPACE = ', '
154
155        if dbg:
156            out.write("PyMailSMPTService sendMailMessage\n")
157        recipients = xMailMessage.getRecipients()
158        sendermail = xMailMessage.SenderAddress
159        sendername = xMailMessage.SenderName
160        subject = xMailMessage.Subject
161        ccrecipients = xMailMessage.getCcRecipients()
162        bccrecipients = xMailMessage.getBccRecipients()
163        if dbg:
164            out.write("PyMailSMPTService subject %s\n" % subject)
165            out.write("PyMailSMPTService from %s\n" % sendername.encode('utf-8'))
166            out.write("PyMailSMTPService from %s\n" % sendermail)
167            out.write("PyMailSMPTService send to %s\n" % str(recipients))
168
169        attachments = xMailMessage.getAttachments()
170
171        textmsg = Message()
172
173        content = xMailMessage.Body
174        flavors = content.getTransferDataFlavors()
175        if dbg:
176            out.write("PyMailSMPTService flavors len %d\n" % len(flavors))
177
178        #Use first flavor that's sane for an email body
179        for flavor in flavors:
180            if flavor.MimeType.find('text/html') != -1 or flavor.MimeType.find('text/plain') != -1:
181                if dbg:
182                    out.write("PyMailSMPTService mimetype is %s\n" % flavor.MimeType)
183                textbody = content.getTransferData(flavor)
184                try:
185                    textbody = textbody.value
186                except:
187                    pass
188                textbody = textbody.encode('utf-8')
189
190                if len(textbody):
191                    mimeEncoding = re.sub("charset=.*", "charset=UTF-8", flavor.MimeType)
192                    if mimeEncoding.find('charset=UTF-8') == -1:
193                        mimeEncoding = mimeEncoding + "; charset=UTF-8"
194                    textmsg['Content-Type'] = mimeEncoding
195                    textmsg['MIME-Version'] = '1.0'
196                    textmsg.set_payload(textbody)
197
198                break
199
200        if (len(attachments)):
201            msg = MIMEMultipart()
202            msg.epilogue = ''
203            msg.attach(textmsg)
204        else:
205            msg = textmsg
206
207        hdr = Header(sendername, 'utf-8')
208        hdr.append('<'+sendermail+'>','us-ascii')
209        msg['Subject'] = subject
210        msg['From'] = hdr
211        msg['To'] = COMMASPACE.join(recipients)
212        if len(ccrecipients):
213            msg['Cc'] = COMMASPACE.join(ccrecipients)
214        if xMailMessage.ReplyToAddress != '':
215            msg['Reply-To'] = xMailMessage.ReplyToAddress
216
217        mailerstring = "OpenOffice.org 2.0 via Caolan's mailmerge component"
218        try:
219            ctx = uno.getComponentContext()
220            aConfigProvider = ctx.ServiceManager.createInstance("com.sun.star.configuration.ConfigurationProvider")
221            prop = uno.createUnoStruct('com.sun.star.beans.PropertyValue')
222            prop.Name = "nodepath"
223            prop.Value = "/org.openoffice.Setup/Product"
224            aSettings = aConfigProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
225                    (prop,))
226            mailerstring = aSettings.getByName("ooName") + " " + \
227                    aSettings.getByName("ooSetupVersion") + " via Caolan's mailmerge component"
228        except:
229            pass
230
231        msg['X-Mailer'] = mailerstring
232        msg['Date'] = formatdate(localtime=True)
233
234        for attachment in attachments:
235            content = attachment.Data
236            flavors = content.getTransferDataFlavors()
237            flavor = flavors[0]
238            ctype = flavor.MimeType
239            maintype, subtype = ctype.split('/', 1)
240            msgattachment = MIMEBase(maintype, subtype)
241            data = content.getTransferData(flavor)
242            msgattachment.set_payload(data)
243            Encoders.encode_base64(msgattachment)
244            fname = attachment.ReadableName
245            try:
246                fname.encode('ascii')
247            except:
248                fname = ('utf-8','',fname.encode('utf-8'))
249            msgattachment.add_header('Content-Disposition', 'attachment', \
250                    filename=fname)
251            msg.attach(msgattachment)
252
253        uniquer = {}
254        for key in recipients:
255            uniquer[key] = True
256        if len(ccrecipients):
257            for key in ccrecipients:
258                uniquer[key] = True
259        if len(bccrecipients):
260            for key in bccrecipients:
261                uniquer[key] = True
262        truerecipients = list(uniquer.keys())
263
264        if dbg:
265            out.write("PyMailSMPTService recipients are %s\n" % str(truerecipients))
266
267        self.server.sendmail(sendermail, truerecipients, msg.as_string())
268
269class PyMailIMAPService(unohelper.Base, XMailService):
270    def __init__( self, ctx ):
271        self.ctx = ctx
272        self.listeners = []
273        self.supportedtypes = ('Insecure', 'Ssl')
274        self.server = None
275        self.connectioncontext = None
276        self.notify = EventObject(self)
277        if dbg:
278            out.write("PyMailIMAPService init\n")
279    def addConnectionListener(self, xListener):
280        if dbg:
281            out.write("PyMailIMAPService addConnectionListener\n")
282        self.listeners.append(xListener)
283    def removeConnectionListener(self, xListener):
284        if dbg:
285            out.write("PyMailIMAPService removeConnectionListener\n")
286        self.listeners.remove(xListener)
287    def getSupportedConnectionTypes(self):
288        if dbg:
289            out.write("PyMailIMAPService getSupportedConnectionTypes\n")
290        return self.supportedtypes
291    def connect(self, xConnectionContext, xAuthenticator):
292        if dbg:
293            out.write("PyMailIMAPService connect\n")
294
295        self.connectioncontext = xConnectionContext
296        server = xConnectionContext.getValueByName("ServerName")
297        if dbg:
298            out.write("Server: %s\n" % server)
299        port = xConnectionContext.getValueByName("Port")
300        if dbg:
301            out.write("Port: %d\n" % port)
302        connectiontype = xConnectionContext.getValueByName("ConnectionType")
303        if dbg:
304            out.write("Connection type: %s\n" % connectiontype)
305        out.write("BEFORE\n")
306        if connectiontype.upper() == 'SSL':
307            self.server = imaplib.IMAP4_SSL(server, port)
308        else:
309            self.server = imaplib.IMAP4(server, port)
310        out.write("AFTER\n")
311
312        user = xAuthenticator.getUserName().encode('ascii')
313        password = xAuthenticator.getPassword().encode('ascii')
314        if user != '':
315            if dbg:
316                out.write('Logging in, username of %s\n' % user)
317            self.server.login(user, password)
318
319        for listener in self.listeners:
320            listener.connected(self.notify)
321    def disconnect(self):
322        if dbg:
323            out.write("PyMailIMAPService disconnect\n")
324        if self.server:
325            self.server.logout()
326            self.server = None
327        for listener in self.listeners:
328            listener.disconnected(self.notify)
329    def isConnected(self):
330        if dbg:
331            out.write("PyMailIMAPService isConnected\n")
332        return self.server != None
333    def getCurrentConnectionContext(self):
334        if dbg:
335            out.write("PyMailIMAPService getCurrentConnectionContext\n")
336        return self.connectioncontext
337
338class PyMailPOP3Service(unohelper.Base, XMailService):
339    def __init__( self, ctx ):
340        self.ctx = ctx
341        self.listeners = []
342        self.supportedtypes = ('Insecure', 'Ssl')
343        self.server = None
344        self.connectioncontext = None
345        self.notify = EventObject(self)
346        if dbg:
347            out.write("PyMailPOP3Service init\n")
348    def addConnectionListener(self, xListener):
349        if dbg:
350            out.write("PyMailPOP3Service addConnectionListener\n")
351        self.listeners.append(xListener)
352    def removeConnectionListener(self, xListener):
353        if dbg:
354            out.write("PyMailPOP3Service removeConnectionListener\n")
355        self.listeners.remove(xListener)
356    def getSupportedConnectionTypes(self):
357        if dbg:
358            out.write("PyMailPOP3Service getSupportedConnectionTypes\n")
359        return self.supportedtypes
360    def connect(self, xConnectionContext, xAuthenticator):
361        if dbg:
362            out.write("PyMailPOP3Service connect\n")
363
364        self.connectioncontext = xConnectionContext
365        server = xConnectionContext.getValueByName("ServerName")
366        if dbg:
367            out.write("Server: %s\n" % server)
368        port = xConnectionContext.getValueByName("Port")
369        if dbg:
370            out.write("Port: %s\n" % port)
371        connectiontype = xConnectionContext.getValueByName("ConnectionType")
372        if dbg:
373            out.write("Connection type: %s\n" % str(connectiontype))
374        out.write("BEFORE\n")
375        if connectiontype.upper() == 'SSL':
376            self.server = poplib.POP3_SSL(server, port)
377        else:
378            tout = xConnectionContext.getValueByName("Timeout")
379            if dbg:
380                out.write("Timeout is instance of int? %s\n" % isinstance(tout,int))
381            if not isinstance(tout,int):
382                tout = _GLOBAL_DEFAULT_TIMEOUT
383            if dbg:
384                out.write("Timeout: %s\n" % str(tout))
385            self.server = poplib.POP3(server, port, timeout=tout)
386        out.write("AFTER\n")
387
388        user = xAuthenticator.getUserName().encode('ascii')
389        password = xAuthenticator.getPassword().encode('ascii')
390        if dbg:
391            out.write('Logging in, username of %s\n' % user)
392        self.server.user(user)
393        self.server.pass_(password)
394
395        for listener in self.listeners:
396            listener.connected(self.notify)
397    def disconnect(self):
398        if dbg:
399            out.write("PyMailPOP3Service disconnect\n")
400        if self.server:
401            self.server.quit()
402            self.server = None
403        for listener in self.listeners:
404            listener.disconnected(self.notify)
405    def isConnected(self):
406        if dbg:
407            out.write("PyMailPOP3Service isConnected\n")
408        return self.server != None
409    def getCurrentConnectionContext(self):
410        if dbg:
411            out.write("PyMailPOP3Service getCurrentConnectionContext\n")
412        return self.connectioncontext
413
414class PyMailServiceProvider(unohelper.Base, XMailServiceProvider):
415    def __init__( self, ctx ):
416        if dbg:
417            out.write("PyMailServiceProvider init\n")
418        self.ctx = ctx
419    def create(self, aType):
420        if dbg:
421            out.write("PyMailServiceProvider create with %s\n" % aType)
422        if aType == SMTP:
423            return PyMailSMTPService(self.ctx);
424        elif aType == POP3:
425            return PyMailPOP3Service(self.ctx);
426        elif aType == IMAP:
427            return PyMailIMAPService(self.ctx);
428        else:
429            out.write("PyMailServiceProvider, unknown TYPE %s\n" % aType)
430
431class PyMailMessage(unohelper.Base, XMailMessage):
432    def __init__( self, ctx, sTo='', sFrom='', Subject='', Body=None, aMailAttachment=None ):
433        if dbg:
434            out.write("PyMailMessage init\n")
435        self.ctx = ctx
436
437        self.recipients = [sTo]
438        self.ccrecipients = []
439        self.bccrecipients = []
440        self.aMailAttachments = []
441        if aMailAttachment != None:
442            self.aMailAttachments.append(aMailAttachment)
443
444        self.SenderName, self.SenderAddress = parseaddr(sFrom)
445        self.ReplyToAddress = sFrom
446        self.Subject = Subject
447        self.Body = Body
448        if dbg:
449            out.write("post PyMailMessage init\n")
450    def addRecipient( self, recipient ):
451        if dbg:
452            out.write("PyMailMessage.addRecipient%s\n" % recipient)
453        self.recipients.append(recipient)
454    def addCcRecipient( self, ccrecipient ):
455        if dbg:
456            out.write("PyMailMessage.addCcRecipient%s\n" % ccrecipient)
457        self.ccrecipients.append(ccrecipient)
458    def addBccRecipient( self, bccrecipient ):
459        if dbg:
460            out.write("PyMailMessage.addBccRecipient%s\n" % bccrecipient)
461        self.bccrecipients.append(bccrecipient)
462    def getRecipients( self ):
463        if dbg:
464            out.write("PyMailMessage.getRecipients%s\n" % self.recipients)
465        return tuple(self.recipients)
466    def getCcRecipients( self ):
467        if dbg:
468            out.write("PyMailMessage.getCcRecipients%s\n" % self.ccrecipients)
469        return tuple(self.ccrecipients)
470    def getBccRecipients( self ):
471        if dbg:
472            out.write("PyMailMessage.getBccRecipients%s\n" % self.bccrecipients)
473        return tuple(self.bccrecipients)
474    def addAttachment( self, aMailAttachment ):
475        if dbg:
476            out.write("PyMailMessage.addAttachment\n")
477        self.aMailAttachments.append(aMailAttachment)
478    def getAttachments( self ):
479        if dbg:
480            out.write("PyMailMessage.getAttachments\n")
481        return tuple(self.aMailAttachments)
482
483
484# pythonloader looks for a static g_ImplementationHelper variable
485g_ImplementationHelper = unohelper.ImplementationHelper()
486g_ImplementationHelper.addImplementation( \
487        PyMailServiceProvider, "org.openoffice.pyuno.MailServiceProvider",
488                ("com.sun.star.mail.MailServiceProvider",),)
489g_ImplementationHelper.addImplementation( \
490        PyMailMessage, "org.openoffice.pyuno.MailMessage",
491                ("com.sun.star.mail.MailMessage",),)
492