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