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
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.
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.
68 enable_sendmail
=False,
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.
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.
97 message: Message to write to log.
98 log: Log function of type string -> None
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.
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,
155 """Get a list of mail messages sent via the Mail API.
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.
165 A list of matching mail.EmailMessage objects.
167 messages
= self
._cached
_messages
169 def recipient_matches(recipient
):
170 return re
.search(to
, recipient
)
173 messages
= [m
for m
in messages
if filter(recipient_matches
, m
.to_list())]
175 messages
= [m
for m
in messages
if re
.search(sender
, m
.sender())]
177 messages
= [m
for m
in messages
if re
.search(subject
, m
.subject())]
179 messages
= [m
for m
in messages
if re
.search(body
, m
.textbody())]
181 messages
= [m
for m
in messages
if re
.search(html
, m
.htmlbody())]
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
)
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.
197 mime_message: MimeMessage to send. Create using ToMIMEMessage.
198 smtp_lib: Class of SMTP library. Used for dependency injection.
203 smtp
.connect(self
._smtp
_host
, self
._smtp
_port
)
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())
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.
222 mime_message: MimeMessage to send. Create using ToMIMEMessage.
223 popen: popen function to create a new sub-process.
230 for to
in ('To', 'Cc', 'Bcc'):
232 tos
.extend("'%s'" % addr
.strip().replace("'", r
"'\''")
233 for addr
in mime_message
[to
].split(','))
235 command
= '%s %s' % (sendmail_command
, ' '.join(tos
))
238 child
= popen(command
,
240 stdin
=subprocess
.PIPE
,
241 stdout
=subprocess
.PIPE
)
242 except (IOError, OSError), e
:
243 logging
.error('Unable to open pipe to sendmail')
246 child
.stdin
.write(mime_message
.as_string())
251 while child
.poll() is None:
252 child
.stdout
.read(100)
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.
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
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.')
288 mime_message
= mail
.MailMessageToMIMEMessage(request
)
291 self
._SendSMTP
(mime_message
, smtp_lib
)
292 elif self
._enable
_sendmail
:
293 self
._SendSendmail
(mime_message
, popen
, sendmail_command
)
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
308 Given the difficulty of determining who the actual sender
309 is, Sendmail and SMTP are disabled for this action.
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
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