source: main/waeup.sirp/trunk/src/waeup/sirp/smtp.py @ 7474

Last change on this file since 7474 was 7474, checked in by uli, 13 years ago

pyflakes.

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