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

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, 10 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/whatsnew/3.2.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. A string is encoded to bytes using the ascii codec, and lone
668 \r and \n characters are converted to \r\n characters.
650 669
651 If there has been no previous EHLO or HELO command this session, this 670 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 671 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 672 and each of the specified options will be passed to it. If EHLO
654 fails, HELO will be tried and ESMTP options suppressed. 673 fails, HELO will be tried and ESMTP options suppressed.
655 674
656 This method will return normally if the mail is accepted for at least 675 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 676 one recipient. It returns a dictionary, with one entry for each
658 recipient that was refused. Each entry contains a tuple of the SMTP 677 recipient that was refused. Each entry contains a tuple of the SMTP
659 error code and the accompanying error message sent by the server. 678 error code and the accompanying error message sent by the server.
(...skipping 26 matching lines...) Expand all
686 >>> s.quit() 705 >>> s.quit()
687 706
688 In the above example, the message was accepted for delivery to three 707 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 708 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 709 550. If all addresses are accepted, then the method will return an
691 empty dictionary. 710 empty dictionary.
692 711
693 """ 712 """
694 self.ehlo_or_helo_if_needed() 713 self.ehlo_or_helo_if_needed()
695 esmtp_opts = [] 714 esmtp_opts = []
715 if isinstance(msg, str):
716 msg = _fix_eols(msg).encode('ascii')
696 if self.does_esmtp: 717 if self.does_esmtp:
697 # Hmmm? what's this? -ddm 718 # Hmmm? what's this? -ddm
698 # self.esmtp_features['7bit']="" 719 # self.esmtp_features['7bit']=""
699 if self.has_extn('size'): 720 if self.has_extn('size'):
700 esmtp_opts.append("size=%d" % len(msg)) 721 esmtp_opts.append("size=%d" % len(msg))
701 for option in mail_options: 722 for option in mail_options:
702 esmtp_opts.append(option) 723 esmtp_opts.append(option)
703
704 (code,resp) = self.mail(from_addr, esmtp_opts) 724 (code,resp) = self.mail(from_addr, esmtp_opts)
705 if code != 250: 725 if code != 250:
706 self.rset() 726 self.rset()
707 raise SMTPSenderRefused(code, resp, from_addr) 727 raise SMTPSenderRefused(code, resp, from_addr)
708 senderrs={} 728 senderrs={}
709 if isinstance(to_addrs, str): 729 if isinstance(to_addrs, str):
710 to_addrs = [to_addrs] 730 to_addrs = [to_addrs]
711 for each in to_addrs: 731 for each in to_addrs:
712 (code,resp)=self.rcpt(each, rcpt_options) 732 (code,resp)=self.rcpt(each, rcpt_options)
713 if (code != 250) and (code != 251): 733 if (code != 250) and (code != 251):
714 senderrs[each]=(code,resp) 734 senderrs[each]=(code,resp)
715 if len(senderrs)==len(to_addrs): 735 if len(senderrs)==len(to_addrs):
716 # the server refused all our recipients 736 # the server refused all our recipients
717 self.rset() 737 self.rset()
718 raise SMTPRecipientsRefused(senderrs) 738 raise SMTPRecipientsRefused(senderrs)
719 (code,resp) = self.data(msg) 739 (code,resp) = self.data(msg)
720 if code != 250: 740 if code != 250:
721 self.rset() 741 self.rset()
722 raise SMTPDataError(code, resp) 742 raise SMTPDataError(code, resp)
723 #if we got here then somebody got our mail 743 #if we got here then somebody got our mail
724 return senderrs 744 return senderrs
745
746 def send_message(self, msg, from_addr=None, to_addrs=None,
747 mail_options=[], rcpt_options={}):
748 """Converts message to a bytestring and passes it to sendmail.
749
750 The arguments are as for sendmail, except that msg is an
751 email.message.Message object. If from_addr is None, the from_addr is
752 taken from the 'From' header of the Message. If to_addrs is None, its
753 value is composed from the addresses listed in the 'To', 'CC', and
754 'Bcc' fields. Regardless of the values of from_addr and to_addr, any
755 Bcc field in the Message object is deleted. The Message object is then
756 serialized using email.generator.BytesGenerator and sendmail is called
757 to transmit the message.
758 """
759 if from_addr is None:
760 from_addr = msg['From']
761 if to_addrs is None:
762 addr_fields = [f for f in (msg['To'], msg['Bcc'], msg['CC'])
763 if f is not None]
764 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]
765 del msg['Bcc']
766 with io.BytesIO() as bytesmsg:
767 g = email.generator.BytesGenerator(bytesmsg)
768 g.flatten(msg, linesep='\r\n')
769 flatmsg = bytesmsg.getvalue()
770 return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,
771 rcpt_options)
725 772
726 773
727 def close(self): 774 def close(self):
728 """Close the connection to the SMTP server.""" 775 """Close the connection to the SMTP server."""
729 if self.file: 776 if self.file:
730 self.file.close() 777 self.file.close()
731 self.file = None 778 self.file = None
732 if self.sock: 779 if self.sock:
733 self.sock.close() 780 self.sock.close()
734 self.sock = None 781 self.sock = None
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
828 line = sys.stdin.readline() 875 line = sys.stdin.readline()
829 if not line: 876 if not line:
830 break 877 break
831 msg = msg + line 878 msg = msg + line
832 print("Message length is %d" % len(msg)) 879 print("Message length is %d" % len(msg))
833 880
834 server = SMTP('localhost') 881 server = SMTP('localhost')
835 server.set_debuglevel(1) 882 server.set_debuglevel(1)
836 server.sendmail(fromaddr, toaddrs, msg) 883 server.sendmail(fromaddr, toaddrs, msg)
837 server.quit() 884 server.quit()
OLDNEW
« no previous file with comments | « Doc/whatsnew/3.2.rst ('k') | Lib/test/test_smtplib.py » ('j') | no next file with comments »

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