3434elif sys .version_info [0 ] == 3 :
3535 from email import encoders as email_encoders
3636 basestring = str
37-
37+
3838 def unicode (_str , _charset ):
3939 return str (_str .encode (_charset ), _charset )
4040else :
4141 raise RuntimeError ('Unsupported Python version: %d.%d.%d' % (
4242 sys .version_info [0 ], sys .version_info [1 ], sys .version_info [2 ]
4343 ))
4444
45+ from email .header import Header
4546from email .mime .base import MIMEBase
4647from email .mime .multipart import MIMEMultipart
4748from email .mime .application import MIMEApplication
@@ -50,6 +51,7 @@ def unicode(_str, _charset):
5051from email .mime .text import MIMEText
5152import mimetypes
5253import os
54+ import re
5355
5456from .conn import SMTP
5557from .compat import encoded
@@ -78,13 +80,14 @@ class Envelope(object):
7880 :param subject: message subject
7981 :param html_body: optional HTML part of the message
8082 :param text_body: optional plain text part of the message
81- :param cc_addrs : optional list of CC address
82- :param bcc_addrs : optional list of BCC address
83+ :param cc_addr : optional single CC address or list of CC addresses
84+ :param bcc_addr : optional single BCC address or list of BCC addresses
8385 :param headers: optional dictionary of headers
8486 :param charset: message charset
8587 """
8688
8789 ADDR_FORMAT = '%s <%s>'
90+ ADDR_REGEXP = re .compile (r'^(.*) <([^@]+@[^@]+)>$' )
8891
8992 def __init__ (self , to_addr = None , from_addr = None , subject = None ,
9093 html_body = None , text_body = None , cc_addr = None , bcc_addr = None ,
@@ -108,12 +111,18 @@ def __init__(self, to_addr=None, from_addr=None, subject=None,
108111 self ._parts .append (('text/html' , html_body , charset ))
109112
110113 if cc_addr :
111- self ._cc = cc_addr
114+ if isinstance (cc_addr , list ):
115+ self ._cc = cc_addr
116+ else :
117+ self ._cc = [cc_addr ]
112118 else :
113119 self ._cc = []
114120
115121 if bcc_addr :
116- self ._bcc = bcc_addr
122+ if isinstance (bcc_addr , list ):
123+ self ._bcc = bcc_addr
124+ else :
125+ self ._bcc = [bcc_addr ]
117126 else :
118127 self ._bcc = []
119128
@@ -126,6 +135,13 @@ def __init__(self, to_addr=None, from_addr=None, subject=None,
126135
127136 self ._addr_format = unicode (self .ADDR_FORMAT , charset )
128137
138+ def __repr__ (self ):
139+ return u'<Envelope from="%s" to="%s" subject="%s">' % (
140+ self ._addrs_to_header ([self ._from ]),
141+ self ._addrs_to_header (self ._to ),
142+ self ._subject
143+ )
144+
129145 @property
130146 def to_addr (self ):
131147 """List of ``To`` addresses."""
@@ -189,7 +205,7 @@ def _addr_tuple_to_addr(self, addr_tuple):
189205
190206 if len (addr_tuple ) == 2 and addr_tuple [1 ]:
191207 addr = self ._addr_format % (
192- addr_tuple [1 ] or '' ,
208+ self . _header ( addr_tuple [1 ] or '' ) ,
193209 addr_tuple [0 ] or ''
194210 )
195211 elif addr_tuple [0 ]:
@@ -217,40 +233,58 @@ def _addrs_to_header(self, addrs):
217233 continue
218234
219235 if isinstance (addr , basestring ):
220- _addrs .append (addr )
236+ if self ._is_ascii (addr ):
237+ _addrs .append (self ._encoded (addr ))
238+ else :
239+ # these headers need special care when encoding, see:
240+ # http://tools.ietf.org/html/rfc2047#section-8
241+ # Need to break apart the name from the address if there are
242+ # non-ascii chars
243+ m = self .ADDR_REGEXP .match (addr )
244+ if m :
245+ t = (m .group (2 ), m .group (1 ))
246+ _addrs .append (self ._addr_tuple_to_addr (t ))
247+ else :
248+ # What can we do? Just pass along what the user gave us and hope they did it right
249+ _addrs .append (self ._encoded (addr ))
221250 elif isinstance (addr , tuple ):
222251 _addrs .append (self ._addr_tuple_to_addr (addr ))
223252 else :
224253 self ._raise (MessageEncodeError ,
225254 '%s is not a valid address' % str (addr ))
226255
227- _header = unicode ( ',' , self . _charset ) .join (_addrs )
256+ _header = ',' .join (_addrs )
228257 return _header
229258
230259 def _raise (self , exc_class , message ):
231260 raise exc_class (self ._encoded (message ))
232261
262+ def _header (self , _str ):
263+ if self ._is_ascii (_str ):
264+ return _str
265+ return Header (_str , self ._charset ).encode ()
266+
267+ def _is_ascii (self , _str ):
268+ return all (ord (c ) < 128 for c in _str )
269+
233270 def _encoded (self , _str ):
234271 return encoded (_str , self ._charset )
235272
236273 def to_mime_message (self ):
237274 """Returns the envelope as
238275 :py:class:`email.mime.multipart.MIMEMultipart`."""
239276 msg = MIMEMultipart ('alternative' )
240- msg ['Subject' ] = self ._encoded (self ._subject or '' )
277+ msg ['Subject' ] = self ._header (self ._subject or '' )
241278
242279 msg ['From' ] = self ._encoded (self ._addrs_to_header ([self ._from ]))
243280 msg ['To' ] = self ._encoded (self ._addrs_to_header (self ._to ))
244281
245282 if self ._cc :
246- msg ['CC' ] = self ._encoded (self ._addrs_to_header (self ._cc ))
247-
248- if self ._bcc :
249- msg ['BCC' ] = self ._encoded (self ._addrs_to_header (self ._bcc ))
283+ msg ['CC' ] = self ._addrs_to_header (self ._cc )
250284
251285 if self ._headers :
252286 for key , value in self ._headers .items ():
253- msg [key ] = self ._encoded (value )
287+ msg [key ] = self ._header (value )
254288
255289 for part in self ._parts :
256290 type_maj , type_min = part [0 ].split ('/' )
0 commit comments