Port various other bugfixes in smtplib.py from upstream

This commit is contained in:
Kovid Goyal 2016-07-05 09:39:03 +05:30
parent 0b86d5175a
commit 415ebb7a6c

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python2
from __future__ import print_function
'''SMTP/ESMTP client class.
@ -47,8 +48,8 @@ import re
import email.utils
import base64
import hmac
import sys
from email.base64mime import encode as encode_base64
from sys import stderr
from functools import partial
__all__ = ["SMTPException", "SMTPServerDisconnected", "SMTPResponseException",
@ -59,9 +60,11 @@ __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
SMTP_PORT = 25
SMTP_SSL_PORT = 465
CRLF = "\r\n"
_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3
OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
# Exception classes used by this module.
class SMTPException(Exception):
"""Base class for all exceptions raised by this module."""
@ -130,6 +133,7 @@ class SMTPAuthenticationError(SMTPResponseException):
combination provided.
"""
def quoteaddr(addr):
"""Quote a subset of the email addresses defined by RFC 821.
@ -149,6 +153,13 @@ def quoteaddr(addr):
else:
return "<%s>" % m
def _addr_only(addrstring):
displayname, addr = email.utils.parseaddr(addrstring)
if (displayname, addr) == ('', ''):
# parseaddr couldn't parse it, so use it as is.
return addrstring
return addr
def quotedata(data):
"""Quote data for email.
@ -172,10 +183,14 @@ else:
def __init__(self, sslobj):
self.sslobj = sslobj
def readline(self):
def readline(self, size=-1):
if size < 0:
size = None
str = ""
chr = None
while chr != "\n":
if size is not None and len(str) >= size:
break
chr = self.sslobj.read(1)
if not chr:
break
@ -222,10 +237,11 @@ class SMTP:
ehlo_msg = "ehlo"
ehlo_resp = None
does_esmtp = 0
default_port = SMTP_PORT
def __init__(self, host='', port=0, local_hostname=None,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
debug_to=partial(print, file=sys.stderr)):
debug_to=partial(print, file=stderr)):
"""Initialize a new instance.
If specified, `host' is the name of the remote host to which to
@ -241,7 +257,6 @@ class SMTP:
self.timeout = timeout
self.debug = debug_to
self.esmtp_features = {}
self.default_port = SMTP_PORT
if host:
(code, msg) = self.connect(host, port)
if code != 220:
@ -356,14 +371,18 @@ class SMTP:
self.file = self.sock.makefile('rb')
while True:
try:
line = self.file.readline()
except socket.error:
line = ''
line = self.file.readline(_MAXLINE + 1)
except socket.error as e:
self.close()
raise SMTPServerDisconnected("Connection unexpectedly closed: " +
str(e))
if line == '':
self.close()
raise SMTPServerDisconnected("Connection unexpectedly closed")
if self.debuglevel > 0:
self.debug('reply:', repr(line))
if len(line) > _MAXLINE:
raise SMTPResponseException(500, "Line too long.")
resp.append(line[4:].strip())
code = line[:3]
# Check that the error code is syntactically correct.
@ -509,14 +528,14 @@ class SMTP:
def verify(self, address):
"""SMTP 'verify' command -- checks for address validity."""
self.putcmd("vrfy", quoteaddr(address))
self.putcmd("vrfy", _addr_only(address))
return self.getreply()
# a.k.a.
vrfy = verify
def expn(self, address):
"""SMTP 'expn' command -- expands a mailing list."""
self.putcmd("expn", quoteaddr(address))
self.putcmd("expn", _addr_only(address))
return self.getreply()
# some useful methods
@ -749,16 +768,24 @@ class SMTP:
def close(self):
"""Close the connection to the SMTP server."""
if self.file:
self.file.close()
try:
file = self.file
self.file = None
if self.sock:
self.sock.close()
if file:
file.close()
finally:
sock = self.sock
self.sock = None
if sock:
sock.close()
def quit(self):
"""Terminate the SMTP session."""
res = self.docmd("quit")
# A new EHLO is required after reconnecting with connect()
self.ehlo_resp = self.helo_resp = None
self.esmtp_features = {}
self.does_esmtp = False
self.close()
return res
@ -772,15 +799,17 @@ if _have_ssl:
are also optional - they can contain a PEM formatted private key and
certificate chain file for the SSL connection.
"""
default_port = SMTP_SSL_PORT
def __init__(self, host='', port=0, local_hostname=None,
keyfile=None, certfile=None,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
debug_to=partial(print, file=sys.stderr)):
debug_to=partial(print, file=stderr)):
self.keyfile = keyfile
self.certfile = certfile
SMTP.__init__(self, host, port, local_hostname, timeout,
debug_to=debug_to)
self.default_port = SMTP_SSL_PORT
def _get_socket(self, host, port, timeout):
if self.debuglevel > 0:
@ -825,18 +854,40 @@ class LMTP(SMTP):
try:
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(host)
except socket.error as msg:
except socket.error:
if self.debuglevel > 0:
self.debug('connect fail:', host)
if self.sock:
self.sock.close()
self.sock = None
raise socket.error(msg)
raise
(code, msg) = self.getreply()
if self.debuglevel > 0:
self.debug("connect:", msg)
return (code, msg)
# Test the sendmail method, which tests most of the others.
# Note: This always sends to localhost.
if __name__ == '__main__':
import sys
def prompt(prompt):
sys.stdout.write(prompt + ": ")
return sys.stdin.readline().strip()
fromaddr = prompt("From")
toaddrs = prompt("To").split(',')
print ("Enter message, end with ^D:")
msg = ''
while 1:
line = sys.stdin.readline()
if not line:
break
msg = msg + line
print ("Message length is %d" % len(msg))
server = SMTP('localhost')
server.set_debuglevel(1)
server.sendmail(fromaddr, toaddrs, msg)
server.quit()