thirdparty/google_appengine/google/appengine/api/mail_stub.py
changeset 109 620f9b141567
child 686 df109be0567c
equal deleted inserted replaced
108:261778de26ff 109:620f9b141567
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     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
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    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.
       
    16 #
       
    17 
       
    18 """Stub version of the mail API, writes email to logs and can optionally
       
    19 send real email via SMTP or sendmail."""
       
    20 
       
    21 
       
    22 
       
    23 
       
    24 
       
    25 from email import MIMEBase
       
    26 from email import MIMEMultipart
       
    27 from email import MIMEText
       
    28 import logging
       
    29 import mail
       
    30 import mimetypes
       
    31 import subprocess
       
    32 import smtplib
       
    33 
       
    34 
       
    35 class ServiceStub(object):
       
    36   """Service stub base class used to forward requests to methods.
       
    37 
       
    38   Use this base class to defined service stub classes.  Instead of overriding
       
    39   MakeSyncCall, the default implementation forwards the call to appropriate
       
    40   sub-class method.
       
    41 
       
    42   If the sub class defines a static string 'SERVICE', it will also check
       
    43   to make sure that calls to this service stub are always made to that named
       
    44   service.
       
    45   """
       
    46 
       
    47   def MakeSyncCall(self, service, call, request, response):
       
    48     """The main RPC entry point.
       
    49 
       
    50     Args:
       
    51       service: Must be name as defined by sub class variable SERVICE.
       
    52       call: A string representing the rpc to make.  Must be part of
       
    53         MailService.
       
    54       request: A protocol buffer of the type corresponding to 'call'.
       
    55       response: A protocol buffer of the type corresponding to 'call'.
       
    56     """
       
    57     assert not hasattr(self, 'SERVICE') or service == self.SERVICE
       
    58     explanation = []
       
    59     assert request.IsInitialized(explanation), explanation
       
    60 
       
    61     attr = getattr(self, '_Dynamic_' + call)
       
    62     attr(request, response)
       
    63 
       
    64 
       
    65 class MailServiceStub(ServiceStub):
       
    66   """Python only mail service stub.
       
    67 
       
    68   This stub does not actually attempt to send email.  instead it merely logs
       
    69   a description of the email to the developers console.
       
    70 
       
    71   Args:
       
    72     host: Host of SMTP server to use.  Blank disables sending SMTP.
       
    73     port: Port of SMTP server to use.
       
    74     user: User to log in to SMTP server as.
       
    75     password: Password for SMTP server user.
       
    76   """
       
    77   SERVICE = 'mail'
       
    78 
       
    79   def __init__(self,
       
    80                host=None,
       
    81                port=25,
       
    82                user='',
       
    83                password='',
       
    84                enable_sendmail=False,
       
    85                show_mail_body=False):
       
    86     self._smtp_host = host
       
    87     self._smtp_port = port
       
    88     self._smtp_user = user
       
    89     self._smtp_password = password
       
    90     self._enable_sendmail = enable_sendmail
       
    91     self._show_mail_body = show_mail_body
       
    92 
       
    93   def _GenerateLog(self, method, message, log):
       
    94     """Generate a list of log messages representing sent mail.
       
    95 
       
    96     Args:
       
    97       message: Message to write to log.
       
    98       log: Log function of type string -> None
       
    99     """
       
   100     log('MailService.%s' % method)
       
   101     log('  From: %s' % message.sender())
       
   102 
       
   103     for address in message.to_list():
       
   104       log('  To: %s' % address)
       
   105     for address in message.cc_list():
       
   106       log('  Cc: %s' % address)
       
   107     for address in message.bcc_list():
       
   108       log('  Bcc: %s' % address)
       
   109 
       
   110     if message.replyto():
       
   111       log('  Reply-to: %s' % message.replyto())
       
   112 
       
   113     log('  Subject: %s' % message.subject())
       
   114 
       
   115     if message.has_textbody():
       
   116       log('  Body:')
       
   117       log('    Content-type: text/plain')
       
   118       log('    Data length: %d' % len(message.textbody()))
       
   119       if self._show_mail_body:
       
   120         log('-----\n' + message.textbody() + '\n-----')
       
   121 
       
   122     if message.has_htmlbody():
       
   123       log('  Body:')
       
   124       log('    Content-type: text/html')
       
   125       log('    Data length: %d' % len(message.htmlbody()))
       
   126       if self._show_mail_body:
       
   127         log('-----\n' + message.htmlbody() + '\n-----')
       
   128 
       
   129     for attachment in message.attachment_list():
       
   130       log('  Attachment:')
       
   131       log('    File name: %s' % attachment.filename())
       
   132       log('    Data length: %s' % len(attachment.data()))
       
   133 
       
   134   def _SendSMTP(self, mime_message, smtp_lib=smtplib.SMTP):
       
   135     """Send MIME message via SMTP.
       
   136 
       
   137     Connects to SMTP server and sends MIME message.  If user is supplied
       
   138     will try to login to that server to send as authenticated.  Does not
       
   139     currently support encryption.
       
   140 
       
   141     Args:
       
   142       mime_message: MimeMessage to send.  Create using ToMIMEMessage.
       
   143       smtp_lib: Class of SMTP library.  Used for dependency injection.
       
   144     """
       
   145     smtp = smtp_lib()
       
   146     try:
       
   147       smtp.connect(self._smtp_host, self._smtp_port)
       
   148       if self._smtp_user:
       
   149         smtp.login(self._smtp_user, self._smtp_password)
       
   150 
       
   151       tos = ', '.join([mime_message[to] for to in ['To', 'Cc', 'Bcc']
       
   152                        if mime_message[to]])
       
   153       smtp.sendmail(mime_message['From'], tos, str(mime_message))
       
   154     finally:
       
   155       smtp.quit()
       
   156 
       
   157   def _SendSendmail(self, mime_message,
       
   158                     popen=subprocess.Popen,
       
   159                     sendmail_command='sendmail'):
       
   160     """Send MIME message via sendmail, if exists on computer.
       
   161 
       
   162     Attempts to send email via sendmail.  Any IO failure, including
       
   163     the program not being found is ignored.
       
   164 
       
   165     Args:
       
   166       mime_message: MimeMessage to send.  Create using ToMIMEMessage.
       
   167       popen: popen function to create a new sub-process.
       
   168     """
       
   169     try:
       
   170       tos = [mime_message[to] for to in ['To', 'Cc', 'Bcc'] if mime_message[to]]
       
   171       sendmail_command = '%s %s' % (sendmail_command, ' '.join(tos))
       
   172 
       
   173       try:
       
   174         child = popen(sendmail_command,
       
   175                       shell=True,
       
   176                       stdin=subprocess.PIPE,
       
   177                       stdout=subprocess.PIPE)
       
   178       except (IOError, OSError), e:
       
   179         logging.error('Unable to open pipe to sendmail')
       
   180         raise
       
   181       try:
       
   182         child.stdin.write(str(mime_message))
       
   183         child.stdin.close()
       
   184       finally:
       
   185         while child.poll() is None:
       
   186           child.stdout.read(100)
       
   187         child.stdout.close()
       
   188     except (IOError, OSError), e:
       
   189       logging.error('Error sending mail using sendmail: ' + str(e))
       
   190 
       
   191   def _Send(self, request, response, log=logging.info,
       
   192             smtp_lib=smtplib.SMTP,
       
   193             popen=subprocess.Popen,
       
   194             sendmail_command='sendmail'):
       
   195     """Implementation of MailServer::Send().
       
   196 
       
   197     Logs email message.  Contents of attachments are not shown, only
       
   198     their sizes.  If SMTP is configured, will send via SMTP, else
       
   199     will use Sendmail if it is installed.
       
   200 
       
   201     Args:
       
   202       request: The message to send, a SendMailRequest.
       
   203       response: The send response, a SendMailResponse.
       
   204       log: Log function to send log information.  Used for dependency
       
   205         injection.
       
   206       smtp_lib: Class of SMTP library.  Used for dependency injection.
       
   207       popen2: popen2 function to use for opening pipe to other process.
       
   208         Used for dependency injection.
       
   209     """
       
   210     self._GenerateLog('Send', request, log)
       
   211 
       
   212     if self._smtp_host and self._enable_sendmail:
       
   213       log('Both SMTP and sendmail are enabled.  Ignoring sendmail.')
       
   214 
       
   215     mime_message = mail.MailMessageToMIMEMessage(request)
       
   216     if self._smtp_host:
       
   217       self._SendSMTP(mime_message, smtp_lib)
       
   218     elif self._enable_sendmail:
       
   219       self._SendSendmail(mime_message, popen, sendmail_command)
       
   220     else:
       
   221       logging.info('You are not currently sending out real email.  '
       
   222                    'If you have sendmail installed you can use it '
       
   223                    'by using the server with --enable_sendmail')
       
   224 
       
   225   _Dynamic_Send = _Send
       
   226 
       
   227   def _SendToAdmins(self, request, response, log=logging.info):
       
   228     """Implementation of MailServer::SendToAdmins().
       
   229 
       
   230     Logs email message.  Contents of attachments are not shown, only
       
   231     their sizes.
       
   232 
       
   233     Given the difficulty of determining who the actual sender
       
   234     is, Sendmail and SMTP are disabled for this action.
       
   235 
       
   236     Args:
       
   237       request: The message to send, a SendMailRequest.
       
   238       response: The send response, a SendMailResponse.
       
   239       log: Log function to send log information.  Used for dependency
       
   240         injection.
       
   241     """
       
   242     self._GenerateLog('SendToAdmins', request, log)
       
   243 
       
   244     if self._smtp_host and self._enable_sendmail:
       
   245       log('Both SMTP and sendmail are enabled.  Ignoring sendmail.')
       
   246 
       
   247   _Dynamic_SendToAdmins = _SendToAdmins