Merge branch 'stable'
[mediagoblin.git] / mediagoblin / tools / mail.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
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 Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 from __future__ import print_function, unicode_literals
18
19 import six
20 import smtplib
21 import sys
22 from mediagoblin import mg_globals, messages
23 from mediagoblin._compat import MIMEText
24 from mediagoblin.tools import common
25
26 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27 ### Special email test stuff begins HERE
28 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29
30 # We have two "test inboxes" here:
31 #
32 # EMAIL_TEST_INBOX:
33 # ----------------
34 # If you're writing test views, you'll probably want to check this.
35 # It contains a list of MIMEText messages.
36 #
37 # EMAIL_TEST_MBOX_INBOX:
38 # ----------------------
39 # This collects the messages from the FakeMhost inbox. It's reslly
40 # just here for testing the send_email method itself.
41 #
42 # Anyway this contains:
43 # - from
44 # - to: a list of email recipient addresses
45 # - message: not just the body, but the whole message, including
46 # headers, etc.
47 #
48 # ***IMPORTANT!***
49 # ----------------
50 # Before running tests that call functions which send email, you should
51 # always call _clear_test_inboxes() to "wipe" the inboxes clean.
52
53 EMAIL_TEST_INBOX = []
54 EMAIL_TEST_MBOX_INBOX = []
55
56
57 class FakeMhost(object):
58 """
59 Just a fake mail host so we can capture and test messages
60 from send_email
61 """
62 def login(self, *args, **kwargs):
63 pass
64
65 def sendmail(self, from_addr, to_addrs, message):
66 EMAIL_TEST_MBOX_INBOX.append(
67 {'from': from_addr,
68 'to': to_addrs,
69 'message': message})
70
71 def starttls(self):
72 raise smtplib.SMTPException("No STARTTLS here")
73
74 def _clear_test_inboxes():
75 global EMAIL_TEST_INBOX
76 global EMAIL_TEST_MBOX_INBOX
77 EMAIL_TEST_INBOX = []
78 EMAIL_TEST_MBOX_INBOX = []
79
80
81 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82 ### </Special email test stuff>
83 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
84
85 def send_email(from_addr, to_addrs, subject, message_body):
86 """
87 Simple email sending wrapper, use this so we can capture messages
88 for unit testing purposes.
89
90 Args:
91 - from_addr: address you're sending the email from
92 - to_addrs: list of recipient email addresses
93 - subject: subject of the email
94 - message_body: email body text
95 """
96 if common.TESTS_ENABLED or mg_globals.app_config['email_debug_mode']:
97 mhost = FakeMhost()
98 elif not mg_globals.app_config['email_debug_mode']:
99 if mg_globals.app_config['email_smtp_use_ssl']:
100 smtp_init = smtplib.SMTP_SSL
101 else:
102 smtp_init = smtplib.SMTP
103
104 mhost = smtp_init(
105 mg_globals.app_config['email_smtp_host'],
106 mg_globals.app_config['email_smtp_port'])
107
108 # SMTP.__init__ Issues SMTP.connect implicitly if host
109 if not mg_globals.app_config['email_smtp_host']: # e.g. host = ''
110 mhost.connect() # We SMTP.connect explicitly
111
112 try:
113 mhost.starttls()
114 except smtplib.SMTPException:
115 # Only raise an exception if we're forced to
116 if mg_globals.app_config['email_smtp_force_starttls']:
117 six.reraise(*sys.exc_info())
118
119 if ((not common.TESTS_ENABLED)
120 and (mg_globals.app_config['email_smtp_user']
121 or mg_globals.app_config['email_smtp_pass'])):
122 mhost.login(
123 mg_globals.app_config['email_smtp_user'],
124 mg_globals.app_config['email_smtp_pass'])
125
126 message = MIMEText(message_body.encode('utf-8'), 'plain', 'utf-8')
127 message['Subject'] = subject
128 message['From'] = from_addr
129 message['To'] = ', '.join(to_addrs)
130
131 if common.TESTS_ENABLED:
132 EMAIL_TEST_INBOX.append(message)
133
134 elif mg_globals.app_config['email_debug_mode']:
135 print("===== Email =====")
136 print("From address: %s" % message['From'])
137 print("To addresses: %s" % message['To'])
138 print("Subject: %s" % message['Subject'])
139 print("-- Body: --")
140 print(message.get_payload(decode=True))
141
142 return mhost.sendmail(from_addr, to_addrs, message.as_string())
143
144
145 def normalize_email(email):
146 """return case sensitive part, lower case domain name
147
148 :returns: None in case of broken email addresses"""
149 try:
150 em_user, em_dom = email.split('@', 1)
151 except ValueError:
152 # email contained no '@'
153 return None
154 email = "@".join((em_user, em_dom.lower()))
155 return email
156
157
158 def email_debug_message(request):
159 """
160 If the server is running in email debug mode (which is
161 the current default), give a debug message to the user
162 so that they have an idea where to find their email.
163 """
164 if mg_globals.app_config['email_debug_mode']:
165 # DEBUG message, no need to translate
166 messages.add_message(request, messages.DEBUG,
167 "This instance is running in email debug mode. "
168 "The email will be on the console of the server process.")