##// END OF EJS Templates
mail: drop python 2.5 support
timeless@mozdev.org -
r26673:ab1af5e7 default
parent child Browse files
Show More
@@ -1,341 +1,337
1 # mail.py - mail sending bits for mercurial
1 # mail.py - mail sending bits for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import email
10 import email
11 import os
11 import os
12 import quopri
12 import quopri
13 import smtplib
13 import smtplib
14 import socket
14 import socket
15 import sys
15 import sys
16 import time
16 import time
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 encoding,
20 encoding,
21 error,
21 error,
22 sslutil,
22 sslutil,
23 util,
23 util,
24 )
24 )
25
25
26 _oldheaderinit = email.Header.Header.__init__
26 _oldheaderinit = email.Header.Header.__init__
27 def _unifiedheaderinit(self, *args, **kw):
27 def _unifiedheaderinit(self, *args, **kw):
28 """
28 """
29 Python 2.7 introduces a backwards incompatible change
29 Python 2.7 introduces a backwards incompatible change
30 (Python issue1974, r70772) in email.Generator.Generator code:
30 (Python issue1974, r70772) in email.Generator.Generator code:
31 pre-2.7 code passed "continuation_ws='\t'" to the Header
31 pre-2.7 code passed "continuation_ws='\t'" to the Header
32 constructor, and 2.7 removed this parameter.
32 constructor, and 2.7 removed this parameter.
33
33
34 Default argument is continuation_ws=' ', which means that the
34 Default argument is continuation_ws=' ', which means that the
35 behavior is different in <2.7 and 2.7
35 behavior is different in <2.7 and 2.7
36
36
37 We consider the 2.7 behavior to be preferable, but need
37 We consider the 2.7 behavior to be preferable, but need
38 to have an unified behavior for versions 2.4 to 2.7
38 to have an unified behavior for versions 2.4 to 2.7
39 """
39 """
40 # override continuation_ws
40 # override continuation_ws
41 kw['continuation_ws'] = ' '
41 kw['continuation_ws'] = ' '
42 _oldheaderinit(self, *args, **kw)
42 _oldheaderinit(self, *args, **kw)
43
43
44 email.Header.Header.__dict__['__init__'] = _unifiedheaderinit
44 email.Header.Header.__dict__['__init__'] = _unifiedheaderinit
45
45
46 class STARTTLS(smtplib.SMTP):
46 class STARTTLS(smtplib.SMTP):
47 '''Derived class to verify the peer certificate for STARTTLS.
47 '''Derived class to verify the peer certificate for STARTTLS.
48
48
49 This class allows to pass any keyword arguments to SSL socket creation.
49 This class allows to pass any keyword arguments to SSL socket creation.
50 '''
50 '''
51 def __init__(self, sslkwargs, **kwargs):
51 def __init__(self, sslkwargs, **kwargs):
52 smtplib.SMTP.__init__(self, **kwargs)
52 smtplib.SMTP.__init__(self, **kwargs)
53 self._sslkwargs = sslkwargs
53 self._sslkwargs = sslkwargs
54
54
55 def starttls(self, keyfile=None, certfile=None):
55 def starttls(self, keyfile=None, certfile=None):
56 if not self.has_extn("starttls"):
56 if not self.has_extn("starttls"):
57 msg = "STARTTLS extension not supported by server"
57 msg = "STARTTLS extension not supported by server"
58 raise smtplib.SMTPException(msg)
58 raise smtplib.SMTPException(msg)
59 (resp, reply) = self.docmd("STARTTLS")
59 (resp, reply) = self.docmd("STARTTLS")
60 if resp == 220:
60 if resp == 220:
61 self.sock = sslutil.wrapsocket(self.sock, keyfile, certfile,
61 self.sock = sslutil.wrapsocket(self.sock, keyfile, certfile,
62 **self._sslkwargs)
62 **self._sslkwargs)
63 if not util.safehasattr(self.sock, "read"):
63 if not util.safehasattr(self.sock, "read"):
64 # using httplib.FakeSocket with Python 2.5.x or earlier
64 # using httplib.FakeSocket with Python 2.5.x or earlier
65 self.sock.read = self.sock.recv
65 self.sock.read = self.sock.recv
66 self.file = smtplib.SSLFakeFile(self.sock)
66 self.file = smtplib.SSLFakeFile(self.sock)
67 self.helo_resp = None
67 self.helo_resp = None
68 self.ehlo_resp = None
68 self.ehlo_resp = None
69 self.esmtp_features = {}
69 self.esmtp_features = {}
70 self.does_esmtp = 0
70 self.does_esmtp = 0
71 return (resp, reply)
71 return (resp, reply)
72
72
73 if util.safehasattr(smtplib.SMTP, '_get_socket'):
74 class SMTPS(smtplib.SMTP):
73 class SMTPS(smtplib.SMTP):
75 '''Derived class to verify the peer certificate for SMTPS.
74 '''Derived class to verify the peer certificate for SMTPS.
76
75
77 This class allows to pass any keyword arguments to SSL socket creation.
76 This class allows to pass any keyword arguments to SSL socket creation.
78 '''
77 '''
79 def __init__(self, sslkwargs, keyfile=None, certfile=None, **kwargs):
78 def __init__(self, sslkwargs, keyfile=None, certfile=None, **kwargs):
80 self.keyfile = keyfile
79 self.keyfile = keyfile
81 self.certfile = certfile
80 self.certfile = certfile
82 smtplib.SMTP.__init__(self, **kwargs)
81 smtplib.SMTP.__init__(self, **kwargs)
83 self.default_port = smtplib.SMTP_SSL_PORT
82 self.default_port = smtplib.SMTP_SSL_PORT
84 self._sslkwargs = sslkwargs
83 self._sslkwargs = sslkwargs
85
84
86 def _get_socket(self, host, port, timeout):
85 def _get_socket(self, host, port, timeout):
87 if self.debuglevel > 0:
86 if self.debuglevel > 0:
88 print >> sys.stderr, 'connect:', (host, port)
87 print >> sys.stderr, 'connect:', (host, port)
89 new_socket = socket.create_connection((host, port), timeout)
88 new_socket = socket.create_connection((host, port), timeout)
90 new_socket = sslutil.wrapsocket(new_socket,
89 new_socket = sslutil.wrapsocket(new_socket,
91 self.keyfile, self.certfile,
90 self.keyfile, self.certfile,
92 **self._sslkwargs)
91 **self._sslkwargs)
93 self.file = smtplib.SSLFakeFile(new_socket)
92 self.file = smtplib.SSLFakeFile(new_socket)
94 return new_socket
93 return new_socket
95 else:
96 def SMTPS(sslkwargs, keyfile=None, certfile=None, **kwargs):
97 raise error.Abort(_('SMTPS requires Python 2.6 or later'))
98
94
99 def _smtp(ui):
95 def _smtp(ui):
100 '''build an smtp connection and return a function to send mail'''
96 '''build an smtp connection and return a function to send mail'''
101 local_hostname = ui.config('smtp', 'local_hostname')
97 local_hostname = ui.config('smtp', 'local_hostname')
102 tls = ui.config('smtp', 'tls', 'none')
98 tls = ui.config('smtp', 'tls', 'none')
103 # backward compatible: when tls = true, we use starttls.
99 # backward compatible: when tls = true, we use starttls.
104 starttls = tls == 'starttls' or util.parsebool(tls)
100 starttls = tls == 'starttls' or util.parsebool(tls)
105 smtps = tls == 'smtps'
101 smtps = tls == 'smtps'
106 if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
102 if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
107 raise error.Abort(_("can't use TLS: Python SSL support not installed"))
103 raise error.Abort(_("can't use TLS: Python SSL support not installed"))
108 mailhost = ui.config('smtp', 'host')
104 mailhost = ui.config('smtp', 'host')
109 if not mailhost:
105 if not mailhost:
110 raise error.Abort(_('smtp.host not configured - cannot send mail'))
106 raise error.Abort(_('smtp.host not configured - cannot send mail'))
111 verifycert = ui.config('smtp', 'verifycert', 'strict')
107 verifycert = ui.config('smtp', 'verifycert', 'strict')
112 if verifycert not in ['strict', 'loose']:
108 if verifycert not in ['strict', 'loose']:
113 if util.parsebool(verifycert) is not False:
109 if util.parsebool(verifycert) is not False:
114 raise error.Abort(_('invalid smtp.verifycert configuration: %s')
110 raise error.Abort(_('invalid smtp.verifycert configuration: %s')
115 % (verifycert))
111 % (verifycert))
116 verifycert = False
112 verifycert = False
117 if (starttls or smtps) and verifycert:
113 if (starttls or smtps) and verifycert:
118 sslkwargs = sslutil.sslkwargs(ui, mailhost)
114 sslkwargs = sslutil.sslkwargs(ui, mailhost)
119 else:
115 else:
120 # 'ui' is required by sslutil.wrapsocket() and set by sslkwargs()
116 # 'ui' is required by sslutil.wrapsocket() and set by sslkwargs()
121 sslkwargs = {'ui': ui}
117 sslkwargs = {'ui': ui}
122 if smtps:
118 if smtps:
123 ui.note(_('(using smtps)\n'))
119 ui.note(_('(using smtps)\n'))
124 s = SMTPS(sslkwargs, local_hostname=local_hostname)
120 s = SMTPS(sslkwargs, local_hostname=local_hostname)
125 elif starttls:
121 elif starttls:
126 s = STARTTLS(sslkwargs, local_hostname=local_hostname)
122 s = STARTTLS(sslkwargs, local_hostname=local_hostname)
127 else:
123 else:
128 s = smtplib.SMTP(local_hostname=local_hostname)
124 s = smtplib.SMTP(local_hostname=local_hostname)
129 if smtps:
125 if smtps:
130 defaultport = 465
126 defaultport = 465
131 else:
127 else:
132 defaultport = 25
128 defaultport = 25
133 mailport = util.getport(ui.config('smtp', 'port', defaultport))
129 mailport = util.getport(ui.config('smtp', 'port', defaultport))
134 ui.note(_('sending mail: smtp host %s, port %s\n') %
130 ui.note(_('sending mail: smtp host %s, port %s\n') %
135 (mailhost, mailport))
131 (mailhost, mailport))
136 s.connect(host=mailhost, port=mailport)
132 s.connect(host=mailhost, port=mailport)
137 if starttls:
133 if starttls:
138 ui.note(_('(using starttls)\n'))
134 ui.note(_('(using starttls)\n'))
139 s.ehlo()
135 s.ehlo()
140 s.starttls()
136 s.starttls()
141 s.ehlo()
137 s.ehlo()
142 if (starttls or smtps) and verifycert:
138 if (starttls or smtps) and verifycert:
143 ui.note(_('(verifying remote certificate)\n'))
139 ui.note(_('(verifying remote certificate)\n'))
144 sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
140 sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
145 username = ui.config('smtp', 'username')
141 username = ui.config('smtp', 'username')
146 password = ui.config('smtp', 'password')
142 password = ui.config('smtp', 'password')
147 if username and not password:
143 if username and not password:
148 password = ui.getpass()
144 password = ui.getpass()
149 if username and password:
145 if username and password:
150 ui.note(_('(authenticating to mail server as %s)\n') %
146 ui.note(_('(authenticating to mail server as %s)\n') %
151 (username))
147 (username))
152 try:
148 try:
153 s.login(username, password)
149 s.login(username, password)
154 except smtplib.SMTPException as inst:
150 except smtplib.SMTPException as inst:
155 raise error.Abort(inst)
151 raise error.Abort(inst)
156
152
157 def send(sender, recipients, msg):
153 def send(sender, recipients, msg):
158 try:
154 try:
159 return s.sendmail(sender, recipients, msg)
155 return s.sendmail(sender, recipients, msg)
160 except smtplib.SMTPRecipientsRefused as inst:
156 except smtplib.SMTPRecipientsRefused as inst:
161 recipients = [r[1] for r in inst.recipients.values()]
157 recipients = [r[1] for r in inst.recipients.values()]
162 raise error.Abort('\n' + '\n'.join(recipients))
158 raise error.Abort('\n' + '\n'.join(recipients))
163 except smtplib.SMTPException as inst:
159 except smtplib.SMTPException as inst:
164 raise error.Abort(inst)
160 raise error.Abort(inst)
165
161
166 return send
162 return send
167
163
168 def _sendmail(ui, sender, recipients, msg):
164 def _sendmail(ui, sender, recipients, msg):
169 '''send mail using sendmail.'''
165 '''send mail using sendmail.'''
170 program = ui.config('email', 'method', 'smtp')
166 program = ui.config('email', 'method', 'smtp')
171 cmdline = '%s -f %s %s' % (program, util.email(sender),
167 cmdline = '%s -f %s %s' % (program, util.email(sender),
172 ' '.join(map(util.email, recipients)))
168 ' '.join(map(util.email, recipients)))
173 ui.note(_('sending mail: %s\n') % cmdline)
169 ui.note(_('sending mail: %s\n') % cmdline)
174 fp = util.popen(cmdline, 'w')
170 fp = util.popen(cmdline, 'w')
175 fp.write(msg)
171 fp.write(msg)
176 ret = fp.close()
172 ret = fp.close()
177 if ret:
173 if ret:
178 raise error.Abort('%s %s' % (
174 raise error.Abort('%s %s' % (
179 os.path.basename(program.split(None, 1)[0]),
175 os.path.basename(program.split(None, 1)[0]),
180 util.explainexit(ret)[0]))
176 util.explainexit(ret)[0]))
181
177
182 def _mbox(mbox, sender, recipients, msg):
178 def _mbox(mbox, sender, recipients, msg):
183 '''write mails to mbox'''
179 '''write mails to mbox'''
184 fp = open(mbox, 'ab+')
180 fp = open(mbox, 'ab+')
185 # Should be time.asctime(), but Windows prints 2-characters day
181 # Should be time.asctime(), but Windows prints 2-characters day
186 # of month instead of one. Make them print the same thing.
182 # of month instead of one. Make them print the same thing.
187 date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
183 date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
188 fp.write('From %s %s\n' % (sender, date))
184 fp.write('From %s %s\n' % (sender, date))
189 fp.write(msg)
185 fp.write(msg)
190 fp.write('\n\n')
186 fp.write('\n\n')
191 fp.close()
187 fp.close()
192
188
193 def connect(ui, mbox=None):
189 def connect(ui, mbox=None):
194 '''make a mail connection. return a function to send mail.
190 '''make a mail connection. return a function to send mail.
195 call as sendmail(sender, list-of-recipients, msg).'''
191 call as sendmail(sender, list-of-recipients, msg).'''
196 if mbox:
192 if mbox:
197 open(mbox, 'wb').close()
193 open(mbox, 'wb').close()
198 return lambda s, r, m: _mbox(mbox, s, r, m)
194 return lambda s, r, m: _mbox(mbox, s, r, m)
199 if ui.config('email', 'method', 'smtp') == 'smtp':
195 if ui.config('email', 'method', 'smtp') == 'smtp':
200 return _smtp(ui)
196 return _smtp(ui)
201 return lambda s, r, m: _sendmail(ui, s, r, m)
197 return lambda s, r, m: _sendmail(ui, s, r, m)
202
198
203 def sendmail(ui, sender, recipients, msg, mbox=None):
199 def sendmail(ui, sender, recipients, msg, mbox=None):
204 send = connect(ui, mbox=mbox)
200 send = connect(ui, mbox=mbox)
205 return send(sender, recipients, msg)
201 return send(sender, recipients, msg)
206
202
207 def validateconfig(ui):
203 def validateconfig(ui):
208 '''determine if we have enough config data to try sending email.'''
204 '''determine if we have enough config data to try sending email.'''
209 method = ui.config('email', 'method', 'smtp')
205 method = ui.config('email', 'method', 'smtp')
210 if method == 'smtp':
206 if method == 'smtp':
211 if not ui.config('smtp', 'host'):
207 if not ui.config('smtp', 'host'):
212 raise error.Abort(_('smtp specified as email transport, '
208 raise error.Abort(_('smtp specified as email transport, '
213 'but no smtp host configured'))
209 'but no smtp host configured'))
214 else:
210 else:
215 if not util.findexe(method):
211 if not util.findexe(method):
216 raise error.Abort(_('%r specified as email transport, '
212 raise error.Abort(_('%r specified as email transport, '
217 'but not in PATH') % method)
213 'but not in PATH') % method)
218
214
219 def mimetextpatch(s, subtype='plain', display=False):
215 def mimetextpatch(s, subtype='plain', display=False):
220 '''Return MIME message suitable for a patch.
216 '''Return MIME message suitable for a patch.
221 Charset will be detected as utf-8 or (possibly fake) us-ascii.
217 Charset will be detected as utf-8 or (possibly fake) us-ascii.
222 Transfer encodings will be used if necessary.'''
218 Transfer encodings will be used if necessary.'''
223
219
224 cs = 'us-ascii'
220 cs = 'us-ascii'
225 if not display:
221 if not display:
226 try:
222 try:
227 s.decode('us-ascii')
223 s.decode('us-ascii')
228 except UnicodeDecodeError:
224 except UnicodeDecodeError:
229 try:
225 try:
230 s.decode('utf-8')
226 s.decode('utf-8')
231 cs = 'utf-8'
227 cs = 'utf-8'
232 except UnicodeDecodeError:
228 except UnicodeDecodeError:
233 # We'll go with us-ascii as a fallback.
229 # We'll go with us-ascii as a fallback.
234 pass
230 pass
235
231
236 return mimetextqp(s, subtype, cs)
232 return mimetextqp(s, subtype, cs)
237
233
238 def mimetextqp(body, subtype, charset):
234 def mimetextqp(body, subtype, charset):
239 '''Return MIME message.
235 '''Return MIME message.
240 Quoted-printable transfer encoding will be used if necessary.
236 Quoted-printable transfer encoding will be used if necessary.
241 '''
237 '''
242 enc = None
238 enc = None
243 for line in body.splitlines():
239 for line in body.splitlines():
244 if len(line) > 950:
240 if len(line) > 950:
245 body = quopri.encodestring(body)
241 body = quopri.encodestring(body)
246 enc = "quoted-printable"
242 enc = "quoted-printable"
247 break
243 break
248
244
249 msg = email.MIMEText.MIMEText(body, subtype, charset)
245 msg = email.MIMEText.MIMEText(body, subtype, charset)
250 if enc:
246 if enc:
251 del msg['Content-Transfer-Encoding']
247 del msg['Content-Transfer-Encoding']
252 msg['Content-Transfer-Encoding'] = enc
248 msg['Content-Transfer-Encoding'] = enc
253 return msg
249 return msg
254
250
255 def _charsets(ui):
251 def _charsets(ui):
256 '''Obtains charsets to send mail parts not containing patches.'''
252 '''Obtains charsets to send mail parts not containing patches.'''
257 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')]
253 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')]
258 fallbacks = [encoding.fallbackencoding.lower(),
254 fallbacks = [encoding.fallbackencoding.lower(),
259 encoding.encoding.lower(), 'utf-8']
255 encoding.encoding.lower(), 'utf-8']
260 for cs in fallbacks: # find unique charsets while keeping order
256 for cs in fallbacks: # find unique charsets while keeping order
261 if cs not in charsets:
257 if cs not in charsets:
262 charsets.append(cs)
258 charsets.append(cs)
263 return [cs for cs in charsets if not cs.endswith('ascii')]
259 return [cs for cs in charsets if not cs.endswith('ascii')]
264
260
265 def _encode(ui, s, charsets):
261 def _encode(ui, s, charsets):
266 '''Returns (converted) string, charset tuple.
262 '''Returns (converted) string, charset tuple.
267 Finds out best charset by cycling through sendcharsets in descending
263 Finds out best charset by cycling through sendcharsets in descending
268 order. Tries both encoding and fallbackencoding for input. Only as
264 order. Tries both encoding and fallbackencoding for input. Only as
269 last resort send as is in fake ascii.
265 last resort send as is in fake ascii.
270 Caveat: Do not use for mail parts containing patches!'''
266 Caveat: Do not use for mail parts containing patches!'''
271 try:
267 try:
272 s.decode('ascii')
268 s.decode('ascii')
273 except UnicodeDecodeError:
269 except UnicodeDecodeError:
274 sendcharsets = charsets or _charsets(ui)
270 sendcharsets = charsets or _charsets(ui)
275 for ics in (encoding.encoding, encoding.fallbackencoding):
271 for ics in (encoding.encoding, encoding.fallbackencoding):
276 try:
272 try:
277 u = s.decode(ics)
273 u = s.decode(ics)
278 except UnicodeDecodeError:
274 except UnicodeDecodeError:
279 continue
275 continue
280 for ocs in sendcharsets:
276 for ocs in sendcharsets:
281 try:
277 try:
282 return u.encode(ocs), ocs
278 return u.encode(ocs), ocs
283 except UnicodeEncodeError:
279 except UnicodeEncodeError:
284 pass
280 pass
285 except LookupError:
281 except LookupError:
286 ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs)
282 ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs)
287 # if ascii, or all conversion attempts fail, send (broken) ascii
283 # if ascii, or all conversion attempts fail, send (broken) ascii
288 return s, 'us-ascii'
284 return s, 'us-ascii'
289
285
290 def headencode(ui, s, charsets=None, display=False):
286 def headencode(ui, s, charsets=None, display=False):
291 '''Returns RFC-2047 compliant header from given string.'''
287 '''Returns RFC-2047 compliant header from given string.'''
292 if not display:
288 if not display:
293 # split into words?
289 # split into words?
294 s, cs = _encode(ui, s, charsets)
290 s, cs = _encode(ui, s, charsets)
295 return str(email.Header.Header(s, cs))
291 return str(email.Header.Header(s, cs))
296 return s
292 return s
297
293
298 def _addressencode(ui, name, addr, charsets=None):
294 def _addressencode(ui, name, addr, charsets=None):
299 name = headencode(ui, name, charsets)
295 name = headencode(ui, name, charsets)
300 try:
296 try:
301 acc, dom = addr.split('@')
297 acc, dom = addr.split('@')
302 acc = acc.encode('ascii')
298 acc = acc.encode('ascii')
303 dom = dom.decode(encoding.encoding).encode('idna')
299 dom = dom.decode(encoding.encoding).encode('idna')
304 addr = '%s@%s' % (acc, dom)
300 addr = '%s@%s' % (acc, dom)
305 except UnicodeDecodeError:
301 except UnicodeDecodeError:
306 raise error.Abort(_('invalid email address: %s') % addr)
302 raise error.Abort(_('invalid email address: %s') % addr)
307 except ValueError:
303 except ValueError:
308 try:
304 try:
309 # too strict?
305 # too strict?
310 addr = addr.encode('ascii')
306 addr = addr.encode('ascii')
311 except UnicodeDecodeError:
307 except UnicodeDecodeError:
312 raise error.Abort(_('invalid local address: %s') % addr)
308 raise error.Abort(_('invalid local address: %s') % addr)
313 return email.Utils.formataddr((name, addr))
309 return email.Utils.formataddr((name, addr))
314
310
315 def addressencode(ui, address, charsets=None, display=False):
311 def addressencode(ui, address, charsets=None, display=False):
316 '''Turns address into RFC-2047 compliant header.'''
312 '''Turns address into RFC-2047 compliant header.'''
317 if display or not address:
313 if display or not address:
318 return address or ''
314 return address or ''
319 name, addr = email.Utils.parseaddr(address)
315 name, addr = email.Utils.parseaddr(address)
320 return _addressencode(ui, name, addr, charsets)
316 return _addressencode(ui, name, addr, charsets)
321
317
322 def addrlistencode(ui, addrs, charsets=None, display=False):
318 def addrlistencode(ui, addrs, charsets=None, display=False):
323 '''Turns a list of addresses into a list of RFC-2047 compliant headers.
319 '''Turns a list of addresses into a list of RFC-2047 compliant headers.
324 A single element of input list may contain multiple addresses, but output
320 A single element of input list may contain multiple addresses, but output
325 always has one address per item'''
321 always has one address per item'''
326 if display:
322 if display:
327 return [a.strip() for a in addrs if a.strip()]
323 return [a.strip() for a in addrs if a.strip()]
328
324
329 result = []
325 result = []
330 for name, addr in email.Utils.getaddresses(addrs):
326 for name, addr in email.Utils.getaddresses(addrs):
331 if name or addr:
327 if name or addr:
332 result.append(_addressencode(ui, name, addr, charsets))
328 result.append(_addressencode(ui, name, addr, charsets))
333 return result
329 return result
334
330
335 def mimeencode(ui, s, charsets=None, display=False):
331 def mimeencode(ui, s, charsets=None, display=False):
336 '''creates mime text object, encodes it if needed, and sets
332 '''creates mime text object, encodes it if needed, and sets
337 charset and transfer-encoding accordingly.'''
333 charset and transfer-encoding accordingly.'''
338 cs = 'us-ascii'
334 cs = 'us-ascii'
339 if not display:
335 if not display:
340 s, cs = _encode(ui, s, charsets)
336 s, cs = _encode(ui, s, charsets)
341 return mimetextqp(s, 'plain', cs)
337 return mimetextqp(s, 'plain', cs)
General Comments 0
You need to be logged in to leave comments. Login now