comparison mercurial/mail.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children eef9a2d67051
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
42 smtplib.SMTP.__init__(self, **kwargs) 42 smtplib.SMTP.__init__(self, **kwargs)
43 self._ui = ui 43 self._ui = ui
44 self._host = host 44 self._host = host
45 45
46 def starttls(self, keyfile=None, certfile=None): 46 def starttls(self, keyfile=None, certfile=None):
47 if not self.has_extn("starttls"): 47 if not self.has_extn(b"starttls"):
48 msg = "STARTTLS extension not supported by server" 48 msg = b"STARTTLS extension not supported by server"
49 raise smtplib.SMTPException(msg) 49 raise smtplib.SMTPException(msg)
50 (resp, reply) = self.docmd("STARTTLS") 50 (resp, reply) = self.docmd(b"STARTTLS")
51 if resp == 220: 51 if resp == 220:
52 self.sock = sslutil.wrapsocket( 52 self.sock = sslutil.wrapsocket(
53 self.sock, 53 self.sock,
54 keyfile, 54 keyfile,
55 certfile, 55 certfile,
78 self.default_port = smtplib.SMTP_SSL_PORT 78 self.default_port = smtplib.SMTP_SSL_PORT
79 self._ui = ui 79 self._ui = ui
80 80
81 def _get_socket(self, host, port, timeout): 81 def _get_socket(self, host, port, timeout):
82 if self.debuglevel > 0: 82 if self.debuglevel > 0:
83 self._ui.debug('connect: %r\n' % ((host, port),)) 83 self._ui.debug(b'connect: %r\n' % ((host, port),))
84 new_socket = socket.create_connection((host, port), timeout) 84 new_socket = socket.create_connection((host, port), timeout)
85 new_socket = sslutil.wrapsocket( 85 new_socket = sslutil.wrapsocket(
86 new_socket, 86 new_socket,
87 self.keyfile, 87 self.keyfile,
88 self.certfile, 88 self.certfile,
104 return False 104 return False
105 105
106 106
107 def _smtp(ui): 107 def _smtp(ui):
108 '''build an smtp connection and return a function to send mail''' 108 '''build an smtp connection and return a function to send mail'''
109 local_hostname = ui.config('smtp', 'local_hostname') 109 local_hostname = ui.config(b'smtp', b'local_hostname')
110 tls = ui.config('smtp', 'tls') 110 tls = ui.config(b'smtp', b'tls')
111 # backward compatible: when tls = true, we use starttls. 111 # backward compatible: when tls = true, we use starttls.
112 starttls = tls == 'starttls' or stringutil.parsebool(tls) 112 starttls = tls == b'starttls' or stringutil.parsebool(tls)
113 smtps = tls == 'smtps' 113 smtps = tls == b'smtps'
114 if (starttls or smtps) and not _pyhastls(): 114 if (starttls or smtps) and not _pyhastls():
115 raise error.Abort(_("can't use TLS: Python SSL support not installed")) 115 raise error.Abort(_(b"can't use TLS: Python SSL support not installed"))
116 mailhost = ui.config('smtp', 'host') 116 mailhost = ui.config(b'smtp', b'host')
117 if not mailhost: 117 if not mailhost:
118 raise error.Abort(_('smtp.host not configured - cannot send mail')) 118 raise error.Abort(_(b'smtp.host not configured - cannot send mail'))
119 if smtps: 119 if smtps:
120 ui.note(_('(using smtps)\n')) 120 ui.note(_(b'(using smtps)\n'))
121 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost) 121 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
122 elif starttls: 122 elif starttls:
123 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost) 123 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
124 else: 124 else:
125 s = smtplib.SMTP(local_hostname=local_hostname) 125 s = smtplib.SMTP(local_hostname=local_hostname)
126 if smtps: 126 if smtps:
127 defaultport = 465 127 defaultport = 465
128 else: 128 else:
129 defaultport = 25 129 defaultport = 25
130 mailport = util.getport(ui.config('smtp', 'port', defaultport)) 130 mailport = util.getport(ui.config(b'smtp', b'port', defaultport))
131 ui.note(_('sending mail: smtp host %s, port %d\n') % (mailhost, mailport)) 131 ui.note(_(b'sending mail: smtp host %s, port %d\n') % (mailhost, mailport))
132 s.connect(host=mailhost, port=mailport) 132 s.connect(host=mailhost, port=mailport)
133 if starttls: 133 if starttls:
134 ui.note(_('(using starttls)\n')) 134 ui.note(_(b'(using starttls)\n'))
135 s.ehlo() 135 s.ehlo()
136 s.starttls() 136 s.starttls()
137 s.ehlo() 137 s.ehlo()
138 if starttls or smtps: 138 if starttls or smtps:
139 ui.note(_('(verifying remote certificate)\n')) 139 ui.note(_(b'(verifying remote certificate)\n'))
140 sslutil.validatesocket(s.sock) 140 sslutil.validatesocket(s.sock)
141 username = ui.config('smtp', 'username') 141 username = ui.config(b'smtp', b'username')
142 password = ui.config('smtp', 'password') 142 password = ui.config(b'smtp', b'password')
143 if username and not password: 143 if username and not password:
144 password = ui.getpass() 144 password = ui.getpass()
145 if username and password: 145 if username and password:
146 ui.note(_('(authenticating to mail server as %s)\n') % username) 146 ui.note(_(b'(authenticating to mail server as %s)\n') % username)
147 try: 147 try:
148 s.login(username, password) 148 s.login(username, password)
149 except smtplib.SMTPException as inst: 149 except smtplib.SMTPException as inst:
150 raise error.Abort(inst) 150 raise error.Abort(inst)
151 151
152 def send(sender, recipients, msg): 152 def send(sender, recipients, msg):
153 try: 153 try:
154 return s.sendmail(sender, recipients, msg) 154 return s.sendmail(sender, recipients, msg)
155 except smtplib.SMTPRecipientsRefused as inst: 155 except smtplib.SMTPRecipientsRefused as inst:
156 recipients = [r[1] for r in inst.recipients.values()] 156 recipients = [r[1] for r in inst.recipients.values()]
157 raise error.Abort('\n' + '\n'.join(recipients)) 157 raise error.Abort(b'\n' + b'\n'.join(recipients))
158 except smtplib.SMTPException as inst: 158 except smtplib.SMTPException as inst:
159 raise error.Abort(inst) 159 raise error.Abort(inst)
160 160
161 return send 161 return send
162 162
163 163
164 def _sendmail(ui, sender, recipients, msg): 164 def _sendmail(ui, sender, recipients, msg):
165 '''send mail using sendmail.''' 165 '''send mail using sendmail.'''
166 program = ui.config('email', 'method') 166 program = ui.config(b'email', b'method')
167 stremail = lambda x: ( 167 stremail = lambda x: (
168 procutil.quote(stringutil.email(encoding.strtolocal(x))) 168 procutil.quote(stringutil.email(encoding.strtolocal(x)))
169 ) 169 )
170 cmdline = '%s -f %s %s' % ( 170 cmdline = b'%s -f %s %s' % (
171 program, 171 program,
172 stremail(sender), 172 stremail(sender),
173 ' '.join(map(stremail, recipients)), 173 b' '.join(map(stremail, recipients)),
174 ) 174 )
175 ui.note(_('sending mail: %s\n') % cmdline) 175 ui.note(_(b'sending mail: %s\n') % cmdline)
176 fp = procutil.popen(cmdline, 'wb') 176 fp = procutil.popen(cmdline, b'wb')
177 fp.write(util.tonativeeol(msg)) 177 fp.write(util.tonativeeol(msg))
178 ret = fp.close() 178 ret = fp.close()
179 if ret: 179 if ret:
180 raise error.Abort( 180 raise error.Abort(
181 '%s %s' 181 b'%s %s'
182 % ( 182 % (
183 os.path.basename(program.split(None, 1)[0]), 183 os.path.basename(program.split(None, 1)[0]),
184 procutil.explainexit(ret), 184 procutil.explainexit(ret),
185 ) 185 )
186 ) 186 )
187 187
188 188
189 def _mbox(mbox, sender, recipients, msg): 189 def _mbox(mbox, sender, recipients, msg):
190 '''write mails to mbox''' 190 '''write mails to mbox'''
191 fp = open(mbox, 'ab+') 191 fp = open(mbox, b'ab+')
192 # Should be time.asctime(), but Windows prints 2-characters day 192 # Should be time.asctime(), but Windows prints 2-characters day
193 # of month instead of one. Make them print the same thing. 193 # of month instead of one. Make them print the same thing.
194 date = time.strftime(r'%a %b %d %H:%M:%S %Y', time.localtime()) 194 date = time.strftime(r'%a %b %d %H:%M:%S %Y', time.localtime())
195 fp.write( 195 fp.write(
196 'From %s %s\n' 196 b'From %s %s\n'
197 % (encoding.strtolocal(sender), encoding.strtolocal(date)) 197 % (encoding.strtolocal(sender), encoding.strtolocal(date))
198 ) 198 )
199 fp.write(msg) 199 fp.write(msg)
200 fp.write('\n\n') 200 fp.write(b'\n\n')
201 fp.close() 201 fp.close()
202 202
203 203
204 def connect(ui, mbox=None): 204 def connect(ui, mbox=None):
205 '''make a mail connection. return a function to send mail. 205 '''make a mail connection. return a function to send mail.
206 call as sendmail(sender, list-of-recipients, msg).''' 206 call as sendmail(sender, list-of-recipients, msg).'''
207 if mbox: 207 if mbox:
208 open(mbox, 'wb').close() 208 open(mbox, b'wb').close()
209 return lambda s, r, m: _mbox(mbox, s, r, m) 209 return lambda s, r, m: _mbox(mbox, s, r, m)
210 if ui.config('email', 'method') == 'smtp': 210 if ui.config(b'email', b'method') == b'smtp':
211 return _smtp(ui) 211 return _smtp(ui)
212 return lambda s, r, m: _sendmail(ui, s, r, m) 212 return lambda s, r, m: _sendmail(ui, s, r, m)
213 213
214 214
215 def sendmail(ui, sender, recipients, msg, mbox=None): 215 def sendmail(ui, sender, recipients, msg, mbox=None):
217 return send(sender, recipients, msg) 217 return send(sender, recipients, msg)
218 218
219 219
220 def validateconfig(ui): 220 def validateconfig(ui):
221 '''determine if we have enough config data to try sending email.''' 221 '''determine if we have enough config data to try sending email.'''
222 method = ui.config('email', 'method') 222 method = ui.config(b'email', b'method')
223 if method == 'smtp': 223 if method == b'smtp':
224 if not ui.config('smtp', 'host'): 224 if not ui.config(b'smtp', b'host'):
225 raise error.Abort( 225 raise error.Abort(
226 _( 226 _(
227 'smtp specified as email transport, ' 227 b'smtp specified as email transport, '
228 'but no smtp host configured' 228 b'but no smtp host configured'
229 ) 229 )
230 ) 230 )
231 else: 231 else:
232 if not procutil.findexe(method): 232 if not procutil.findexe(method):
233 raise error.Abort( 233 raise error.Abort(
234 _('%r specified as email transport, ' 'but not in PATH') 234 _(b'%r specified as email transport, ' b'but not in PATH')
235 % method 235 % method
236 ) 236 )
237 237
238 238
239 def codec2iana(cs): 239 def codec2iana(cs):
240 '''''' 240 ''''''
241 cs = pycompat.sysbytes(email.charset.Charset(cs).input_charset.lower()) 241 cs = pycompat.sysbytes(email.charset.Charset(cs).input_charset.lower())
242 242
243 # "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1" 243 # "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1"
244 if cs.startswith("iso") and not cs.startswith("iso-"): 244 if cs.startswith(b"iso") and not cs.startswith(b"iso-"):
245 return "iso-" + cs[3:] 245 return b"iso-" + cs[3:]
246 return cs 246 return cs
247 247
248 248
249 def mimetextpatch(s, subtype='plain', display=False): 249 def mimetextpatch(s, subtype=b'plain', display=False):
250 '''Return MIME message suitable for a patch. 250 '''Return MIME message suitable for a patch.
251 Charset will be detected by first trying to decode as us-ascii, then utf-8, 251 Charset will be detected by first trying to decode as us-ascii, then utf-8,
252 and finally the global encodings. If all those fail, fall back to 252 and finally the global encodings. If all those fail, fall back to
253 ISO-8859-1, an encoding with that allows all byte sequences. 253 ISO-8859-1, an encoding with that allows all byte sequences.
254 Transfer encodings will be used if necessary.''' 254 Transfer encodings will be used if necessary.'''
255 255
256 cs = ['us-ascii', 'utf-8', encoding.encoding, encoding.fallbackencoding] 256 cs = [b'us-ascii', b'utf-8', encoding.encoding, encoding.fallbackencoding]
257 if display: 257 if display:
258 cs = ['us-ascii'] 258 cs = [b'us-ascii']
259 for charset in cs: 259 for charset in cs:
260 try: 260 try:
261 s.decode(pycompat.sysstr(charset)) 261 s.decode(pycompat.sysstr(charset))
262 return mimetextqp(s, subtype, codec2iana(charset)) 262 return mimetextqp(s, subtype, codec2iana(charset))
263 except UnicodeDecodeError: 263 except UnicodeDecodeError:
264 pass 264 pass
265 265
266 return mimetextqp(s, subtype, "iso-8859-1") 266 return mimetextqp(s, subtype, b"iso-8859-1")
267 267
268 268
269 def mimetextqp(body, subtype, charset): 269 def mimetextqp(body, subtype, charset):
270 '''Return MIME message. 270 '''Return MIME message.
271 Quoted-printable transfer encoding will be used if necessary. 271 Quoted-printable transfer encoding will be used if necessary.
272 ''' 272 '''
273 cs = email.charset.Charset(charset) 273 cs = email.charset.Charset(charset)
274 msg = email.message.Message() 274 msg = email.message.Message()
275 msg.set_type(pycompat.sysstr('text/' + subtype)) 275 msg.set_type(pycompat.sysstr(b'text/' + subtype))
276 276
277 for line in body.splitlines(): 277 for line in body.splitlines():
278 if len(line) > 950: 278 if len(line) > 950:
279 cs.body_encoding = email.charset.QP 279 cs.body_encoding = email.charset.QP
280 break 280 break
291 return msg 291 return msg
292 292
293 293
294 def _charsets(ui): 294 def _charsets(ui):
295 '''Obtains charsets to send mail parts not containing patches.''' 295 '''Obtains charsets to send mail parts not containing patches.'''
296 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')] 296 charsets = [cs.lower() for cs in ui.configlist(b'email', b'charsets')]
297 fallbacks = [ 297 fallbacks = [
298 encoding.fallbackencoding.lower(), 298 encoding.fallbackencoding.lower(),
299 encoding.encoding.lower(), 299 encoding.encoding.lower(),
300 'utf-8', 300 b'utf-8',
301 ] 301 ]
302 for cs in fallbacks: # find unique charsets while keeping order 302 for cs in fallbacks: # find unique charsets while keeping order
303 if cs not in charsets: 303 if cs not in charsets:
304 charsets.append(cs) 304 charsets.append(cs)
305 return [cs for cs in charsets if not cs.endswith('ascii')] 305 return [cs for cs in charsets if not cs.endswith(b'ascii')]
306 306
307 307
308 def _encode(ui, s, charsets): 308 def _encode(ui, s, charsets):
309 '''Returns (converted) string, charset tuple. 309 '''Returns (converted) string, charset tuple.
310 Finds out best charset by cycling through sendcharsets in descending 310 Finds out best charset by cycling through sendcharsets in descending
320 try: 320 try:
321 return s.encode(pycompat.sysstr(ocs)), ocs 321 return s.encode(pycompat.sysstr(ocs)), ocs
322 except UnicodeEncodeError: 322 except UnicodeEncodeError:
323 pass 323 pass
324 except LookupError: 324 except LookupError:
325 ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs) 325 ui.warn(_(b'ignoring invalid sendcharset: %s\n') % ocs)
326 else: 326 else:
327 # Everything failed, ascii-armor what we've got and send it. 327 # Everything failed, ascii-armor what we've got and send it.
328 return s.encode('ascii', 'backslashreplace') 328 return s.encode('ascii', 'backslashreplace')
329 # We have a bytes of unknown encoding. We'll try and guess a valid 329 # We have a bytes of unknown encoding. We'll try and guess a valid
330 # encoding, falling back to pretending we had ascii even though we 330 # encoding, falling back to pretending we had ascii even though we
341 try: 341 try:
342 return u.encode(pycompat.sysstr(ocs)), ocs 342 return u.encode(pycompat.sysstr(ocs)), ocs
343 except UnicodeEncodeError: 343 except UnicodeEncodeError:
344 pass 344 pass
345 except LookupError: 345 except LookupError:
346 ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs) 346 ui.warn(_(b'ignoring invalid sendcharset: %s\n') % ocs)
347 # if ascii, or all conversion attempts fail, send (broken) ascii 347 # if ascii, or all conversion attempts fail, send (broken) ascii
348 return s, 'us-ascii' 348 return s, b'us-ascii'
349 349
350 350
351 def headencode(ui, s, charsets=None, display=False): 351 def headencode(ui, s, charsets=None, display=False):
352 '''Returns RFC-2047 compliant header from given string.''' 352 '''Returns RFC-2047 compliant header from given string.'''
353 if not display: 353 if not display:
359 359
360 def _addressencode(ui, name, addr, charsets=None): 360 def _addressencode(ui, name, addr, charsets=None):
361 assert isinstance(addr, bytes) 361 assert isinstance(addr, bytes)
362 name = headencode(ui, name, charsets) 362 name = headencode(ui, name, charsets)
363 try: 363 try:
364 acc, dom = addr.split('@') 364 acc, dom = addr.split(b'@')
365 acc.decode('ascii') 365 acc.decode('ascii')
366 dom = dom.decode(pycompat.sysstr(encoding.encoding)).encode('idna') 366 dom = dom.decode(pycompat.sysstr(encoding.encoding)).encode('idna')
367 addr = '%s@%s' % (acc, dom) 367 addr = b'%s@%s' % (acc, dom)
368 except UnicodeDecodeError: 368 except UnicodeDecodeError:
369 raise error.Abort(_('invalid email address: %s') % addr) 369 raise error.Abort(_(b'invalid email address: %s') % addr)
370 except ValueError: 370 except ValueError:
371 try: 371 try:
372 # too strict? 372 # too strict?
373 addr.decode('ascii') 373 addr.decode('ascii')
374 except UnicodeDecodeError: 374 except UnicodeDecodeError:
375 raise error.Abort(_('invalid local address: %s') % addr) 375 raise error.Abort(_(b'invalid local address: %s') % addr)
376 return pycompat.bytesurl( 376 return pycompat.bytesurl(
377 email.utils.formataddr((name, encoding.strfromlocal(addr))) 377 email.utils.formataddr((name, encoding.strfromlocal(addr)))
378 ) 378 )
379 379
380 380
381 def addressencode(ui, address, charsets=None, display=False): 381 def addressencode(ui, address, charsets=None, display=False):
382 '''Turns address into RFC-2047 compliant header.''' 382 '''Turns address into RFC-2047 compliant header.'''
383 if display or not address: 383 if display or not address:
384 return address or '' 384 return address or b''
385 name, addr = email.utils.parseaddr(encoding.strfromlocal(address)) 385 name, addr = email.utils.parseaddr(encoding.strfromlocal(address))
386 return _addressencode(ui, name, encoding.strtolocal(addr), charsets) 386 return _addressencode(ui, name, encoding.strtolocal(addr), charsets)
387 387
388 388
389 def addrlistencode(ui, addrs, charsets=None, display=False): 389 def addrlistencode(ui, addrs, charsets=None, display=False):
406 406
407 407
408 def mimeencode(ui, s, charsets=None, display=False): 408 def mimeencode(ui, s, charsets=None, display=False):
409 '''creates mime text object, encodes it if needed, and sets 409 '''creates mime text object, encodes it if needed, and sets
410 charset and transfer-encoding accordingly.''' 410 charset and transfer-encoding accordingly.'''
411 cs = 'us-ascii' 411 cs = b'us-ascii'
412 if not display: 412 if not display:
413 s, cs = _encode(ui, s, charsets) 413 s, cs = _encode(ui, s, charsets)
414 return mimetextqp(s, 'plain', cs) 414 return mimetextqp(s, b'plain', cs)
415 415
416 416
417 if pycompat.ispy3: 417 if pycompat.ispy3:
418 418
419 def parse(fp): 419 def parse(fp):