|
4 | 4 | Author: The Dragon De Monsyne <[email protected]>
|
5 | 5 | ESMTP support, test code and doc fixes added by
|
6 | 6 | 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. |
7 | 9 |
|
8 | 10 | (This was modified from the Python 1.5 library HTTP lib.)
|
9 | 11 |
|
|
43 | 45 | SMTPRecipientsRefused="All Recipients refused"
|
44 | 46 | SMTPDataError="Error transmitting message data"
|
45 | 47 |
|
| 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 | + |
46 | 79 | class SMTP:
|
47 | 80 | """This class manages a connection to an SMTP or ESMTP server."""
|
48 | 81 | debuglevel = 0
|
@@ -208,36 +241,25 @@ def mail(self,sender,options=[]):
|
208 | 241 | options = " " + string.joinfields(options, ' ')
|
209 | 242 | else:
|
210 | 243 | options = ''
|
211 |
| - self.putcmd("mail from:", sender + options) |
| 244 | + self.putcmd("mail", "from:" + quoteaddr(sender) + options) |
212 | 245 | return self.getreply()
|
213 | 246 |
|
214 | 247 | def rcpt(self,recip):
|
215 | 248 | """ SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
|
216 |
| - self.putcmd("rcpt","to: %s" % recip) |
| 249 | + self.putcmd("rcpt","to:%s" % quoteaddr(recip)) |
217 | 250 | return self.getreply()
|
218 | 251 |
|
219 | 252 | def data(self,msg):
|
220 | 253 | """ SMTP 'DATA' command. Sends message data to server.
|
221 | 254 | 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) |
233 | 255 | self.putcmd("data")
|
234 | 256 | (code,repl)=self.getreply()
|
235 | 257 | if self.debuglevel >0 : print "data:", (code,repl)
|
236 | 258 | if code <> 354:
|
237 | 259 | return -1
|
238 | 260 | else:
|
239 |
| - self.send(msg) |
240 |
| - self.send("\n.\n") |
| 261 | + self.send(quotedata(msg)) |
| 262 | + self.send("%s.%s" % (CRLF, CRLF)) |
241 | 263 | (code,msg)=self.getreply()
|
242 | 264 | if self.debuglevel >0 : print "data:", (code,msg)
|
243 | 265 | return code
|
|
0 commit comments