mail.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """Common utilies to format / send emails."""
       
    19 
       
    20 __docformat__ = "restructuredtext en"
       
    21 
       
    22 from base64 import b64encode, b64decode
       
    23 from time import time
       
    24 from email.mime.multipart import MIMEMultipart
       
    25 from email.mime.text import MIMEText
       
    26 from email.mime.image import MIMEImage
       
    27 from email.header import Header
       
    28 from email.utils import formatdate
       
    29 from socket import gethostname
       
    30 
       
    31 from six import PY2, PY3, text_type
       
    32 
       
    33 
       
    34 def header(ustring):
       
    35     if PY3:
       
    36         return Header(ustring, 'utf-8')
       
    37     return Header(ustring.encode('UTF-8'), 'UTF-8')
       
    38 
       
    39 def addrheader(uaddr, uname=None):
       
    40     # even if an email address should be ascii, encode it using utf8 since
       
    41     # automatic tests may generate non ascii email address
       
    42     if PY2:
       
    43         addr = uaddr.encode('UTF-8')
       
    44     else:
       
    45         addr = uaddr
       
    46     if uname:
       
    47         val = '%s <%s>' % (header(uname).encode(), addr)
       
    48     else:
       
    49         val = addr
       
    50     assert isinstance(val, str)  # bytes in py2, ascii-encoded unicode in py3
       
    51     return val
       
    52 
       
    53 
       
    54 def construct_message_id(appid, eid, withtimestamp=True):
       
    55     if withtimestamp:
       
    56         addrpart = 'eid=%s&timestamp=%.10f' % (eid, time())
       
    57     else:
       
    58         addrpart = 'eid=%s' % eid
       
    59     # we don't want any equal sign nor trailing newlines
       
    60     leftpart = b64encode(addrpart.encode('ascii'), b'.-').decode('ascii').rstrip().rstrip('=')
       
    61     return '<%s@%s.%s>' % (leftpart, appid, gethostname())
       
    62 
       
    63 
       
    64 def parse_message_id(msgid, appid):
       
    65     if msgid[0] == '<':
       
    66         msgid = msgid[1:]
       
    67     if msgid[-1] == '>':
       
    68         msgid = msgid[:-1]
       
    69     try:
       
    70         values, qualif = msgid.split('@')
       
    71         padding = len(values) % 4
       
    72         values = b64decode(str(values + '='*padding), '.-').decode('ascii')
       
    73         values = dict(v.split('=') for v in values.split('&'))
       
    74         fromappid, host = qualif.split('.', 1)
       
    75     except Exception:
       
    76         return None
       
    77     if appid != fromappid or host != gethostname():
       
    78         return None
       
    79     return values
       
    80 
       
    81 
       
    82 def format_mail(uinfo, to_addrs, content, subject="",
       
    83                 cc_addrs=(), msgid=None, references=(), config=None):
       
    84     """Sends an Email to 'e_addr' with content 'content', and subject 'subject'
       
    85 
       
    86     to_addrs and cc_addrs are expected to be a list of email address without
       
    87     name
       
    88     """
       
    89     assert isinstance(content, text_type), repr(content)
       
    90     msg = MIMEText(content.encode('UTF-8'), 'plain', 'UTF-8')
       
    91     # safety: keep only the first newline
       
    92     try:
       
    93         subject = subject.splitlines()[0]
       
    94         msg['Subject'] = header(subject)
       
    95     except IndexError:
       
    96         pass # no subject
       
    97     if uinfo.get('email'):
       
    98         email = uinfo['email']
       
    99     elif config and config['sender-addr']:
       
   100         email = text_type(config['sender-addr'])
       
   101     else:
       
   102         email = u''
       
   103     if uinfo.get('name'):
       
   104         name = uinfo['name']
       
   105     elif config and config['sender-name']:
       
   106         name = text_type(config['sender-name'])
       
   107     else:
       
   108         name = u''
       
   109     msg['From'] = addrheader(email, name)
       
   110     if config and config['sender-addr'] and config['sender-addr'] != email:
       
   111         appaddr = addrheader(config['sender-addr'], config['sender-name'])
       
   112         msg['Reply-to'] = '%s, %s' % (msg['From'], appaddr)
       
   113     elif email:
       
   114         msg['Reply-to'] = msg['From']
       
   115     if config is not None:
       
   116         msg['X-CW'] = config.appid
       
   117     unique_addrs = lambda addrs: sorted(set(addr for addr in addrs if addr is not None))
       
   118     msg['To'] = ', '.join(addrheader(addr) for addr in unique_addrs(to_addrs))
       
   119     if cc_addrs:
       
   120         msg['Cc'] = ', '.join(addrheader(addr) for addr in unique_addrs(cc_addrs))
       
   121     if msgid:
       
   122         msg['Message-id'] = msgid
       
   123     if references:
       
   124         msg['References'] = ', '.join(references)
       
   125     msg['Date'] = formatdate()
       
   126     return msg
       
   127 
       
   128 
       
   129 class HtmlEmail(MIMEMultipart):
       
   130 
       
   131     def __init__(self, subject, textcontent, htmlcontent,
       
   132                  sendermail=None, sendername=None, recipients=None, ccrecipients=None):
       
   133         MIMEMultipart.__init__(self, 'related')
       
   134         self['Subject'] = header(subject)
       
   135         self.preamble = 'This is a multi-part message in MIME format.'
       
   136         # Attach alternative text message
       
   137         alternative = MIMEMultipart('alternative')
       
   138         self.attach(alternative)
       
   139         msgtext = MIMEText(textcontent.encode('UTF-8'), 'plain', 'UTF-8')
       
   140         alternative.attach(msgtext)
       
   141         # Attach html message
       
   142         msghtml = MIMEText(htmlcontent.encode('UTF-8'), 'html', 'UTF-8')
       
   143         alternative.attach(msghtml)
       
   144         if sendermail or sendername:
       
   145             self['From'] = addrheader(sendermail, sendername)
       
   146         if recipients:
       
   147             self['To'] = ', '.join(addrheader(addr) for addr in recipients if addr is not None)
       
   148         if ccrecipients:
       
   149             self['Cc'] = ', '.join(addrheader(addr) for addr in ccrecipients if addr is not None)
       
   150 
       
   151     def attach_image(self, data, htmlId):
       
   152         image = MIMEImage(data)
       
   153         image.add_header('Content-ID', '<%s>' % htmlId)
       
   154         self.attach(image)