1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2011 W. Trevor King <wking@drexel.edu>
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.
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.
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 from __future__ import absolute_import
20 from email.header import Header as _Header
21 from email.header import decode_header as _decode_header
22 import email.utils as _email_utils
23 import logging as _logging
24 import smtplib as _smtplib
26 import pgp_mime as _pgp_mime
28 from . import ENCODING as _ENCODING
29 from . import LOG as _LOG
30 from .color import standard_colors as _standard_colors
31 from .color import color_string as _color_string
32 from .color import write_color as _write_color
33 from .model.person import Person as _Person
36 def test_smtp(smtp, author, targets, msg=None):
37 """Test the SMTP connection by sending a message to `target`
40 msg = _pgp_mime.encodedMIMEText('Success!')
41 msg['Date'] = _email_utils.formatdate()
43 msg['Reply-to'] = msg['From']
44 msg['To'] = ', '.join(targets)
45 msg['Subject'] = 'Testing pygrader SMTP connection'
46 _LOG.info('send test message to SMTP server')
47 smtp.send_message(msg=msg)
48 test_smtp.__test__ = False # not a test for nose
50 def send_emails(emails, smtp=None, use_color=None, debug_target=None,
52 """Iterate through `emails` and mail them off one-by-one
54 >>> from email.mime.text import MIMEText
55 >>> from sys import stdout
57 >>> for target in ['Moneypenny <mp@sis.gov.uk>', 'M <m@sis.gov.uk>']:
58 ... msg = MIMEText('howdy!', 'plain', 'us-ascii')
59 ... msg['From'] = 'John Doe <jdoe@a.gov.ru>'
60 ... msg['To'] = target
61 ... msg['Bcc'] = 'James Bond <007@sis.gov.uk>'
64 ... lambda status: stdout.write('SUCCESS: {}\\n'.format(status))))
65 >>> send_emails(emails, use_color=False, dry_run=True)
66 ... # doctest: +REPORT_UDIFF, +NORMALIZE_WHITESPACE
67 sending message to ['Moneypenny <mp@sis.gov.uk>', 'James Bond <007@sis.gov.uk>']...\tDRY-RUN
69 sending message to ['M <m@sis.gov.uk>', 'James Bond <007@sis.gov.uk>']...\tDRY-RUN
72 local_smtp = smtp is None
73 highlight,lowlight,good,bad = _standard_colors(use_color=use_color)
74 for msg,callback in emails:
76 _email_utils.formataddr(a) for a in _pgp_mime.email_sources(msg)]
79 _email_utils.formataddr(a) for a in _pgp_mime.email_targets(msg)]
80 _pgp_mime.strip_bcc(msg)
81 if _LOG.level <= _logging.DEBUG:
82 # TODO: remove convert_content_transfer_encoding?
83 #if msg.get('content-transfer-encoding', None) == 'base64':
84 # convert_content_transfer_encoding(msg, '8bit')
85 _LOG.debug(_color_string(
86 '\n{}\n'.format(msg.as_string()), color=lowlight))
87 _write_color('sending message to {}...'.format(targets),
92 smtp = _smtplib.SMTP('localhost')
94 targets = [debug_target]
95 smtp.sendmail(author, targets, msg.as_string())
99 _write_color('\tFAILED\n', bad)
104 _write_color('\tOK\n', good)
108 _write_color('\tDRY-RUN\n', good)
112 def get_address(person, header=False):
114 >>> from pygrader.model.person import Person as Person
115 >>> p = Person(name='Jack', emails=['a@b.net'])
119 Here's a simple unicode example.
125 When you encode addresses that you intend to place in an email
126 header, you should set the `header` option to `True`. This
127 encodes the name portion of the address without encoding the email
130 >>> get_address(p, header=True)
131 '=?utf-8?b?4pyJ?= <a@b.net>'
133 Note that the address is in the clear. Without the `header`
134 option you'd have to rely on something like:
136 >>> from email.header import Header
137 >>> Header(get_address(p), 'utf-8').encode()
138 '=?utf-8?b?4pyJIDxhQGIubmV0Pg==?='
140 This can cause trouble when your mailer tries to decode the name
141 following :RFC:`2822`, which limits the locations in which encoded
145 encoding = _pgp_mime.guess_encoding(person.name)
146 if encoding == 'us-ascii':
149 name = _Header(person.name, encoding).encode()
150 return _email_utils.formataddr((name, person.emails[0]))
151 return _email_utils.formataddr((person.name, person.emails[0]))
153 def construct_email(author, targets, subject, text, cc=None, sign=True):
154 r"""Built a text/plain email using `Person` instances
156 >>> from pygrader.model.person import Person as Person
157 >>> author = Person(name='Джон Доу', emails=['jdoe@a.gov.ru'])
158 >>> targets = [Person(name='Jill', emails=['c@d.net'])]
159 >>> cc = [Person(name='H.D.', emails=['hd@wall.net'])]
160 >>> msg = construct_email(author, targets, cc=cc,
161 ... subject='Once upon a time', text='Bla bla bla...')
162 >>> print(msg.as_string()) # doctest: +REPORT_UDIFF, +ELLIPSIS
163 Content-Type: text/plain; charset="us-ascii"
165 Content-Transfer-Encoding: 7bit
166 Content-Disposition: inline
168 From: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <jdoe@a.gov.ru>
169 Reply-to: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <jdoe@a.gov.ru>
171 Cc: "H.D." <hd@wall.net>
172 Subject: Once upon a time
178 >>> msg = construct_email(author, targets, cc=cc,
179 ... subject='Once upon a time', text='Funky ✉.')
180 >>> print(msg.as_string()) # doctest: +REPORT_UDIFF, +ELLIPSIS
181 Content-Type: text/plain; charset="utf-8"
183 Content-Transfer-Encoding: base64
184 Content-Disposition: inline
186 From: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <jdoe@a.gov.ru>
187 Reply-to: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <jdoe@a.gov.ru>
189 Cc: "H.D." <hd@wall.net>
190 Subject: Once upon a time
195 msg = _pgp_mime.encodedMIMEText(text)
196 if sign and author.pgp_key:
197 msg = _pgp_mime.sign(message=msg, sign_as=author.pgp_key)
199 msg['Date'] = _email_utils.formatdate()
200 msg['From'] = get_address(author, header=True)
201 msg['Reply-to'] = msg['From']
202 msg['To'] = ', '.join(
203 get_address(target, header=True) for target in targets)
205 msg['Cc'] = ', '.join(
206 get_address(target, header=True) for target in cc)
207 subject_encoding = _pgp_mime.guess_encoding(subject)
208 if subject_encoding == 'us-ascii':
209 msg['Subject'] = subject
211 msg['Subject'] = _Header(subject, subject_encoding)