##// END OF EJS Templates
mail: mime-encode patches that are utf-8...
Christian Ebert -
r7191:d1421221 default
parent child Browse files
Show More
@@ -1,159 +1,170 b''
1 1 # mail.py - mail sending bits for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import os, smtplib, socket
10 10 import email.Header, email.MIMEText, email.Utils
11 11 import util
12 12
13 13 def _smtp(ui):
14 14 '''build an smtp connection and return a function to send mail'''
15 15 local_hostname = ui.config('smtp', 'local_hostname')
16 16 s = smtplib.SMTP(local_hostname=local_hostname)
17 17 mailhost = ui.config('smtp', 'host')
18 18 if not mailhost:
19 19 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
20 20 mailport = int(ui.config('smtp', 'port', 25))
21 21 ui.note(_('sending mail: smtp host %s, port %s\n') %
22 22 (mailhost, mailport))
23 23 s.connect(host=mailhost, port=mailport)
24 24 if ui.configbool('smtp', 'tls'):
25 25 if not hasattr(socket, 'ssl'):
26 26 raise util.Abort(_("can't use TLS: Python SSL support "
27 27 "not installed"))
28 28 ui.note(_('(using tls)\n'))
29 29 s.ehlo()
30 30 s.starttls()
31 31 s.ehlo()
32 32 username = ui.config('smtp', 'username')
33 33 password = ui.config('smtp', 'password')
34 34 if username and not password:
35 35 password = ui.getpass()
36 36 if username and password:
37 37 ui.note(_('(authenticating to mail server as %s)\n') %
38 38 (username))
39 39 s.login(username, password)
40 40
41 41 def send(sender, recipients, msg):
42 42 try:
43 43 return s.sendmail(sender, recipients, msg)
44 44 except smtplib.SMTPRecipientsRefused, inst:
45 45 recipients = [r[1] for r in inst.recipients.values()]
46 46 raise util.Abort('\n' + '\n'.join(recipients))
47 47 except smtplib.SMTPException, inst:
48 48 raise util.Abort(inst)
49 49
50 50 return send
51 51
52 52 def _sendmail(ui, sender, recipients, msg):
53 53 '''send mail using sendmail.'''
54 54 program = ui.config('email', 'method')
55 55 cmdline = '%s -f %s %s' % (program, util.email(sender),
56 56 ' '.join(map(util.email, recipients)))
57 57 ui.note(_('sending mail: %s\n') % cmdline)
58 58 fp = util.popen(cmdline, 'w')
59 59 fp.write(msg)
60 60 ret = fp.close()
61 61 if ret:
62 62 raise util.Abort('%s %s' % (
63 63 os.path.basename(program.split(None, 1)[0]),
64 64 util.explain_exit(ret)[0]))
65 65
66 66 def connect(ui):
67 67 '''make a mail connection. return a function to send mail.
68 68 call as sendmail(sender, list-of-recipients, msg).'''
69 69 if ui.config('email', 'method', 'smtp') == 'smtp':
70 70 return _smtp(ui)
71 71 return lambda s, r, m: _sendmail(ui, s, r, m)
72 72
73 73 def sendmail(ui, sender, recipients, msg):
74 74 send = connect(ui)
75 75 return send(sender, recipients, msg)
76 76
77 77 def validateconfig(ui):
78 78 '''determine if we have enough config data to try sending email.'''
79 79 method = ui.config('email', 'method', 'smtp')
80 80 if method == 'smtp':
81 81 if not ui.config('smtp', 'host'):
82 82 raise util.Abort(_('smtp specified as email transport, '
83 83 'but no smtp host configured'))
84 84 else:
85 85 if not util.find_exe(method):
86 86 raise util.Abort(_('%r specified as email transport, '
87 87 'but not in PATH') % method)
88 88
89 def mimetextpatch(s, subtype='plain', display=False):
90 '''If patch in utf-8 transfer-encode it.'''
91 if not display:
92 for cs in ('us-ascii', 'utf-8'):
93 try:
94 s.decode(cs)
95 return email.MIMEText.MIMEText(s, subtype, cs)
96 except UnicodeDecodeError:
97 pass
98 return email.MIMEText.MIMEText(s, subtype)
99
89 100 def _charsets(ui):
90 101 '''Obtains charsets to send mail parts not containing patches.'''
91 102 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')]
92 103 fallbacks = [util._fallbackencoding.lower(),
93 104 util._encoding.lower(), 'utf-8']
94 105 for cs in fallbacks: # util.unique does not keep order
95 106 if cs not in charsets:
96 107 charsets.append(cs)
97 108 return [cs for cs in charsets if not cs.endswith('ascii')]
98 109
99 110 def _encode(ui, s, charsets):
100 111 '''Returns (converted) string, charset tuple.
101 112 Finds out best charset by cycling through sendcharsets in descending
102 113 order. Tries both _encoding and _fallbackencoding for input. Only as
103 114 last resort send as is in fake ascii.
104 115 Caveat: Do not use for mail parts containing patches!'''
105 116 try:
106 117 s.decode('ascii')
107 118 except UnicodeDecodeError:
108 119 sendcharsets = charsets or _charsets(ui)
109 120 for ics in (util._encoding, util._fallbackencoding):
110 121 try:
111 122 u = s.decode(ics)
112 123 except UnicodeDecodeError:
113 124 continue
114 125 for ocs in sendcharsets:
115 126 try:
116 127 return u.encode(ocs), ocs
117 128 except UnicodeEncodeError:
118 129 pass
119 130 except LookupError:
120 131 ui.warn(_('ignoring invalid sendcharset: %s\n') % cs)
121 132 # if ascii, or all conversion attempts fail, send (broken) ascii
122 133 return s, 'us-ascii'
123 134
124 135 def headencode(ui, s, charsets=None, display=False):
125 136 '''Returns RFC-2047 compliant header from given string.'''
126 137 if not display:
127 138 # split into words?
128 139 s, cs = _encode(ui, s, charsets)
129 140 return str(email.Header.Header(s, cs))
130 141 return s
131 142
132 143 def addressencode(ui, address, charsets=None, display=False):
133 144 '''Turns address into RFC-2047 compliant header.'''
134 145 if display or not address:
135 146 return address or ''
136 147 name, addr = email.Utils.parseaddr(address)
137 148 name = headencode(ui, name, charsets)
138 149 try:
139 150 acc, dom = addr.split('@')
140 151 acc = acc.encode('ascii')
141 152 dom = dom.encode('idna')
142 153 addr = '%s@%s' % (acc, dom)
143 154 except UnicodeDecodeError:
144 155 raise util.Abort(_('invalid email address: %s') % addr)
145 156 except ValueError:
146 157 try:
147 158 # too strict?
148 159 addr = addr.encode('ascii')
149 160 except UnicodeDecodeError:
150 161 raise util.Abort(_('invalid local address: %s') % addr)
151 162 return email.Utils.formataddr((name, addr))
152 163
153 164 def mimeencode(ui, s, charsets=None, display=False):
154 165 '''creates mime text object, encodes it if needed, and sets
155 166 charset and transfer-encoding accordingly.'''
156 167 cs = 'us-ascii'
157 168 if not display:
158 169 s, cs = _encode(ui, s, charsets)
159 170 return email.MIMEText.MIMEText(s, 'plain', cs)
General Comments 0
You need to be logged in to leave comments. Login now