|
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×tamp=%.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) |