Skip to content

Commit 69a79bc

Browse files
committed
Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
by Carey Evans <[email protected]>, for picky mail servers.
1 parent 5c44027 commit 69a79bc

File tree

1 file changed

+37
-15
lines changed

1 file changed

+37
-15
lines changed

Lib/smtplib.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
Author: The Dragon De Monsyne <[email protected]>
55
ESMTP support, test code and doc fixes added by
66
Eric S. Raymond <[email protected]>
7+
Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
8+
by Carey Evans <[email protected]>, for picky mail servers.
79
810
(This was modified from the Python 1.5 library HTTP lib.)
911
@@ -43,6 +45,37 @@
4345
SMTPRecipientsRefused="All Recipients refused"
4446
SMTPDataError="Error transmitting message data"
4547

48+
def quoteaddr(addr):
49+
"""Quote a subset of the email addresses defined by RFC 821.
50+
51+
Technically, only a <mailbox> is allowed. In addition,
52+
email addresses without a domain are permitted.
53+
54+
Addresses will not be modified if they are already quoted
55+
(actually if they begin with '<' and end with '>'."""
56+
if re.match('(?s)\A<.*>\Z', addr):
57+
return addr
58+
59+
localpart = None
60+
domain = ''
61+
try:
62+
at = string.rindex(addr, '@')
63+
localpart = addr[:at]
64+
domain = addr[at:]
65+
except ValueError:
66+
localpart = addr
67+
68+
pat = re.compile(r'([<>()\[\]\\,;:@\"\001-\037\177])')
69+
return '<%s%s>' % (pat.sub(r'\\\1', localpart), domain)
70+
71+
def quotedata(data):
72+
"""Quote data for email.
73+
74+
Double leading '.', and change Unix newline '\n' into
75+
Internet CRLF end-of-line."""
76+
return re.sub(r'(?m)^\.', '..',
77+
re.sub(r'\r?\n', CRLF, data))
78+
4679
class SMTP:
4780
"""This class manages a connection to an SMTP or ESMTP server."""
4881
debuglevel = 0
@@ -208,36 +241,25 @@ def mail(self,sender,options=[]):
208241
options = " " + string.joinfields(options, ' ')
209242
else:
210243
options = ''
211-
self.putcmd("mail from:", sender + options)
244+
self.putcmd("mail", "from:" + quoteaddr(sender) + options)
212245
return self.getreply()
213246

214247
def rcpt(self,recip):
215248
""" SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
216-
self.putcmd("rcpt","to: %s" % recip)
249+
self.putcmd("rcpt","to:%s" % quoteaddr(recip))
217250
return self.getreply()
218251

219252
def data(self,msg):
220253
""" SMTP 'DATA' command. Sends message data to server.
221254
Automatically quotes lines beginning with a period per rfc821. """
222-
#quote periods in msg according to RFC821
223-
# ps, I don't know why I have to do it this way... doing:
224-
# quotepat=re.compile(r"^[.]",re.M)
225-
# msg=re.sub(quotepat,"..",msg)
226-
# should work, but it dosen't (it doubles the number of any
227-
# contiguous series of .'s at the beginning of a line,
228-
#instead of just adding one. )
229-
quotepat=re.compile(r"^[.]+",re.M)
230-
def m(pat):
231-
return "."+pat.group(0)
232-
msg=re.sub(quotepat,m,msg)
233255
self.putcmd("data")
234256
(code,repl)=self.getreply()
235257
if self.debuglevel >0 : print "data:", (code,repl)
236258
if code <> 354:
237259
return -1
238260
else:
239-
self.send(msg)
240-
self.send("\n.\n")
261+
self.send(quotedata(msg))
262+
self.send("%s.%s" % (CRLF, CRLF))
241263
(code,msg)=self.getreply()
242264
if self.debuglevel >0 : print "data:", (code,msg)
243265
return code

0 commit comments

Comments
 (0)