source: main/waeup.kofa/trunk/src/waeup/kofa/smtp.py @ 7933

Last change on this file since 7933 was 7819, checked in by Henrik Bettermann, 13 years ago

KOFA -> Kofa

  • Property svn:keywords set to Id
File size: 5.4 KB
RevLine 
[7476]1## $Id: smtp.py 7819 2012-03-08 22:28:46Z henrik $
[7470]2##
[7476]3## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
[7470]4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
[7476]8##
[7470]9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
[7476]13##
[7470]14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
[7819]19Email (SMTP) services for Kofa.
[7470]20
21Note About Encodings
22--------------------
23
24All functions in this module expect any raw strings passed in to be
25encoded 'utf-8' (if you pass in unicode objects instead, this is not a
26problem).
27
28This is because we cannot easily tell from a raw string (it is in fact
29only a byte stream) what encoding it has. In latin-1 and utf-8, for
30instance, there exist some chars (byte values) that have different
31meanings in both encodings. If we see such a char in a byte stream:
32what is it meant to represent? The respective character from latin1 or
33the one from utf-8?
34
35We therefore interpret all internally used raw strings to be encoded as
36utf-8.
37
38The functions in here nevertheless try hard to produce output (mail
39parts, headers, etc.) encoded in the least complex manner. For
40instance if you pass in some address or mail body that is
41representable (correctly) as ASCII or latin-1, we will turn the text
42into that encoding (given, you passed it in as utf-8) to stay as
43compatible as possible with old mailers that do not understand utf-8.
44
45"""
46import grok
47import logging
48from email.Header import Header
[7474]49from email.Utils import formataddr
[7470]50from email.mime.text import MIMEText
51from zope.component import getUtility
52from zope.sendmail.interfaces import IMailDelivery
[7811]53from waeup.kofa.interfaces import IMailService
[7470]54
55class DefaultMailService(grok.GlobalUtility):
56    """Returns a :class:`zope.sendmail.IMailDelivery`.
57
58    Searches a site from current request (if applicable) and returns
59    the mail delivery set for this site or a fake mailer that does not
60    really send mail (for testing, evaluating, etc.).
61    """
62    grok.implements(IMailService)
63
64    def __call__(self):
65        name = 'No email service'
66        site = grok.getSite()
67        if site is not None:
68            config = site['configuration']
69            name = getattr(config, 'smtp_mailer', name)
70        return getUtility(IMailDelivery, name=name)
71
72class FakeSMTPDelivery(grok.GlobalUtility):
73    """A fake mail delivery for testing etc.
74
75    Instead of sending real mails, this mailer only logs received
76    messages to the ``test.smtp`` logger.
77    """
78    grok.implements(IMailDelivery)
79    grok.name('No email service')
80
81    def send(self, fromaddr, toaddrs, message):
82        logger = logging.getLogger('test.smtp')
83        rcpts = ', '.join([x.decode('utf-8') for x in toaddrs])
84        logger.info(
85            u"Sending email from %s to %s:" % (
86                fromaddr.decode('utf-8'), rcpts))
87        logger.info(u"Message:")
88        for line in message.split('\n'):
89            logger.info(u"msg: " + line.decode('utf-8'))
90        return 'fake-message-id@example.com'
91
92CHARSETS = ('US-ASCII', 'ISO-8859-1', 'UTF-8')
93
94def encode_header_item(item):
95    if not isinstance(item, unicode):
96        item = unicode(item, 'utf-8')
[7472]97    return str(Header(item, 'iso-8859-1')) # try ascii, then latin1, then utf-8
[7470]98
99def encode_address(addr, name=u''):
100    """Turn email address parts into a single valid email string.
101
102    The given email address and the name are turned into a single
103    (byte stream) string, suitable for use with ``To:`` or ``From:``
104    headers in emails.
105
106    Any encodings to a mailer-readable format are performed.
107
108    Preferred input format is unicode, although also raw strings (byte
109    streams) work as long as they are decodable from UTF-8.
110
111    That means: if you pass in non-unicode string, take care to
112    deliver utf-8 encoded strings (or plain ASCII).
113
114    Returns a single (raw) string like "My Name <my@sample.com>".
115    """
116    addr = encode_header_item(addr)
117    name = encode_header_item(name)
118    return formataddr((name, addr))
119
120def encode_body(text):
121    """Build MIME message part from text.
122
123    You can pass unicode objects or simple strings as text.
124
125    .. warn:: If the input is a simple string, this string is expected
126              to be encoded 'utf-8'!
127
128    Returns a MIMEText object.
129    """
130    if not isinstance(text, unicode):
131        text = unicode(text, 'utf-8')
132    charset = CHARSETS[-1] # fallback
133    for charset in CHARSETS:
134        try:
135            text = text.encode(charset)
136        except UnicodeError:
137            pass # try next encoding
138        else:
139            break
140    return MIMEText(text, 'plain', charset)
141
142def send_mail(from_name, from_addr, rcpt_name, rcpt_addr,
143              subject, body, config=None):
144    """Send mail.
145    """
146    # format message
147    body = encode_body(body)
148    body["From"] = encode_address(from_addr, from_name)
149    body["To"] = encode_address(rcpt_addr, rcpt_name)
150    body["Subject"] = encode_header_item(subject)
151
152    mailer = getUtility(IMailService)
153    result = mailer().send(from_addr, [rcpt_addr], body.as_string())
154    return result
Note: See TracBrowser for help on using the repository browser.