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