Skip to content

Commit 34332a7

Browse files
author
Johannes Gijsbers
committed
Extracted duplicated mail-sending code...
...from mailgw, roundupdb and client.py to the new mailer.py module.
1 parent 7664878 commit 34332a7

File tree

4 files changed

+186
-207
lines changed

4 files changed

+186
-207
lines changed

roundup/cgi/client.py

Lines changed: 14 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: client.py,v 1.135 2003-09-07 22:12:24 richard Exp $
1+
# $Id: client.py,v 1.136 2003-09-08 09:28:28 jlgijsbers Exp $
22

33
__doc__ = """
44
WWW request handler (also used in the stand-alone server).
@@ -14,7 +14,8 @@
1414
from roundup.cgi import cgitb
1515
from roundup.cgi.PageTemplates import PageTemplate
1616
from roundup.rfc2822 import encode_header
17-
from roundup.mailgw import uidFromAddress, openSMTPConnection
17+
from roundup.mailgw import uidFromAddress
18+
from roundup.mailer import Mailer, MessageSendError
1819

1920
class HTTPException(Exception):
2021
pass
@@ -27,10 +28,6 @@ class Redirect(HTTPException):
2728
class NotModified(HTTPException):
2829
pass
2930

30-
# set to indicate to roundup not to actually _send_ email
31-
# this var must contain a file to write the mail to
32-
SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '')
33-
3431
# used by a couple of routines
3532
if hasattr(string, 'ascii_letters'):
3633
chars = string.ascii_letters+string.digits
@@ -164,6 +161,7 @@ def __init__(self, instance, request, env, form=None):
164161
self.instance = instance
165162
self.request = request
166163
self.env = env
164+
self.mailer = Mailer(instance.config)
167165

168166
# save off the path
169167
self.path = env['PATH_INFO']
@@ -776,7 +774,7 @@ def registerAction(self):
776774
%(url)s?@action=confrego&otk=%(otk)s
777775
'''%{'name': props['username'], 'tracker': tracker_name, 'url': self.base,
778776
'otk': otk}
779-
if not self.sendEmail(props['address'], subject, body):
777+
if not self.standard_message(props['address'], subject, body):
780778
return
781779

782780
# commit changes to the database
@@ -785,49 +783,13 @@ def registerAction(self):
785783
# redirect to the "you're almost there" page
786784
raise Redirect, '%suser?@template=rego_progress'%self.base
787785

788-
def sendEmail(self, to, subject, content):
789-
# send email to the user's email address
790-
message = StringIO.StringIO()
791-
writer = MimeWriter.MimeWriter(message)
792-
tracker_name = self.db.config.TRACKER_NAME
793-
writer.addheader('Subject', encode_header(subject))
794-
writer.addheader('To', to)
795-
writer.addheader('From', roundupdb.straddr((tracker_name,
796-
self.db.config.ADMIN_EMAIL)))
797-
writer.addheader('Date', time.strftime("%a, %d %b %Y %H:%M:%S +0000",
798-
time.gmtime()))
799-
# add a uniquely Roundup header to help filtering
800-
writer.addheader('X-Roundup-Name', tracker_name)
801-
# avoid email loops
802-
writer.addheader('X-Roundup-Loop', 'hello')
803-
writer.addheader('Content-Transfer-Encoding', 'quoted-printable')
804-
body = writer.startbody('text/plain; charset=utf-8')
805-
806-
# message body, encoded quoted-printable
807-
content = StringIO.StringIO(content)
808-
quopri.encode(content, body, 0)
809-
810-
if SENDMAILDEBUG:
811-
# don't send - just write to a file
812-
open(SENDMAILDEBUG, 'a').write('FROM: %s\nTO: %s\n%s\n'%(
813-
self.db.config.ADMIN_EMAIL,
814-
', '.join(to),message.getvalue()))
815-
else:
816-
# now try to send the message
817-
try:
818-
# send the message as admin so bounces are sent there
819-
# instead of to roundup
820-
smtp = openSMTPConnection(self.db.config)
821-
smtp.sendmail(self.db.config.ADMIN_EMAIL, [to],
822-
message.getvalue())
823-
except socket.error, value:
824-
self.error_message.append("Error: couldn't send email: "
825-
"mailhost %s"%value)
826-
return 0
827-
except smtplib.SMTPException, msg:
828-
self.error_message.append("Error: couldn't send email: %s"%msg)
829-
return 0
830-
return 1
786+
def standard_message(self, to, subject, body):
787+
try:
788+
self.mailer.standard_message(to, subject, body)
789+
return 1
790+
except MessageSendException, e:
791+
self.error_message.append(str(e))
792+
831793

832794
def registerPermission(self, props):
833795
''' Determine whether the user has permission to register
@@ -917,7 +879,7 @@ def passResetAction(self):
917879
918880
Your password is now: %(password)s
919881
'''%{'name': name, 'password': newpw}
920-
if not self.sendEmail(address, subject, body):
882+
if not self.standard_message(address, subject, body):
921883
return
922884

923885
self.ok_message.append('Password reset and email sent to %s'%address)
@@ -960,7 +922,7 @@ def passResetAction(self):
960922
961923
You should then receive another email with the new password.
962924
'''%{'name': name, 'tracker': tracker_name, 'url': self.base, 'otk': otk}
963-
if not self.sendEmail(address, subject, body):
925+
if not self.standard_message(address, subject, body):
964926
return
965927

966928
self.ok_message.append('Email sent to %s'%address)

roundup/mailer.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"""Sending Roundup-specific mail over SMTP."""
2+
# $Id: mailer.py,v 1.1 2003-09-08 09:28:28 jlgijsbers Exp $
3+
4+
import time, quopri, os, socket, smtplib, re
5+
6+
from cStringIO import StringIO
7+
from MimeWriter import MimeWriter
8+
9+
from roundup.rfc2822 import encode_header
10+
11+
class MessageSendError(RuntimeError):
12+
pass
13+
14+
class Mailer:
15+
"""Roundup-specific mail sending."""
16+
def __init__(self, config):
17+
self.config = config
18+
19+
# set to indicate to roundup not to actually _send_ email
20+
# this var must contain a file to write the mail to
21+
self.debug = os.environ.get('SENDMAILDEBUG', '')
22+
23+
def get_standard_message(self, to, subject, author=None):
24+
if not author:
25+
author = straddr((self.config.TRACKER_NAME,
26+
self.config.ADMIN_EMAIL))
27+
message = StringIO()
28+
writer = MimeWriter(message)
29+
writer.addheader('Subject', encode_header(subject))
30+
writer.addheader('To', to)
31+
writer.addheader('From', author)
32+
writer.addheader('Date', time.strftime("%a, %d %b %Y %H:%M:%S +0000",
33+
time.gmtime()))
34+
35+
# Add a unique Roundup header to help filtering
36+
writer.addheader('X-Roundup-Name', self.config.TRACKER_NAME)
37+
# and another one to avoid loops
38+
writer.addheader('X-Roundup-Loop', 'hello')
39+
40+
writer.addheader('MIME-Version', '1.0')
41+
42+
return message, writer
43+
44+
def standard_message(self, to, subject, content):
45+
message, writer = self.get_standard_message(to, subject)
46+
47+
writer.addheader('Content-Transfer-Encoding', 'quoted-printable')
48+
body = writer.startbody('text/plain; charset=utf-8')
49+
content = StringIO(content)
50+
quopri.encode(content, body, 0)
51+
52+
self.smtp_send(to, message)
53+
54+
def bounce_message(self, bounced_message, to, error,
55+
subject='Failed issue tracker submission'):
56+
message, writer = self.get_standard_message(', '.join(to), subject)
57+
58+
part = writer.startmultipartbody('mixed')
59+
part = writer.nextpart()
60+
part.addheader('Content-Transfer-Encoding', 'quoted-printable')
61+
body = part.startbody('text/plain; charset=utf-8')
62+
body.write('\n'.join(error))
63+
64+
# attach the original message to the returned message
65+
part = writer.nextpart()
66+
part.addheader('Content-Disposition', 'attachment')
67+
part.addheader('Content-Description', 'Message you sent')
68+
body = part.startbody('text/plain')
69+
70+
for header in bounced_message.headers:
71+
body.write(header)
72+
body.write('\n')
73+
try:
74+
bounced_message.rewindbody()
75+
except IOError, message:
76+
body.write("*** couldn't include message body: %s ***"
77+
% bounced_message)
78+
else:
79+
body.write(bounced_message.fp.read())
80+
81+
writer.lastpart()
82+
83+
self.smtp_send(to, message)
84+
85+
def smtp_send(self, to, message):
86+
if self.debug:
87+
# don't send - just write to a file
88+
open(self.debug, 'a').write('FROM: %s\nTO: %s\n%s\n' %
89+
(self.config.ADMIN_EMAIL,
90+
', '.join(to),
91+
message.getvalue()))
92+
else:
93+
# now try to send the message
94+
try:
95+
# send the message as admin so bounces are sent there
96+
# instead of to roundup
97+
smtp = SMTPConnection(self.config)
98+
smtp.sendmail(self.config.ADMIN_EMAIL, [to],
99+
message.getvalue())
100+
except socket.error, value:
101+
raise MessageSendError("Error: couldn't send email: "
102+
"mailhost %s"%value)
103+
except smtplib.SMTPException, msg:
104+
raise MessageSendError("Error: couldn't send email: %s"%msg)
105+
106+
class SMTPConnection(smtplib.SMTP):
107+
''' Open an SMTP connection to the mailhost specified in the config
108+
'''
109+
def __init__(self, config):
110+
111+
smtplib.SMTP.__init__(self, config.MAILHOST)
112+
113+
# use TLS?
114+
use_tls = getattr(config, 'MAILHOST_TLS', 'no')
115+
if use_tls == 'yes':
116+
# do we have key files too?
117+
keyfile = getattr(config, 'MAILHOST_TLS_KEYFILE', '')
118+
if keyfile:
119+
certfile = getattr(config, 'MAILHOST_TLS_CERTFILE', '')
120+
if certfile:
121+
args = (keyfile, certfile)
122+
else:
123+
args = (keyfile, )
124+
else:
125+
args = ()
126+
# start the TLS
127+
self.starttls(*args)
128+
129+
# ok, now do we also need to log in?
130+
mailuser = getattr(config, 'MAILUSER', None)
131+
if mailuser:
132+
self.login(*config.MAILUSER)
133+
134+
# use the 'email' module, either imported, or our copied version
135+
try :
136+
from email.Utils import formataddr as straddr
137+
except ImportError :
138+
# code taken from the email package 2.4.3
139+
def straddr(pair, specialsre = re.compile(r'[][\()<>@,:;".]'),
140+
escapesre = re.compile(r'[][\()"]')):
141+
name, address = pair
142+
if name:
143+
quotes = ''
144+
if specialsre.search(name):
145+
quotes = '"'
146+
name = escapesre.sub(r'\\\g<0>', name)
147+
return '%s%s%s <%s>' % (quotes, name, quotes, address)
148+
return address

0 commit comments

Comments
 (0)