From 04e95bf09e78e1072ce5e86894500f999dc3e435 Mon Sep 17 00:00:00 2001 From: MrExcitingDigits <122944243+MrExcitingDigits@users.noreply.github.com> Date: Wed, 16 Jul 2025 22:45:00 -0400 Subject: [PATCH] Unify commands to uppercase for wider support This decision to unify commands in uppercase comes from adherence to the industry standard and RFC 5321. It was posted in the python discussions here: https://discuss.python.org/t/unify-all-smtplib-commands-to-be-uppercase/97482 --- Lib/smtplib.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 84d6d858e7dec1..9d04deab9d72dd 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -1,7 +1,8 @@ '''SMTP/ESMTP client class. -This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP -Authentication) and RFC 2487 (Secure SMTP over TLS). +This should follow RFC 5321 (SMTP which obsoletes RFC 821), RFC 1869 +(ESMTP), RFC 2554 (SMTP Authentication) and RFC 2487 (Secure SMTP +over TLS). Notes: @@ -36,6 +37,8 @@ # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data) # by Carey Evans , for picky mail servers. # RFC 2554 (authentication) support by Gerhard Haering . +# Upper case commands for wider support as enumerated in section 2.4 of RFC 5321 +# by Steve Jacob # # This was modified from the Python 1.5 library HTTP lib. @@ -219,7 +222,7 @@ class SMTP: sock = None file = None helo_resp = None - ehlo_msg = "ehlo" + ehlo_msg = "EHLO" ehlo_resp = None does_esmtp = False default_port = SMTP_PORT @@ -435,7 +438,7 @@ def helo(self, name=''): Hostname to send for this command defaults to the FQDN of the local host. """ - self.putcmd("helo", name or self.local_hostname) + self.putcmd("HELO", name or self.local_hostname) (code, msg) = self.getreply() self.helo_resp = msg return (code, msg) @@ -498,13 +501,13 @@ def has_extn(self, opt): def help(self, args=''): """SMTP 'help' command. Returns help text from server.""" - self.putcmd("help", args) + self.putcmd("HELP", args) return self.getreply()[1] def rset(self): """SMTP 'rset' command -- resets session.""" self.command_encoding = 'ascii' - return self.docmd("rset") + return self.docmd("RSET") def _rset(self): """Internal 'rset' command which ignores any SMTPServerDisconnected error. @@ -520,7 +523,7 @@ def _rset(self): def noop(self): """SMTP 'noop' command -- doesn't do anything :>""" - return self.docmd("noop") + return self.docmd("NOOP") def mail(self, sender, options=()): """SMTP 'mail' command -- begins mail xfer session. @@ -540,7 +543,7 @@ def mail(self, sender, options=()): raise SMTPNotSupportedError( 'SMTPUTF8 not supported by server') optionlist = ' ' + ' '.join(options) - self.putcmd("mail", "from:%s%s" % (quoteaddr(sender), optionlist)) + self.putcmd("MAIL", "FROM:%s%s" % (quoteaddr(sender), optionlist)) return self.getreply() def rcpt(self, recip, options=()): @@ -548,7 +551,7 @@ def rcpt(self, recip, options=()): optionlist = '' if options and self.does_esmtp: optionlist = ' ' + ' '.join(options) - self.putcmd("rcpt", "to:%s%s" % (quoteaddr(recip), optionlist)) + self.putcmd("RCPT", "TO:%s%s" % (quoteaddr(recip), optionlist)) return self.getreply() def data(self, msg): @@ -561,7 +564,7 @@ def data(self, msg): is a string, lone '\\r' and '\\n' characters are converted to '\\r\\n' characters. If msg is bytes, it is transmitted as is. """ - self.putcmd("data") + self.putcmd("DATA") (code, repl) = self.getreply() if self.debuglevel > 0: self._print_debug('data:', (code, repl)) @@ -582,14 +585,14 @@ def data(self, msg): def verify(self, address): """SMTP 'verify' command -- checks for address validity.""" - self.putcmd("vrfy", _addr_only(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", _addr_only(address)) + self.putcmd("EXPN", _addr_only(address)) return self.getreply() # some useful methods @@ -862,7 +865,7 @@ def sendmail(self, from_addr, to_addrs, msg, mail_options=(), msg = _fix_eols(msg).encode('ascii') if self.does_esmtp: if self.has_extn('size'): - esmtp_opts.append("size=%d" % len(msg)) + esmtp_opts.append("SIZE=%d" % len(msg)) for option in mail_options: esmtp_opts.append(option) (code, resp) = self.mail(from_addr, esmtp_opts) @@ -988,7 +991,7 @@ def close(self): def quit(self): """Terminate the SMTP session.""" - res = self.docmd("quit") + res = self.docmd("QUIT") # A new EHLO is required after reconnecting with connect() self.ehlo_resp = self.helo_resp = None self.esmtp_features = {} @@ -1049,7 +1052,7 @@ class LMTP(SMTP): using a Unix socket, LMTP generally don't support or require any authentication, but your mileage might vary.""" - ehlo_msg = "lhlo" + ehlo_msg = "LHLO" def __init__(self, host='', port=LMTP_PORT, local_hostname=None, source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):