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("PyMailSMPTService init", file=sys.stderr)
80    def addConnectionListener(self, xListener):
81        if dbg:
82            print("PyMailSMPTService addConnectionListener", file=sys.stderr)
83        self.listeners.append(xListener)
84    def removeConnectionListener(self, xListener):
85        if dbg:
86            print("PyMailSMPTService removeConnectionListener", file=sys.stderr)
87        self.listeners.remove(xListener)
88    def getSupportedConnectionTypes(self):
89        if dbg:
90            print("PyMailSMPTService getSupportedConnectionTypes", file=sys.stderr)
91        return self.supportedtypes
92    def connect(self, xConnectionContext, xAuthenticator):
93        self.connectioncontext = xConnectionContext
94        if dbg:
95            print("PyMailSMPTService connect", file=sys.stderr)
96
97        server = xConnectionContext.getValueByName("ServerName")
98        if dbg:
99            print("ServerName: %s" % server, file=sys.stderr)
100
101        port = xConnectionContext.getValueByName("Port")
102        if dbg:
103            print("Port: %d" % port, file=sys.stderr)
104
105        tout = xConnectionContext.getValueByName("Timeout")
106        if dbg:
107            print(isinstance(tout,int), file=sys.stderr)
108        if not isinstance(tout,int):
109            tout = _GLOBAL_DEFAULT_TIMEOUT
110        if dbg:
111            print("Timeout: %s" % str(tout), file=sys.stderr)
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("ConnectionType: %s" % connectiontype, file=sys.stderr)
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('Logging in, username of', user, file=sys.stderr)
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("PyMailSMPTService disconnect", file=sys.stderr)
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("PyMailSMPTService isConnected", file=sys.stderr)
146        return self.server != None
147    def getCurrentConnectionContext(self):
148        if dbg:
149            print("PyMailSMPTService getCurrentConnectionContext", file=sys.stderr)
150        return self.connectioncontext
151    def sendMailMessage(self, xMailMessage):
152        COMMASPACE = ', '
153
154        if dbg:
155            print("PyMailSMPTService sendMailMessage", file=sys.stderr)
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("PyMailSMPTService subject", subject, file=sys.stderr)
164            print("PyMailSMPTService from", sendername.encode('utf-8'), file=sys.stderr)
165            print("PyMailSMTPService from", sendermail, file=sys.stderr)
166            print("PyMailSMPTService send to", recipients, file=sys.stderr)
167
168        attachments = xMailMessage.getAttachments()
169
170        textmsg = Message()
171
172        content = xMailMessage.Body
173        flavors = content.getTransferDataFlavors()
174        if dbg:
175            print("PyMailSMPTService flavors len", len(flavors), file=sys.stderr)
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("PyMailSMPTService mimetype is", flavor.MimeType, file=sys.stderr)
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 = list(uniquer.keys())
262
263        if dbg:
264            print("PyMailSMPTService recipients are", truerecipients, file=sys.stderr)
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("PyMailIMAPService init", file=sys.stderr)
278    def addConnectionListener(self, xListener):
279        if dbg:
280            print("PyMailIMAPService addConnectionListener", file=sys.stderr)
281        self.listeners.append(xListener)
282    def removeConnectionListener(self, xListener):
283        if dbg:
284            print("PyMailIMAPService removeConnectionListener", file=sys.stderr)
285        self.listeners.remove(xListener)
286    def getSupportedConnectionTypes(self):
287        if dbg:
288            print("PyMailIMAPService getSupportedConnectionTypes", file=sys.stderr)
289        return self.supportedtypes
290    def connect(self, xConnectionContext, xAuthenticator):
291        if dbg:
292            print("PyMailIMAPService connect", file=sys.stderr)
293
294        self.connectioncontext = xConnectionContext
295        server = xConnectionContext.getValueByName("ServerName")
296        if dbg:
297            print(server, file=sys.stderr)
298        port = xConnectionContext.getValueByName("Port")
299        if dbg:
300            print(port, file=sys.stderr)
301        connectiontype = xConnectionContext.getValueByName("ConnectionType")
302        if dbg:
303            print(connectiontype, file=sys.stderr)
304        print("BEFORE", file=sys.stderr)
305        if connectiontype.upper() == 'SSL':
306            self.server = imaplib.IMAP4_SSL(server, port)
307        else:
308            self.server = imaplib.IMAP4(server, port)
309        print("AFTER", file=sys.stderr)
310
311        user = xAuthenticator.getUserName().encode('ascii')
312        password = xAuthenticator.getPassword().encode('ascii')
313        if user != '':
314            if dbg:
315                print('Logging in, username of', user, file=sys.stderr)
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("PyMailIMAPService disconnect", file=sys.stderr)
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("PyMailIMAPService isConnected", file=sys.stderr)
331        return self.server != None
332    def getCurrentConnectionContext(self):
333        if dbg:
334            print("PyMailIMAPService getCurrentConnectionContext", file=sys.stderr)
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("PyMailPOP3Service init", file=sys.stderr)
347    def addConnectionListener(self, xListener):
348        if dbg:
349            print("PyMailPOP3Service addConnectionListener", file=sys.stderr)
350        self.listeners.append(xListener)
351    def removeConnectionListener(self, xListener):
352        if dbg:
353            print("PyMailPOP3Service removeConnectionListener", file=sys.stderr)
354        self.listeners.remove(xListener)
355    def getSupportedConnectionTypes(self):
356        if dbg:
357            print("PyMailPOP3Service getSupportedConnectionTypes", file=sys.stderr)
358        return self.supportedtypes
359    def connect(self, xConnectionContext, xAuthenticator):
360        if dbg:
361            print("PyMailPOP3Service connect", file=sys.stderr)
362
363        self.connectioncontext = xConnectionContext
364        server = xConnectionContext.getValueByName("ServerName")
365        if dbg:
366            print(server, file=sys.stderr)
367        port = xConnectionContext.getValueByName("Port")
368        if dbg:
369            print(port, file=sys.stderr)
370        connectiontype = xConnectionContext.getValueByName("ConnectionType")
371        if dbg:
372            print(connectiontype, file=sys.stderr)
373        print("BEFORE", file=sys.stderr)
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(isinstance(tout,int), file=sys.stderr)
380            if not isinstance(tout,int):
381                tout = _GLOBAL_DEFAULT_TIMEOUT
382            if dbg:
383                print("Timeout: %s" % str(tout), file=sys.stderr)
384            self.server = poplib.POP3(server, port, timeout=tout)
385        print("AFTER", file=sys.stderr)
386
387        user = xAuthenticator.getUserName().encode('ascii')
388        password = xAuthenticator.getPassword().encode('ascii')
389        if dbg:
390            print('Logging in, username of', user, file=sys.stderr)
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("PyMailPOP3Service disconnect", file=sys.stderr)
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("PyMailPOP3Service isConnected", file=sys.stderr)
407        return self.server != None
408    def getCurrentConnectionContext(self):
409        if dbg:
410            print("PyMailPOP3Service getCurrentConnectionContext", file=sys.stderr)
411        return self.connectioncontext
412
413class PyMailServiceProvider(unohelper.Base, XMailServiceProvider):
414    def __init__( self, ctx ):
415        if dbg:
416            print("PyMailServiceProvider init", file=sys.stderr)
417        self.ctx = ctx
418    def create(self, aType):
419        if dbg:
420            print("PyMailServiceProvider create with", aType, file=sys.stderr)
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("PyMailServiceProvider, unknown TYPE", aType, file=sys.stderr)
429
430class PyMailMessage(unohelper.Base, XMailMessage):
431    def __init__( self, ctx, sTo='', sFrom='', Subject='', Body=None, aMailAttachment=None ):
432        if dbg:
433            print("PyMailMessage init", file=sys.stderr)
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("post PyMailMessage init", file=sys.stderr)
449    def addRecipient( self, recipient ):
450        if dbg:
451            print("PyMailMessage.addRecipient", recipient, file=sys.stderr)
452        self.recipients.append(recipient)
453    def addCcRecipient( self, ccrecipient ):
454        if dbg:
455            print("PyMailMessage.addCcRecipient", ccrecipient, file=sys.stderr)
456        self.ccrecipients.append(ccrecipient)
457    def addBccRecipient( self, bccrecipient ):
458        if dbg:
459            print("PyMailMessage.addBccRecipient", bccrecipient, file=sys.stderr)
460        self.bccrecipients.append(bccrecipient)
461    def getRecipients( self ):
462        if dbg:
463            print("PyMailMessage.getRecipients", self.recipients, file=sys.stderr)
464        return tuple(self.recipients)
465    def getCcRecipients( self ):
466        if dbg:
467            print("PyMailMessage.getCcRecipients", self.ccrecipients, file=sys.stderr)
468        return tuple(self.ccrecipients)
469    def getBccRecipients( self ):
470        if dbg:
471            print("PyMailMessage.getBccRecipients", self.bccrecipients, file=sys.stderr)
472        return tuple(self.bccrecipients)
473    def addAttachment( self, aMailAttachment ):
474        if dbg:
475            print("PyMailMessage.addAttachment", file=sys.stderr)
476        self.aMailAttachments.append(aMailAttachment)
477    def getAttachments( self ):
478        if dbg:
479            print("PyMailMessage.getAttachments", file=sys.stderr)
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