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)