diff options
Diffstat (limited to 'Lib/smtplib.py')
| -rw-r--r-- | Lib/smtplib.py | 152 | 
1 files changed, 90 insertions, 62 deletions
| diff --git a/Lib/smtplib.py b/Lib/smtplib.py index fbef96e691..d37b0e247d 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -133,24 +133,18 @@ class SMTPAuthenticationError(SMTPResponseException):      combination provided.      """ -def quoteaddr(addr): +def quoteaddr(addrstring):      """Quote a subset of the email addresses defined by RFC 821.      Should be able to handle anything email.utils.parseaddr can handle.      """ -    m = (None, None) -    try: -        m = email.utils.parseaddr(addr)[1] -    except AttributeError: -        pass -    if m == (None, None):  # Indicates parse failure or AttributeError -        # something weird here.. punt -ddm -        return "<%s>" % addr -    elif m is None: -        # the sender wants an empty return address -        return "<>" -    else: -        return "<%s>" % m +    displayname, addr = email.utils.parseaddr(addrstring) +    if (displayname, addr) == ('', ''): +        # parseaddr couldn't parse it, use it as is and hope for the best. +        if addrstring.strip().startswith('<'): +            return addrstring +        return "<%s>" % addrstring +    return "<%s>" % addr  def _addr_only(addrstring):      displayname, addr = email.utils.parseaddr(addrstring) @@ -180,27 +174,6 @@ try:  except ImportError:      _have_ssl = False  else: -    class SSLFakeFile: -        """A fake file like object that really wraps a SSLObject. - -        It only supports what is needed in smtplib. -        """ -        def __init__(self, sslobj): -            self.sslobj = sslobj - -        def readline(self): -            str = b"" -            chr = None -            while chr != b"\n": -                chr = self.sslobj.read(1) -                if not chr: -                    break -                str += chr -            return str - -        def close(self): -            pass -      _have_ssl = True @@ -242,7 +215,8 @@ class SMTP:      default_port = SMTP_PORT      def __init__(self, host='', port=0, local_hostname=None, -                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT): +                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, +                 source_address=None):          """Initialize a new instance.          If specified, `host' is the name of the remote host to which to @@ -250,11 +224,16 @@ class SMTP:          By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised          if the specified `host' doesn't respond correctly.  If specified,          `local_hostname` is used as the FQDN of the local host.  By default, -        the local hostname is found using socket.getfqdn(). +        the local hostname is found using socket.getfqdn(). The +        `source_address` parameter takes a 2-tuple (host, port) for the socket +        to bind to as its source address before connecting. If the host is '' +        and port is 0, the OS default behavior will be used.          """          self.timeout = timeout          self.esmtp_features = {} +        self.source_address = source_address +          if host:              (code, msg) = self.connect(host, port)              if code != 220: @@ -277,6 +256,19 @@ class SMTP:                      pass                  self.local_hostname = '[%s]' % addr +    def __enter__(self): +        return self + +    def __exit__(self, *args): +        try: +            code, message = self.docmd("QUIT") +            if code != 221: +                raise SMTPResponseException(code, message) +        except SMTPServerDisconnected: +            pass +        finally: +            self.close() +      def set_debuglevel(self, debuglevel):          """Set the debug output level. @@ -290,10 +282,12 @@ class SMTP:          # This makes it simpler for SMTP_SSL to use the SMTP connect code          # and just alter the socket connection bit.          if self.debuglevel > 0: -            print('connect:', (host, port), file=stderr) -        return socket.create_connection((host, port), timeout) +            print('connect: to', (host, port), self.source_address, +                                 file=stderr) +        return socket.create_connection((host, port), timeout, +                                        self.source_address) -    def connect(self, host='localhost', port=0): +    def connect(self, host='localhost', port=0, source_address=None):          """Connect to a host on a given port.          If the hostname ends with a colon (`:') followed by a number, and @@ -304,6 +298,10 @@ class SMTP:          specified during instantiation.          """ + +        if source_address: +            self.source_address = source_address +          if not port and (host.find(':') == host.rfind(':')):              i = host.rfind(':')              if i >= 0: @@ -317,6 +315,7 @@ class SMTP:          if self.debuglevel > 0:              print('connect:', (host, port), file=stderr)          self.sock = self._get_socket(host, port, self.timeout) +        self.file = None          (code, msg) = self.getreply()          if self.debuglevel > 0:              print("connect:", msg, file=stderr) @@ -388,7 +387,8 @@ class SMTP:          errmsg = b"\n".join(resp)          if self.debuglevel > 0: -            print('reply: retcode (%s); Msg: %s' % (errcode, errmsg), file=stderr) +            print('reply: retcode (%s); Msg: %s' % (errcode, errmsg), +                                                    file=stderr)          return errcode, errmsg      def docmd(self, cmd, args=""): @@ -632,7 +632,7 @@ class SMTP:          # We could not login sucessfully. Return result of last attempt.          raise SMTPAuthenticationError(code, resp) -    def starttls(self, keyfile=None, certfile=None): +    def starttls(self, keyfile=None, certfile=None, context=None):          """Puts the connection to the SMTP server into TLS mode.          If there has been no previous EHLO or HELO command this session, this @@ -656,8 +656,17 @@ class SMTP:          if resp == 220:              if not _have_ssl:                  raise RuntimeError("No SSL support included in this Python") -            self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) -            self.file = SSLFakeFile(self.sock) +            if context is not None and keyfile is not None: +                raise ValueError("context and keyfile arguments are mutually " +                                 "exclusive") +            if context is not None and certfile is not None: +                raise ValueError("context and certfile arguments are mutually " +                                 "exclusive") +            if context is not None: +                self.sock = context.wrap_socket(self.sock) +            else: +                self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) +            self.file = None              # RFC 3207:              # The client MUST discard any knowledge obtained from              # the server, such as the list of SMTP service extensions, @@ -786,7 +795,8 @@ class SMTP:          # TODO implement heuristics to guess the correct Resent-* block with an          # option allowing the user to enable the heuristics.  (It should be          # possible to guess correctly almost all of the time.) -        resent =msg.get_all('Resent-Date') + +        resent = msg.get_all('Resent-Date')          if resent is None:              header_prefix = ''          elif len(resent) == 1: @@ -795,13 +805,13 @@ class SMTP:              raise ValueError("message has more than one 'Resent-' header block")          if from_addr is None:              # Prefer the sender field per RFC 2822:3.6.2. -            from_addr = (msg[header_prefix+'Sender'] -                           if (header_prefix+'Sender') in msg -                           else msg[header_prefix+'From']) +            from_addr = (msg[header_prefix + 'Sender'] +                           if (header_prefix + 'Sender') in msg +                           else msg[header_prefix + 'From'])          if to_addrs is None: -            addr_fields = [f for f in (msg[header_prefix+'To'], -                                       msg[header_prefix+'Bcc'], -                                       msg[header_prefix+'Cc']) if f is not None] +            addr_fields = [f for f in (msg[header_prefix + 'To'], +                                       msg[header_prefix + 'Bcc'], +                                       msg[header_prefix + 'Cc']) if f is not None]              to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]          # Make a local copy so we can delete the bcc headers.          msg_copy = copy.copy(msg) @@ -835,26 +845,41 @@ if _have_ssl:          """ This is a subclass derived from SMTP that connects over an SSL encrypted          socket (to use this class you need a socket module that was compiled with SSL          support). If host is not specified, '' (the local host) is used. If port is -        omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile +        omitted, the standard SMTP-over-SSL port (465) is used. The optional +        source_address takes a two-tuple (host,port) for socket to bind to. keyfile and certfile          are also optional - they can contain a PEM formatted private key and -        certificate chain file for the SSL connection. +        certificate chain file for the SSL connection. context also optional, can contain +        a SSLContext, and is an alternative to keyfile and certfile; If it is specified both +        keyfile and certfile must be None.          """          default_port = SMTP_SSL_PORT          def __init__(self, host='', port=0, local_hostname=None,                       keyfile=None, certfile=None, -                     timeout=socket._GLOBAL_DEFAULT_TIMEOUT): +                     timeout=socket._GLOBAL_DEFAULT_TIMEOUT, +                     source_address=None, context=None): +            if context is not None and keyfile is not None: +                raise ValueError("context and keyfile arguments are mutually " +                                 "exclusive") +            if context is not None and certfile is not None: +                raise ValueError("context and certfile arguments are mutually " +                                 "exclusive")              self.keyfile = keyfile              self.certfile = certfile -            SMTP.__init__(self, host, port, local_hostname, timeout) +            self.context = context +            SMTP.__init__(self, host, port, local_hostname, timeout, +                    source_address)          def _get_socket(self, host, port, timeout):              if self.debuglevel > 0:                  print('connect:', (host, port), file=stderr) -            new_socket = socket.create_connection((host, port), timeout) -            new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile) -            self.file = SSLFakeFile(new_socket) +            new_socket = socket.create_connection((host, port), timeout, +                    self.source_address) +            if self.context is not None: +                new_socket = self.context.wrap_socket(new_socket) +            else: +                new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)              return new_socket      __all__.append("SMTP_SSL") @@ -879,18 +904,21 @@ class LMTP(SMTP):      ehlo_msg = "lhlo" -    def __init__(self, host='', port=LMTP_PORT, local_hostname=None): +    def __init__(self, host='', port=LMTP_PORT, local_hostname=None, +            source_address=None):          """Initialize a new instance.""" -        SMTP.__init__(self, host, port, local_hostname) +        SMTP.__init__(self, host, port, local_hostname=local_hostname, +                      source_address=source_address) -    def connect(self, host='localhost', port=0): +    def connect(self, host='localhost', port=0, source_address=None):          """Connect to the LMTP daemon, on either a Unix or a TCP socket."""          if host[0] != '/': -            return SMTP.connect(self, host, port) +            return SMTP.connect(self, host, port, source_address=source_address)          # Handle Unix-domain sockets.          try:              self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +            self.file = None              self.sock.connect(host)          except socket.error as msg:              if self.debuglevel > 0: | 
