forked from tow/appengine-mailer
-
Notifications
You must be signed in to change notification settings - Fork 1
/
mail.py
157 lines (137 loc) · 5.71 KB
/
mail.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import email.parser, logging, os
from google.appengine.api.mail import EmailMessage, InvalidSenderError
from google.appengine.ext.webapp import RequestHandler
from gmail import Signer
suffixes = dict(line.split()[:2] for line in open('google.mime.types') if not line.startswith('#'))
class BadRequestError(ValueError):
pass
class BadMessageError(ValueError):
pass
class Mailer(object):
def __init__(self, default_sender, fix_sender=False):
self.default_sender = default_sender
self.fix_sender = fix_sender
def send_message(self, msg):
message = self.translate_message(msg)
try:
message.send()
return
except InvalidSenderError:
errmsg = "Unauthorized message sender '%s'" % message.sender
if not self.fix_sender:
raise BadMessageError(errmsg)
else:
logging.info(errmsg)
message.sender = self.default_sender
try:
message.send()
except InvalidSenderError:
raise BadMessageError("Unauthorized default message sender '%s'" % message.sender)
@staticmethod
def get_filename(part):
filename = part.get_filename()
if not filename:
content_type = part.get_content_type()
try:
filename = "file.%s" % suffixes[content_type]
except KeyError:
raise BadMessageError("Google won't let us send content of type '%s'" % content_type)
return filename
def translate_message(self, msg):
sender = msg.get_unixfrom() or msg['From']
if not sender:
if self.fix_sender:
sender = self.default_sender
else:
raise BadMessageError("No sender specified")
to = msg['To']
if not to:
raise BadMessageError("No destination addresses specified")
message = EmailMessage(sender=sender or msg['From'], to=to)
# Go through all the headers which Google will let us use
cc = msg['Cc']
if cc:
message.cc = cc
bcc = msg['Bcc']
if bcc:
message.bcc = cc
reply_to = msg['Reply-To']
if reply_to:
message.reply_to = reply_to
subject = msg['Subject']
if subject:
message.subject = subject
# If there's just a plain text body, use that, otherwise
# iterate over all the attachments
payload = msg.get_payload(decode=True)
if isinstance(payload, basestring):
message.body = payload
else:
body = ''
html = ''
attachments = []
# GAE demands we specify the body explicitly - we use the first text/plain attachment we find.
# Similarly, we pull in the first html we find and use that for message.html
# We pull in any other attachments we find; but we ignore the multipart structure,
# because GAE doesn't give us enough control there.
for part in msg.walk():
if part.get_content_type() == 'text/plain' and not body:
body = part.get_payload(decode=True)
elif part.get_content_type() == 'text/html' and not html:
html = part.get_payload(decode=True)
elif not part.get_content_type().startswith('multipart'):
attachments.append((self.get_filename(part), part.get_payload(decode=True)))
if not body:
raise BadMessageError("No message body specified")
message.body = body
if html:
message.html = html
if attachments:
message.attachments = attachments
return message
class SendMail(RequestHandler):
def __init__(self, *args, **kwargs):
self.GMAIL_SECRET_KEYS = [k.strip() for k in
open('GMAIL_SECRET_KEYS')
if k]
self.default_sender = open('GMAIL_DEFAULT_SENDER').read().strip()
super(SendMail, self).__init__(*args, **kwargs)
def get(self):
# Just so that we can pingdom it to see if it's up.
return
def post(self):
try:
msg_string, fix_sender = self.parse_args()
msg = email.parser.Parser().parsestr(msg_string)
mailer = Mailer(self.default_sender, fix_sender)
mailer.send_message(msg)
logging.info("Sent message ok\n%s" % msg)
self.error(204)
except BadRequestError, e:
logging.error("Malformed request: %s" % e.args[0])
logging.error(str(self.request))
self.error(400)
self.response.out.write(e.args[0])
except BadMessageError, e:
logging.error("Failed to send message: %s" % e.args[0])
logging.error(str(self.request))
self.error(400)
self.response.out.write(e.args[0])
except Exception, e:
logging.exception("Failed to process request")
logging.error(str(self.request))
self.error(500)
def parse_args(self):
msg = self.request.get('msg')
if not msg:
raise BadRequestError("No message found")
signature = self.request.get('signature')
if not signature:
raise BadRequestError("No signature found")
if not self.check_signature(msg, signature):
raise BadRequestError("Signature doesn't match")
msg = str(msg) # email.parser fails on unicode
fix_sender = bool(self.request.get("fix_sender"))
return msg, fix_sender
def check_signature(self, msg, signature):
return Signer(self.GMAIL_SECRET_KEYS).verify_signature(msg, signature)