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