Mercurial > hg-stable
comparison mercurial/mail.py @ 7114:30e49d54c537
mail: add methods to handle non-ascii chars
- headencode, addressencode: encode headers
- mimeencode: encode message parts not containing patches
- new email config "charsets"
Users may configure email.charsets as a list of charsets they
consider appropriate for the recipients of their outgoing mails.
Conversion is tried in this order:
1. us-ascii
(ascii, us-ascii are removed from email.charsets if present)
2. email.charsets (if present) in order given
3. util._fallbackencoding, util._encoding, utf-8
if not already in email.charsets
author | Christian Ebert <blacktrash@gmx.net> |
---|---|
date | Sat, 12 Jul 2008 19:11:59 +0100 |
parents | 962eb403165b |
children | d14212218582 |
comparison
equal
deleted
inserted
replaced
7113:f7fc5f5ecd62 | 7114:30e49d54c537 |
---|---|
5 # This software may be used and distributed according to the terms | 5 # This software may be used and distributed according to the terms |
6 # of the GNU General Public License, incorporated herein by reference. | 6 # of the GNU General Public License, incorporated herein by reference. |
7 | 7 |
8 from i18n import _ | 8 from i18n import _ |
9 import os, smtplib, socket | 9 import os, smtplib, socket |
10 import email.Header, email.MIMEText, email.Utils | |
10 import util | 11 import util |
11 | 12 |
12 def _smtp(ui): | 13 def _smtp(ui): |
13 '''build an smtp connection and return a function to send mail''' | 14 '''build an smtp connection and return a function to send mail''' |
14 local_hostname = ui.config('smtp', 'local_hostname') | 15 local_hostname = ui.config('smtp', 'local_hostname') |
82 'but no smtp host configured')) | 83 'but no smtp host configured')) |
83 else: | 84 else: |
84 if not util.find_exe(method): | 85 if not util.find_exe(method): |
85 raise util.Abort(_('%r specified as email transport, ' | 86 raise util.Abort(_('%r specified as email transport, ' |
86 'but not in PATH') % method) | 87 'but not in PATH') % method) |
88 | |
89 def _charsets(ui): | |
90 '''Obtains charsets to send mail parts not containing patches.''' | |
91 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')] | |
92 fallbacks = [util._fallbackencoding.lower(), | |
93 util._encoding.lower(), 'utf-8'] | |
94 for cs in fallbacks: # util.unique does not keep order | |
95 if cs not in charsets: | |
96 charsets.append(cs) | |
97 return [cs for cs in charsets if not cs.endswith('ascii')] | |
98 | |
99 def _encode(ui, s, charsets): | |
100 '''Returns (converted) string, charset tuple. | |
101 Finds out best charset by cycling through sendcharsets in descending | |
102 order. Tries both _encoding and _fallbackencoding for input. Only as | |
103 last resort send as is in fake ascii. | |
104 Caveat: Do not use for mail parts containing patches!''' | |
105 try: | |
106 s.decode('ascii') | |
107 except UnicodeDecodeError: | |
108 sendcharsets = charsets or _charsets(ui) | |
109 for ics in (util._encoding, util._fallbackencoding): | |
110 try: | |
111 u = s.decode(ics) | |
112 except UnicodeDecodeError: | |
113 continue | |
114 for ocs in sendcharsets: | |
115 try: | |
116 return u.encode(ocs), ocs | |
117 except UnicodeEncodeError: | |
118 pass | |
119 except LookupError: | |
120 ui.warn(_('ignoring invalid sendcharset: %s\n') % cs) | |
121 # if ascii, or all conversion attempts fail, send (broken) ascii | |
122 return s, 'us-ascii' | |
123 | |
124 def headencode(ui, s, charsets=None, display=False): | |
125 '''Returns RFC-2047 compliant header from given string.''' | |
126 if not display: | |
127 # split into words? | |
128 s, cs = _encode(ui, s, charsets) | |
129 return str(email.Header.Header(s, cs)) | |
130 return s | |
131 | |
132 def addressencode(ui, address, charsets=None, display=False): | |
133 '''Turns address into RFC-2047 compliant header.''' | |
134 if display or not address: | |
135 return address or '' | |
136 name, addr = email.Utils.parseaddr(address) | |
137 name = headencode(ui, name, charsets) | |
138 try: | |
139 acc, dom = addr.split('@') | |
140 acc = acc.encode('ascii') | |
141 dom = dom.encode('idna') | |
142 addr = '%s@%s' % (acc, dom) | |
143 except UnicodeDecodeError: | |
144 raise util.Abort(_('invalid email address: %s') % addr) | |
145 except ValueError: | |
146 try: | |
147 # too strict? | |
148 addr = addr.encode('ascii') | |
149 except UnicodeDecodeError: | |
150 raise util.Abort(_('invalid local address: %s') % addr) | |
151 return email.Utils.formataddr((name, addr)) | |
152 | |
153 def mimeencode(ui, s, charsets=None, display=False): | |
154 '''creates mime text object, encodes it if needed, and sets | |
155 charset and transfer-encoding accordingly.''' | |
156 cs = 'us-ascii' | |
157 if not display: | |
158 s, cs = _encode(ui, s, charsets) | |
159 return email.MIMEText.MIMEText(s, 'plain', cs) |