Mercurial > public > mercurial-scm > hg
comparison mercurial/mail.py @ 43076:2372284d9457
formatting: blacken the codebase
This is using my patch to black
(https://github.com/psf/black/pull/826) so we don't un-wrap collection
literals.
Done with:
hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S
# skip-blame mass-reformatting only
# no-check-commit reformats foo_bar functions
Differential Revision: https://phab.mercurial-scm.org/D6971
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 06 Oct 2019 09:45:02 -0400 |
parents | 2cc453284d5c |
children | 687b865b95ad |
comparison
equal
deleted
inserted
replaced
43075:57875cf423c9 | 43076:2372284d9457 |
---|---|
29 from .utils import ( | 29 from .utils import ( |
30 procutil, | 30 procutil, |
31 stringutil, | 31 stringutil, |
32 ) | 32 ) |
33 | 33 |
34 | |
34 class STARTTLS(smtplib.SMTP): | 35 class STARTTLS(smtplib.SMTP): |
35 '''Derived class to verify the peer certificate for STARTTLS. | 36 '''Derived class to verify the peer certificate for STARTTLS. |
36 | 37 |
37 This class allows to pass any keyword arguments to SSL socket creation. | 38 This class allows to pass any keyword arguments to SSL socket creation. |
38 ''' | 39 ''' |
40 | |
39 def __init__(self, ui, host=None, **kwargs): | 41 def __init__(self, ui, host=None, **kwargs): |
40 smtplib.SMTP.__init__(self, **kwargs) | 42 smtplib.SMTP.__init__(self, **kwargs) |
41 self._ui = ui | 43 self._ui = ui |
42 self._host = host | 44 self._host = host |
43 | 45 |
45 if not self.has_extn("starttls"): | 47 if not self.has_extn("starttls"): |
46 msg = "STARTTLS extension not supported by server" | 48 msg = "STARTTLS extension not supported by server" |
47 raise smtplib.SMTPException(msg) | 49 raise smtplib.SMTPException(msg) |
48 (resp, reply) = self.docmd("STARTTLS") | 50 (resp, reply) = self.docmd("STARTTLS") |
49 if resp == 220: | 51 if resp == 220: |
50 self.sock = sslutil.wrapsocket(self.sock, keyfile, certfile, | 52 self.sock = sslutil.wrapsocket( |
51 ui=self._ui, | 53 self.sock, |
52 serverhostname=self._host) | 54 keyfile, |
55 certfile, | |
56 ui=self._ui, | |
57 serverhostname=self._host, | |
58 ) | |
53 self.file = smtplib.SSLFakeFile(self.sock) | 59 self.file = smtplib.SSLFakeFile(self.sock) |
54 self.helo_resp = None | 60 self.helo_resp = None |
55 self.ehlo_resp = None | 61 self.ehlo_resp = None |
56 self.esmtp_features = {} | 62 self.esmtp_features = {} |
57 self.does_esmtp = 0 | 63 self.does_esmtp = 0 |
58 return (resp, reply) | 64 return (resp, reply) |
59 | 65 |
66 | |
60 class SMTPS(smtplib.SMTP): | 67 class SMTPS(smtplib.SMTP): |
61 '''Derived class to verify the peer certificate for SMTPS. | 68 '''Derived class to verify the peer certificate for SMTPS. |
62 | 69 |
63 This class allows to pass any keyword arguments to SSL socket creation. | 70 This class allows to pass any keyword arguments to SSL socket creation. |
64 ''' | 71 ''' |
65 def __init__(self, ui, keyfile=None, certfile=None, host=None, | 72 |
66 **kwargs): | 73 def __init__(self, ui, keyfile=None, certfile=None, host=None, **kwargs): |
67 self.keyfile = keyfile | 74 self.keyfile = keyfile |
68 self.certfile = certfile | 75 self.certfile = certfile |
69 smtplib.SMTP.__init__(self, **kwargs) | 76 smtplib.SMTP.__init__(self, **kwargs) |
70 self._host = host | 77 self._host = host |
71 self.default_port = smtplib.SMTP_SSL_PORT | 78 self.default_port = smtplib.SMTP_SSL_PORT |
73 | 80 |
74 def _get_socket(self, host, port, timeout): | 81 def _get_socket(self, host, port, timeout): |
75 if self.debuglevel > 0: | 82 if self.debuglevel > 0: |
76 self._ui.debug('connect: %r\n' % ((host, port),)) | 83 self._ui.debug('connect: %r\n' % ((host, port),)) |
77 new_socket = socket.create_connection((host, port), timeout) | 84 new_socket = socket.create_connection((host, port), timeout) |
78 new_socket = sslutil.wrapsocket(new_socket, | 85 new_socket = sslutil.wrapsocket( |
79 self.keyfile, self.certfile, | 86 new_socket, |
80 ui=self._ui, | 87 self.keyfile, |
81 serverhostname=self._host) | 88 self.certfile, |
89 ui=self._ui, | |
90 serverhostname=self._host, | |
91 ) | |
82 self.file = new_socket.makefile(r'rb') | 92 self.file = new_socket.makefile(r'rb') |
83 return new_socket | 93 return new_socket |
94 | |
84 | 95 |
85 def _pyhastls(): | 96 def _pyhastls(): |
86 """Returns true iff Python has TLS support, false otherwise.""" | 97 """Returns true iff Python has TLS support, false otherwise.""" |
87 try: | 98 try: |
88 import ssl | 99 import ssl |
100 | |
89 getattr(ssl, 'HAS_TLS', False) | 101 getattr(ssl, 'HAS_TLS', False) |
90 return True | 102 return True |
91 except ImportError: | 103 except ImportError: |
92 return False | 104 return False |
105 | |
93 | 106 |
94 def _smtp(ui): | 107 def _smtp(ui): |
95 '''build an smtp connection and return a function to send mail''' | 108 '''build an smtp connection and return a function to send mail''' |
96 local_hostname = ui.config('smtp', 'local_hostname') | 109 local_hostname = ui.config('smtp', 'local_hostname') |
97 tls = ui.config('smtp', 'tls') | 110 tls = ui.config('smtp', 'tls') |
113 if smtps: | 126 if smtps: |
114 defaultport = 465 | 127 defaultport = 465 |
115 else: | 128 else: |
116 defaultport = 25 | 129 defaultport = 25 |
117 mailport = util.getport(ui.config('smtp', 'port', defaultport)) | 130 mailport = util.getport(ui.config('smtp', 'port', defaultport)) |
118 ui.note(_('sending mail: smtp host %s, port %d\n') % | 131 ui.note(_('sending mail: smtp host %s, port %d\n') % (mailhost, mailport)) |
119 (mailhost, mailport)) | |
120 s.connect(host=mailhost, port=mailport) | 132 s.connect(host=mailhost, port=mailport) |
121 if starttls: | 133 if starttls: |
122 ui.note(_('(using starttls)\n')) | 134 ui.note(_('(using starttls)\n')) |
123 s.ehlo() | 135 s.ehlo() |
124 s.starttls() | 136 s.starttls() |
129 username = ui.config('smtp', 'username') | 141 username = ui.config('smtp', 'username') |
130 password = ui.config('smtp', 'password') | 142 password = ui.config('smtp', 'password') |
131 if username and not password: | 143 if username and not password: |
132 password = ui.getpass() | 144 password = ui.getpass() |
133 if username and password: | 145 if username and password: |
134 ui.note(_('(authenticating to mail server as %s)\n') % | 146 ui.note(_('(authenticating to mail server as %s)\n') % username) |
135 (username)) | |
136 try: | 147 try: |
137 s.login(username, password) | 148 s.login(username, password) |
138 except smtplib.SMTPException as inst: | 149 except smtplib.SMTPException as inst: |
139 raise error.Abort(inst) | 150 raise error.Abort(inst) |
140 | 151 |
147 except smtplib.SMTPException as inst: | 158 except smtplib.SMTPException as inst: |
148 raise error.Abort(inst) | 159 raise error.Abort(inst) |
149 | 160 |
150 return send | 161 return send |
151 | 162 |
163 | |
152 def _sendmail(ui, sender, recipients, msg): | 164 def _sendmail(ui, sender, recipients, msg): |
153 '''send mail using sendmail.''' | 165 '''send mail using sendmail.''' |
154 program = ui.config('email', 'method') | 166 program = ui.config('email', 'method') |
155 stremail = lambda x: ( | 167 stremail = lambda x: ( |
156 procutil.quote(stringutil.email(encoding.strtolocal(x)))) | 168 procutil.quote(stringutil.email(encoding.strtolocal(x))) |
157 cmdline = '%s -f %s %s' % (program, stremail(sender), | 169 ) |
158 ' '.join(map(stremail, recipients))) | 170 cmdline = '%s -f %s %s' % ( |
171 program, | |
172 stremail(sender), | |
173 ' '.join(map(stremail, recipients)), | |
174 ) | |
159 ui.note(_('sending mail: %s\n') % cmdline) | 175 ui.note(_('sending mail: %s\n') % cmdline) |
160 fp = procutil.popen(cmdline, 'wb') | 176 fp = procutil.popen(cmdline, 'wb') |
161 fp.write(util.tonativeeol(msg)) | 177 fp.write(util.tonativeeol(msg)) |
162 ret = fp.close() | 178 ret = fp.close() |
163 if ret: | 179 if ret: |
164 raise error.Abort('%s %s' % ( | 180 raise error.Abort( |
165 os.path.basename(program.split(None, 1)[0]), | 181 '%s %s' |
166 procutil.explainexit(ret))) | 182 % ( |
183 os.path.basename(program.split(None, 1)[0]), | |
184 procutil.explainexit(ret), | |
185 ) | |
186 ) | |
187 | |
167 | 188 |
168 def _mbox(mbox, sender, recipients, msg): | 189 def _mbox(mbox, sender, recipients, msg): |
169 '''write mails to mbox''' | 190 '''write mails to mbox''' |
170 fp = open(mbox, 'ab+') | 191 fp = open(mbox, 'ab+') |
171 # Should be time.asctime(), but Windows prints 2-characters day | 192 # Should be time.asctime(), but Windows prints 2-characters day |
172 # of month instead of one. Make them print the same thing. | 193 # of month instead of one. Make them print the same thing. |
173 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()) |
174 fp.write('From %s %s\n' % (encoding.strtolocal(sender), | 195 fp.write( |
175 encoding.strtolocal(date))) | 196 'From %s %s\n' |
197 % (encoding.strtolocal(sender), encoding.strtolocal(date)) | |
198 ) | |
176 fp.write(msg) | 199 fp.write(msg) |
177 fp.write('\n\n') | 200 fp.write('\n\n') |
178 fp.close() | 201 fp.close() |
202 | |
179 | 203 |
180 def connect(ui, mbox=None): | 204 def connect(ui, mbox=None): |
181 '''make a mail connection. return a function to send mail. | 205 '''make a mail connection. return a function to send mail. |
182 call as sendmail(sender, list-of-recipients, msg).''' | 206 call as sendmail(sender, list-of-recipients, msg).''' |
183 if mbox: | 207 if mbox: |
185 return lambda s, r, m: _mbox(mbox, s, r, m) | 209 return lambda s, r, m: _mbox(mbox, s, r, m) |
186 if ui.config('email', 'method') == 'smtp': | 210 if ui.config('email', 'method') == 'smtp': |
187 return _smtp(ui) | 211 return _smtp(ui) |
188 return lambda s, r, m: _sendmail(ui, s, r, m) | 212 return lambda s, r, m: _sendmail(ui, s, r, m) |
189 | 213 |
214 | |
190 def sendmail(ui, sender, recipients, msg, mbox=None): | 215 def sendmail(ui, sender, recipients, msg, mbox=None): |
191 send = connect(ui, mbox=mbox) | 216 send = connect(ui, mbox=mbox) |
192 return send(sender, recipients, msg) | 217 return send(sender, recipients, msg) |
218 | |
193 | 219 |
194 def validateconfig(ui): | 220 def validateconfig(ui): |
195 '''determine if we have enough config data to try sending email.''' | 221 '''determine if we have enough config data to try sending email.''' |
196 method = ui.config('email', 'method') | 222 method = ui.config('email', 'method') |
197 if method == 'smtp': | 223 if method == 'smtp': |
198 if not ui.config('smtp', 'host'): | 224 if not ui.config('smtp', 'host'): |
199 raise error.Abort(_('smtp specified as email transport, ' | 225 raise error.Abort( |
200 'but no smtp host configured')) | 226 _( |
227 'smtp specified as email transport, ' | |
228 'but no smtp host configured' | |
229 ) | |
230 ) | |
201 else: | 231 else: |
202 if not procutil.findexe(method): | 232 if not procutil.findexe(method): |
203 raise error.Abort(_('%r specified as email transport, ' | 233 raise error.Abort( |
204 'but not in PATH') % method) | 234 _('%r specified as email transport, ' 'but not in PATH') |
235 % method | |
236 ) | |
237 | |
205 | 238 |
206 def codec2iana(cs): | 239 def codec2iana(cs): |
207 '''''' | 240 '''''' |
208 cs = pycompat.sysbytes(email.charset.Charset(cs).input_charset.lower()) | 241 cs = pycompat.sysbytes(email.charset.Charset(cs).input_charset.lower()) |
209 | 242 |
210 # "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1" | 243 # "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1" |
211 if cs.startswith("iso") and not cs.startswith("iso-"): | 244 if cs.startswith("iso") and not cs.startswith("iso-"): |
212 return "iso-" + cs[3:] | 245 return "iso-" + cs[3:] |
213 return cs | 246 return cs |
247 | |
214 | 248 |
215 def mimetextpatch(s, subtype='plain', display=False): | 249 def mimetextpatch(s, subtype='plain', display=False): |
216 '''Return MIME message suitable for a patch. | 250 '''Return MIME message suitable for a patch. |
217 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, |
218 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 |
228 return mimetextqp(s, subtype, codec2iana(charset)) | 262 return mimetextqp(s, subtype, codec2iana(charset)) |
229 except UnicodeDecodeError: | 263 except UnicodeDecodeError: |
230 pass | 264 pass |
231 | 265 |
232 return mimetextqp(s, subtype, "iso-8859-1") | 266 return mimetextqp(s, subtype, "iso-8859-1") |
267 | |
233 | 268 |
234 def mimetextqp(body, subtype, charset): | 269 def mimetextqp(body, subtype, charset): |
235 '''Return MIME message. | 270 '''Return MIME message. |
236 Quoted-printable transfer encoding will be used if necessary. | 271 Quoted-printable transfer encoding will be used if necessary. |
237 ''' | 272 ''' |
253 # use ascii in the absence of a charset). | 288 # use ascii in the absence of a charset). |
254 msg.set_payload(body, cs) | 289 msg.set_payload(body, cs) |
255 | 290 |
256 return msg | 291 return msg |
257 | 292 |
293 | |
258 def _charsets(ui): | 294 def _charsets(ui): |
259 '''Obtains charsets to send mail parts not containing patches.''' | 295 '''Obtains charsets to send mail parts not containing patches.''' |
260 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')] | 296 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')] |
261 fallbacks = [encoding.fallbackencoding.lower(), | 297 fallbacks = [ |
262 encoding.encoding.lower(), 'utf-8'] | 298 encoding.fallbackencoding.lower(), |
263 for cs in fallbacks: # find unique charsets while keeping order | 299 encoding.encoding.lower(), |
300 'utf-8', | |
301 ] | |
302 for cs in fallbacks: # find unique charsets while keeping order | |
264 if cs not in charsets: | 303 if cs not in charsets: |
265 charsets.append(cs) | 304 charsets.append(cs) |
266 return [cs for cs in charsets if not cs.endswith('ascii')] | 305 return [cs for cs in charsets if not cs.endswith('ascii')] |
306 | |
267 | 307 |
268 def _encode(ui, s, charsets): | 308 def _encode(ui, s, charsets): |
269 '''Returns (converted) string, charset tuple. | 309 '''Returns (converted) string, charset tuple. |
270 Finds out best charset by cycling through sendcharsets in descending | 310 Finds out best charset by cycling through sendcharsets in descending |
271 order. Tries both encoding and fallbackencoding for input. Only as | 311 order. Tries both encoding and fallbackencoding for input. Only as |
305 except LookupError: | 345 except LookupError: |
306 ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs) | 346 ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs) |
307 # if ascii, or all conversion attempts fail, send (broken) ascii | 347 # if ascii, or all conversion attempts fail, send (broken) ascii |
308 return s, 'us-ascii' | 348 return s, 'us-ascii' |
309 | 349 |
350 | |
310 def headencode(ui, s, charsets=None, display=False): | 351 def headencode(ui, s, charsets=None, display=False): |
311 '''Returns RFC-2047 compliant header from given string.''' | 352 '''Returns RFC-2047 compliant header from given string.''' |
312 if not display: | 353 if not display: |
313 # split into words? | 354 # split into words? |
314 s, cs = _encode(ui, s, charsets) | 355 s, cs = _encode(ui, s, charsets) |
315 return str(email.header.Header(s, cs)) | 356 return str(email.header.Header(s, cs)) |
316 return s | 357 return s |
358 | |
317 | 359 |
318 def _addressencode(ui, name, addr, charsets=None): | 360 def _addressencode(ui, name, addr, charsets=None): |
319 assert isinstance(addr, bytes) | 361 assert isinstance(addr, bytes) |
320 name = headencode(ui, name, charsets) | 362 name = headencode(ui, name, charsets) |
321 try: | 363 try: |
330 # too strict? | 372 # too strict? |
331 addr.decode('ascii') | 373 addr.decode('ascii') |
332 except UnicodeDecodeError: | 374 except UnicodeDecodeError: |
333 raise error.Abort(_('invalid local address: %s') % addr) | 375 raise error.Abort(_('invalid local address: %s') % addr) |
334 return pycompat.bytesurl( | 376 return pycompat.bytesurl( |
335 email.utils.formataddr((name, encoding.strfromlocal(addr)))) | 377 email.utils.formataddr((name, encoding.strfromlocal(addr))) |
378 ) | |
379 | |
336 | 380 |
337 def addressencode(ui, address, charsets=None, display=False): | 381 def addressencode(ui, address, charsets=None, display=False): |
338 '''Turns address into RFC-2047 compliant header.''' | 382 '''Turns address into RFC-2047 compliant header.''' |
339 if display or not address: | 383 if display or not address: |
340 return address or '' | 384 return address or '' |
341 name, addr = email.utils.parseaddr(encoding.strfromlocal(address)) | 385 name, addr = email.utils.parseaddr(encoding.strfromlocal(address)) |
342 return _addressencode(ui, name, encoding.strtolocal(addr), charsets) | 386 return _addressencode(ui, name, encoding.strtolocal(addr), charsets) |
343 | 387 |
388 | |
344 def addrlistencode(ui, addrs, charsets=None, display=False): | 389 def addrlistencode(ui, addrs, charsets=None, display=False): |
345 '''Turns a list of addresses into a list of RFC-2047 compliant headers. | 390 '''Turns a list of addresses into a list of RFC-2047 compliant headers. |
346 A single element of input list may contain multiple addresses, but output | 391 A single element of input list may contain multiple addresses, but output |
347 always has one address per item''' | 392 always has one address per item''' |
348 for a in addrs: | 393 for a in addrs: |
349 assert isinstance(a, bytes), (r'%r unexpectedly not a bytestr' % a) | 394 assert isinstance(a, bytes), r'%r unexpectedly not a bytestr' % a |
350 if display: | 395 if display: |
351 return [a.strip() for a in addrs if a.strip()] | 396 return [a.strip() for a in addrs if a.strip()] |
352 | 397 |
353 result = [] | 398 result = [] |
354 for name, addr in email.utils.getaddresses( | 399 for name, addr in email.utils.getaddresses( |
355 [encoding.strfromlocal(a) for a in addrs]): | 400 [encoding.strfromlocal(a) for a in addrs] |
401 ): | |
356 if name or addr: | 402 if name or addr: |
357 r = _addressencode(ui, name, encoding.strtolocal(addr), charsets) | 403 r = _addressencode(ui, name, encoding.strtolocal(addr), charsets) |
358 result.append(r) | 404 result.append(r) |
359 return result | 405 return result |
406 | |
360 | 407 |
361 def mimeencode(ui, s, charsets=None, display=False): | 408 def mimeencode(ui, s, charsets=None, display=False): |
362 '''creates mime text object, encodes it if needed, and sets | 409 '''creates mime text object, encodes it if needed, and sets |
363 charset and transfer-encoding accordingly.''' | 410 charset and transfer-encoding accordingly.''' |
364 cs = 'us-ascii' | 411 cs = 'us-ascii' |
365 if not display: | 412 if not display: |
366 s, cs = _encode(ui, s, charsets) | 413 s, cs = _encode(ui, s, charsets) |
367 return mimetextqp(s, 'plain', cs) | 414 return mimetextqp(s, 'plain', cs) |
368 | 415 |
416 | |
369 if pycompat.ispy3: | 417 if pycompat.ispy3: |
418 | |
370 def parse(fp): | 419 def parse(fp): |
371 ep = email.parser.Parser() | 420 ep = email.parser.Parser() |
372 # disable the "universal newlines" mode, which isn't binary safe. | 421 # disable the "universal newlines" mode, which isn't binary safe. |
373 # I have no idea if ascii/surrogateescape is correct, but that's | 422 # I have no idea if ascii/surrogateescape is correct, but that's |
374 # what the standard Python email parser does. | 423 # what the standard Python email parser does. |
375 fp = io.TextIOWrapper(fp, encoding=r'ascii', | 424 fp = io.TextIOWrapper( |
376 errors=r'surrogateescape', newline=chr(10)) | 425 fp, encoding=r'ascii', errors=r'surrogateescape', newline=chr(10) |
426 ) | |
377 try: | 427 try: |
378 return ep.parse(fp) | 428 return ep.parse(fp) |
379 finally: | 429 finally: |
380 fp.detach() | 430 fp.detach() |
431 | |
432 | |
381 else: | 433 else: |
434 | |
382 def parse(fp): | 435 def parse(fp): |
383 ep = email.parser.Parser() | 436 ep = email.parser.Parser() |
384 return ep.parse(fp) | 437 return ep.parse(fp) |
438 | |
385 | 439 |
386 def headdecode(s): | 440 def headdecode(s): |
387 '''Decodes RFC-2047 header''' | 441 '''Decodes RFC-2047 header''' |
388 uparts = [] | 442 uparts = [] |
389 for part, charset in email.header.decode_header(s): | 443 for part, charset in email.header.decode_header(s): |