App Engine Python SDK version 1.8.1
[gae.git] / python / google / appengine / api / mail_stub.py
blobe8df17d737e9785ec8fdf514e8ac0662cdf766f9
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 """Stub version of the mail API, writes email to logs and can optionally
22 send real email via SMTP or sendmail."""
32 from email import MIMEBase
33 from email import MIMEMultipart
34 from email import MIMEText
35 import logging
36 import mail
37 import mimetypes
38 import re
39 import subprocess
40 import smtplib
42 from google.appengine.api import apiproxy_stub
45 MAX_REQUEST_SIZE = 32 << 20
48 class MailServiceStub(apiproxy_stub.APIProxyStub):
49 """Python only mail service stub.
51 This stub does not actually attempt to send email. instead it merely logs
52 a description of the email to the developers console.
54 Args:
55 host: Host of SMTP server to use. Blank disables sending SMTP.
56 port: Port of SMTP server to use.
57 user: User to log in to SMTP server as.
58 password: Password for SMTP server user.
59 """
61 THREADSAFE = True
63 def __init__(self,
64 host=None,
65 port=25,
66 user='',
67 password='',
68 enable_sendmail=False,
69 show_mail_body=False,
70 service_name='mail'):
71 """Constructor.
73 Args:
74 host: Host of SMTP mail server.
75 post: Port of SMTP mail server.
76 user: Sending user of SMTP mail.
77 password: SMTP password.
78 enable_sendmail: Whether sendmail enabled or not.
79 show_mail_body: Whether to show mail body in log.
80 service_name: Service name expected for all calls.
81 """
82 super(MailServiceStub, self).__init__(service_name,
83 max_request_size=MAX_REQUEST_SIZE)
84 self._smtp_host = host
85 self._smtp_port = port
86 self._smtp_user = user
87 self._smtp_password = password
88 self._enable_sendmail = enable_sendmail
89 self._show_mail_body = show_mail_body
91 self._cached_messages = []
93 def _GenerateLog(self, method, message, log):
94 """Generate a list of log messages representing sent mail.
96 Args:
97 message: Message to write to log.
98 log: Log function of type string -> None
99 """
100 log_message = []
101 log_message.append('MailService.%s' % method)
102 log_message.append(' From: %s' % message.sender())
105 for address in message.to_list():
106 log_message.append(' To: %s' % address)
107 for address in message.cc_list():
108 log_message.append(' Cc: %s' % address)
109 for address in message.bcc_list():
110 log_message.append(' Bcc: %s' % address)
112 if message.replyto():
113 log_message.append(' Reply-to: %s' % message.replyto())
116 log_message.append(' Subject: %s' % message.subject())
119 if message.has_textbody():
120 log_message.append(' Body:')
121 log_message.append(' Content-type: text/plain')
122 log_message.append(' Data length: %d' % len(message.textbody()))
123 if self._show_mail_body:
124 log_message.append('-----\n' + message.textbody() + '\n-----')
127 if message.has_htmlbody():
128 log_message.append(' Body:')
129 log_message.append(' Content-type: text/html')
130 log_message.append(' Data length: %d' % len(message.htmlbody()))
131 if self._show_mail_body:
132 log_message.append('-----\n' + message.htmlbody() + '\n-----')
135 for attachment in message.attachment_list():
136 log_message.append(' Attachment:')
137 log_message.append(' File name: %s' % attachment.filename())
138 log_message.append(' Data length: %s' % len(attachment.data()))
140 log('\n'.join(log_message))
142 @apiproxy_stub.Synchronized
143 def _CacheMessage(self, message):
144 """Cache a message that were sent for later inspection.
146 Args:
147 message: Message to cache.
149 self._cached_messages.append(message)
152 @apiproxy_stub.Synchronized
153 def get_sent_messages(self, to=None, sender=None, subject=None, body=None,
154 html=None):
155 """Get a list of mail messages sent via the Mail API.
157 Args:
158 to: A regular expression that at least one recipient must match.
159 sender: A regular expression that the sender must match.
160 subject: A regular expression that the message subject must match.
161 body: A regular expression that the text body must match.
162 html: A regular expression that the HTML body must match.
164 Returns:
165 A list of matching mail.EmailMessage objects.
167 messages = self._cached_messages
169 def recipient_matches(recipient):
170 return re.search(to, recipient)
172 if to:
173 messages = [m for m in messages if filter(recipient_matches, m.to_list())]
174 if sender:
175 messages = [m for m in messages if re.search(sender, m.sender())]
176 if subject:
177 messages = [m for m in messages if re.search(subject, m.subject())]
178 if body:
179 messages = [m for m in messages if re.search(body, m.textbody())]
180 if html:
181 messages = [m for m in messages if re.search(html, m.htmlbody())]
182 mail_messages = []
183 for message in messages:
184 mime_message = mail.mail_message_to_mime_message(message)
185 email_message = mail.EmailMessage(mime_message=mime_message)
186 mail_messages.append(email_message)
187 return mail_messages
189 def _SendSMTP(self, mime_message, smtp_lib=smtplib.SMTP):
190 """Send MIME message via SMTP.
192 Connects to SMTP server and sends MIME message. If user is supplied
193 will try to login to that server to send as authenticated. Does not
194 currently support encryption.
196 Args:
197 mime_message: MimeMessage to send. Create using ToMIMEMessage.
198 smtp_lib: Class of SMTP library. Used for dependency injection.
201 smtp = smtp_lib()
202 try:
203 smtp.connect(self._smtp_host, self._smtp_port)
204 if self._smtp_user:
205 smtp.login(self._smtp_user, self._smtp_password)
208 tos = [mime_message[to] for to in ['To', 'Cc', 'Bcc'] if mime_message[to]]
209 smtp.sendmail(mime_message['From'], tos, mime_message.as_string())
210 finally:
211 smtp.quit()
213 def _SendSendmail(self, mime_message,
214 popen=subprocess.Popen,
215 sendmail_command='sendmail'):
216 """Send MIME message via sendmail, if exists on computer.
218 Attempts to send email via sendmail. Any IO failure, including
219 the program not being found is ignored.
221 Args:
222 mime_message: MimeMessage to send. Create using ToMIMEMessage.
223 popen: popen function to create a new sub-process.
225 try:
229 tos = []
230 for to in ('To', 'Cc', 'Bcc'):
231 if mime_message[to]:
232 tos.extend("'%s'" % addr.strip().replace("'", r"'\''")
233 for addr in mime_message[to].split(','))
235 command = '%s %s' % (sendmail_command, ' '.join(tos))
237 try:
238 child = popen(command,
239 shell=True,
240 stdin=subprocess.PIPE,
241 stdout=subprocess.PIPE)
242 except (IOError, OSError), e:
243 logging.error('Unable to open pipe to sendmail')
244 raise
245 try:
246 child.stdin.write(mime_message.as_string())
247 child.stdin.close()
248 finally:
251 while child.poll() is None:
252 child.stdout.read(100)
253 child.stdout.close()
254 except (IOError, OSError), e:
255 logging.error('Error sending mail using sendmail: ' + str(e))
257 def _Send(self, request, response, log=logging.info,
258 smtp_lib=smtplib.SMTP,
259 popen=subprocess.Popen,
260 sendmail_command='sendmail'):
261 """Implementation of MailServer::Send().
263 Logs email message. Contents of attachments are not shown, only
264 their sizes. If SMTP is configured, will send via SMTP, else
265 will use Sendmail if it is installed.
267 Args:
268 request: The message to send, a SendMailRequest.
269 response: The send response, a SendMailResponse.
270 log: Log function to send log information. Used for dependency
271 injection.
272 smtp_lib: Class of SMTP library. Used for dependency injection.
273 popen2: popen2 function to use for opening pipe to other process.
274 Used for dependency injection.
276 self._CacheMessage(request)
277 self._GenerateLog('Send', request, log)
279 if self._smtp_host and self._enable_sendmail:
280 log('Both SMTP and sendmail are enabled. Ignoring sendmail.')
286 import email
288 mime_message = mail.MailMessageToMIMEMessage(request)
289 if self._smtp_host:
291 self._SendSMTP(mime_message, smtp_lib)
292 elif self._enable_sendmail:
293 self._SendSendmail(mime_message, popen, sendmail_command)
294 else:
296 logging.info('You are not currently sending out real email. '
297 'If you have sendmail installed you can use it '
298 'by using the server with --enable_sendmail')
300 _Dynamic_Send = _Send
302 def _SendToAdmins(self, request, response, log=logging.info):
303 """Implementation of MailServer::SendToAdmins().
305 Logs email message. Contents of attachments are not shown, only
306 their sizes.
308 Given the difficulty of determining who the actual sender
309 is, Sendmail and SMTP are disabled for this action.
311 Args:
312 request: The message to send, a SendMailRequest.
313 response: The send response, a SendMailResponse.
314 log: Log function to send log information. Used for dependency
315 injection.
317 self._GenerateLog('SendToAdmins', request, log)
319 if self._smtp_host and self._enable_sendmail:
320 log('Both SMTP and sendmail are enabled. Ignoring sendmail.')
322 _Dynamic_SendToAdmins = _SendToAdmins