Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(34137)

Side by Side Diff: Lib/smtplib.py

Issue 10321: Add support for Message objects and binary data to smtplib.sendmail
Patch Set: Created 8 years, 11 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « Doc/library/smtplib.rst ('k') | Lib/test/test_smtplib.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #! /usr/bin/env python3 1 #! /usr/bin/env python3
2 2
3 '''SMTP/ESMTP client class. 3 '''SMTP/ESMTP client class.
4 4
5 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP 5 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
6 Authentication) and RFC 2487 (Secure SMTP over TLS). 6 Authentication) and RFC 2487 (Secure SMTP over TLS).
7 7
8 Notes: 8 Notes:
9 9
10 Please remember, when doing ESMTP, that the names of the SMTP service 10 Please remember, when doing ESMTP, that the names of the SMTP service
(...skipping 24 matching lines...) Expand all
35 # Author: The Dragon De Monsyne <dragondm@integral.org> 35 # Author: The Dragon De Monsyne <dragondm@integral.org>
36 # ESMTP support, test code and doc fixes added by 36 # ESMTP support, test code and doc fixes added by
37 # Eric S. Raymond <esr@thyrsus.com> 37 # Eric S. Raymond <esr@thyrsus.com>
38 # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data) 38 # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
39 # by Carey Evans <c.evans@clear.net.nz>, for picky mail servers. 39 # by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
40 # RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>. 40 # RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>.
41 # 41 #
42 # This was modified from the Python 1.5 library HTTP lib. 42 # This was modified from the Python 1.5 library HTTP lib.
43 43
44 import socket 44 import socket
45 import io
45 import re 46 import re
46 import email.utils 47 import email.utils
48 import email.message
49 import email.generator
47 import base64 50 import base64
48 import hmac 51 import hmac
49 from email.base64mime import body_encode as encode_base64 52 from email.base64mime import body_encode as encode_base64
50 from sys import stderr 53 from sys import stderr
51 54
52 __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException", 55 __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
53 "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError", 56 "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError",
54 "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError", 57 "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError",
55 "quoteaddr","quotedata","SMTP"] 58 "quoteaddr","quotedata","SMTP"]
56 59
57 SMTP_PORT = 25 60 SMTP_PORT = 25
58 SMTP_SSL_PORT = 465 61 SMTP_SSL_PORT = 465
59 CRLF="\r\n" 62 CRLF="\r\n"
63 bCRLF=b"\r\n"
60 64
61 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I) 65 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
62 66
63 # Exception classes used by this module. 67 # Exception classes used by this module.
64 class SMTPException(Exception): 68 class SMTPException(Exception):
65 """Base class for all exceptions raised by this module.""" 69 """Base class for all exceptions raised by this module."""
66 70
67 class SMTPServerDisconnected(SMTPException): 71 class SMTPServerDisconnected(SMTPException):
68 """Not connected to any SMTP server. 72 """Not connected to any SMTP server.
69 73
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
140 pass 144 pass
141 if m == (None, None): # Indicates parse failure or AttributeError 145 if m == (None, None): # Indicates parse failure or AttributeError
142 # something weird here.. punt -ddm 146 # something weird here.. punt -ddm
143 return "<%s>" % addr 147 return "<%s>" % addr
144 elif m is None: 148 elif m is None:
145 # the sender wants an empty return address 149 # the sender wants an empty return address
146 return "<>" 150 return "<>"
147 else: 151 else:
148 return "<%s>" % m 152 return "<%s>" % m
149 153
154 # Legacy method kept for backward compatibility.
150 def quotedata(data): 155 def quotedata(data):
151 """Quote data for email. 156 """Quote data for email.
152 157
153 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into 158 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
154 Internet CRLF end-of-line. 159 Internet CRLF end-of-line.
155 """ 160 """
156 return re.sub(r'(?m)^\.', '..', 161 return re.sub(r'(?m)^\.', '..',
157 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) 162 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
163
164 def _quote_periods(bindata):
165 return re.sub(br'(?m)^\.', '..', bindata)
166
167 def _fix_eols(data):
168 return re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)
158 169
159 try: 170 try:
160 import ssl 171 import ssl
161 except ImportError: 172 except ImportError:
162 _have_ssl = False 173 _have_ssl = False
163 else: 174 else:
164 class SSLFakeFile: 175 class SSLFakeFile:
165 """A fake file like object that really wraps a SSLObject. 176 """A fake file like object that really wraps a SSLObject.
166 177
167 It only supports what is needed in smtplib. 178 It only supports what is needed in smtplib.
(...skipping 294 matching lines...) Expand 10 before | Expand all | Expand 10 after
462 optionlist = ' ' + ' '.join(options) 473 optionlist = ' ' + ' '.join(options)
463 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist)) 474 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
464 return self.getreply() 475 return self.getreply()
465 476
466 def data(self,msg): 477 def data(self,msg):
467 """SMTP 'DATA' command -- sends message data to server. 478 """SMTP 'DATA' command -- sends message data to server.
468 479
469 Automatically quotes lines beginning with a period per rfc821. 480 Automatically quotes lines beginning with a period per rfc821.
470 Raises SMTPDataError if there is an unexpected reply to the 481 Raises SMTPDataError if there is an unexpected reply to the
471 DATA command; the return value from this method is the final 482 DATA command; the return value from this method is the final
472 response code received when the all data is sent. 483 response code received when the all data is sent. If msg
484 is a string, lone '\r' and '\n' characters are converted to
485 '\r\n' characters. If msg is bytes, it is transmitted as is.
473 """ 486 """
474 self.putcmd("data") 487 self.putcmd("data")
475 (code,repl)=self.getreply() 488 (code,repl)=self.getreply()
476 if self.debuglevel >0 : print("data:", (code,repl), file=stderr) 489 if self.debuglevel >0 : print("data:", (code,repl), file=stderr)
477 if code != 354: 490 if code != 354:
478 raise SMTPDataError(code,repl) 491 raise SMTPDataError(code,repl)
479 else: 492 else:
480 q = quotedata(msg) 493 if isinstance(msg, str):
481 if q[-2:] != CRLF: 494 msg = _fix_eols(msg).encode('ascii')
482 q = q + CRLF 495 q = _quote_periods(msg)
483 q = q + "." + CRLF 496 if q[-2:] != bCRLF:
497 q = q + bCRLF
498 q = q + b"." + bCRLF
484 self.send(q) 499 self.send(q)
485 (code,msg)=self.getreply() 500 (code,msg)=self.getreply()
486 if self.debuglevel >0 : print("data:", (code,msg), file=stderr) 501 if self.debuglevel >0 : print("data:", (code,msg), file=stderr)
487 return (code,msg) 502 return (code,msg)
488 503
489 def verify(self, address): 504 def verify(self, address):
490 """SMTP 'verify' command -- checks for address validity.""" 505 """SMTP 'verify' command -- checks for address validity."""
491 self.putcmd("vrfy", quoteaddr(address)) 506 self.putcmd("vrfy", quoteaddr(address))
492 return self.getreply() 507 return self.getreply()
493 # a.k.a. 508 # a.k.a.
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after
640 655
641 The arguments are: 656 The arguments are:
642 - from_addr : The address sending this mail. 657 - from_addr : The address sending this mail.
643 - to_addrs : A list of addresses to send this mail to. A bare 658 - to_addrs : A list of addresses to send this mail to. A bare
644 string will be treated as a list with 1 address. 659 string will be treated as a list with 1 address.
645 - msg : The message to send. 660 - msg : The message to send.
646 - mail_options : List of ESMTP options (such as 8bitmime) for the 661 - mail_options : List of ESMTP options (such as 8bitmime) for the
647 mail command. 662 mail command.
648 - rcpt_options : List of ESMTP options (such as DSN commands) for 663 - rcpt_options : List of ESMTP options (such as DSN commands) for
649 all the rcpt commands. 664 all the rcpt commands.
665
666 msg may be a string containing characters in the ASCII range, or a byte
667 string, or an email.message.Message object. A string is encoded to
668 bytes using the ascii codec, and lone \r and \n characters are
669 converted to \r\n characters. A byte string is transmitted as is. A
670 Message object is serialized using email.generator.BytesGenerator.
671
672 When msg is a Message object, from_addr and to_addrs may be set to
673 None, in which case the from_addr is taken from the 'From' header of
674 the Message, and the to_addrs is composed from the addresses listed in
675 the 'To', 'CC', and 'Bcc' fields. Regardless, any Bcc field in
676 the Message object is deleted before it is serialized.
650 677
651 If there has been no previous EHLO or HELO command this session, this 678 If there has been no previous EHLO or HELO command this session, this
652 method tries ESMTP EHLO first. If the server does ESMTP, message size 679 method tries ESMTP EHLO first. If the server does ESMTP, message size
653 and each of the specified options will be passed to it. If EHLO 680 and each of the specified options will be passed to it. If EHLO
654 fails, HELO will be tried and ESMTP options suppressed. 681 fails, HELO will be tried and ESMTP options suppressed.
655 682
656 This method will return normally if the mail is accepted for at least 683 This method will return normally if the mail is accepted for at least
657 one recipient. It returns a dictionary, with one entry for each 684 one recipient. It returns a dictionary, with one entry for each
658 recipient that was refused. Each entry contains a tuple of the SMTP 685 recipient that was refused. Each entry contains a tuple of the SMTP
659 error code and the accompanying error message sent by the server. 686 error code and the accompanying error message sent by the server.
(...skipping 26 matching lines...) Expand all
686 >>> s.quit() 713 >>> s.quit()
687 714
688 In the above example, the message was accepted for delivery to three 715 In the above example, the message was accepted for delivery to three
689 of the four addresses, and one was rejected, with the error code 716 of the four addresses, and one was rejected, with the error code
690 550. If all addresses are accepted, then the method will return an 717 550. If all addresses are accepted, then the method will return an
691 empty dictionary. 718 empty dictionary.
692 719
693 """ 720 """
694 self.ehlo_or_helo_if_needed() 721 self.ehlo_or_helo_if_needed()
695 esmtp_opts = [] 722 esmtp_opts = []
723 if isinstance(msg, email.message.Message):
724 if from_addr is None:
725 from_addr = msg['From']
726 if to_addrs is None:
727 addr_fields = [f for f in (msg['To'], msg['Bcc'], msg['CC'])
728 if f is not None]
729 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]
730 del msg['Bcc']
731 bytesmsg = io.BytesIO()
732 g = email.generator.BytesGenerator(bytesmsg)
733 g.flatten(msg, linesep='\r\n')
734 bytesmsg = bytesmsg.getvalue()
735 elif isinstance(msg, str):
736 bytesmsg = _fix_eols(msg).encode('ascii')
737 else:
738 bytesmsg = msg
696 if self.does_esmtp: 739 if self.does_esmtp:
697 # Hmmm? what's this? -ddm 740 # Hmmm? what's this? -ddm
698 # self.esmtp_features['7bit']="" 741 # self.esmtp_features['7bit']=""
699 if self.has_extn('size'): 742 if self.has_extn('size'):
700 esmtp_opts.append("size=%d" % len(msg)) 743 esmtp_opts.append("size=%d" % len(msg))
701 for option in mail_options: 744 for option in mail_options:
702 esmtp_opts.append(option) 745 esmtp_opts.append(option)
703 746
704 (code,resp) = self.mail(from_addr, esmtp_opts) 747 (code,resp) = self.mail(from_addr, esmtp_opts)
705 if code != 250: 748 if code != 250:
706 self.rset() 749 self.rset()
707 raise SMTPSenderRefused(code, resp, from_addr) 750 raise SMTPSenderRefused(code, resp, from_addr)
708 senderrs={} 751 senderrs={}
709 if isinstance(to_addrs, str): 752 if isinstance(to_addrs, str):
710 to_addrs = [to_addrs] 753 to_addrs = [to_addrs]
711 for each in to_addrs: 754 for each in to_addrs:
712 (code,resp)=self.rcpt(each, rcpt_options) 755 (code,resp)=self.rcpt(each, rcpt_options)
713 if (code != 250) and (code != 251): 756 if (code != 250) and (code != 251):
714 senderrs[each]=(code,resp) 757 senderrs[each]=(code,resp)
715 if len(senderrs)==len(to_addrs): 758 if len(senderrs)==len(to_addrs):
716 # the server refused all our recipients 759 # the server refused all our recipients
717 self.rset() 760 self.rset()
718 raise SMTPRecipientsRefused(senderrs) 761 raise SMTPRecipientsRefused(senderrs)
719 (code,resp) = self.data(msg) 762 (code,resp) = self.data(bytesmsg)
720 if code != 250: 763 if code != 250:
721 self.rset() 764 self.rset()
722 raise SMTPDataError(code, resp) 765 raise SMTPDataError(code, resp)
723 #if we got here then somebody got our mail 766 #if we got here then somebody got our mail
724 return senderrs 767 return senderrs
725 768
726 769
727 def close(self): 770 def close(self):
728 """Close the connection to the SMTP server.""" 771 """Close the connection to the SMTP server."""
729 if self.file: 772 if self.file:
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
828 line = sys.stdin.readline() 871 line = sys.stdin.readline()
829 if not line: 872 if not line:
830 break 873 break
831 msg = msg + line 874 msg = msg + line
832 print("Message length is %d" % len(msg)) 875 print("Message length is %d" % len(msg))
833 876
834 server = SMTP('localhost') 877 server = SMTP('localhost')
835 server.set_debuglevel(1) 878 server.set_debuglevel(1)
836 server.sendmail(fromaddr, toaddrs, msg) 879 server.sendmail(fromaddr, toaddrs, msg)
837 server.quit() 880 server.quit()
OLDNEW
« no previous file with comments | « Doc/library/smtplib.rst ('k') | Lib/test/test_smtplib.py » ('j') | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+