diff options
Diffstat (limited to 'Lib/smtplib.py')
-rw-r--r-- | Lib/smtplib.py | 130 |
1 files changed, 76 insertions, 54 deletions
diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 7b97a6a88f..2039592719 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,11 @@ 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 +297,7 @@ 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 +311,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) @@ -630,7 +625,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 @@ -654,8 +649,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, @@ -833,26 +837,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") @@ -877,18 +896,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: |