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) |