annotate mercurial/mail.py @ 48184:8fae2cc6ee86

dispatch: don't change error status if flushing stdio fails If we already have a non-zero exit code, I don't think we should change it to 255 because we fail to flush stdio. This may not matter yet, but it will matter when I make a killed pager result in exit code 250 (it's currently 255). Differential Revision: https://phab.mercurial-scm.org/D11626
author Martin von Zweigbergk <martinvonz@google.com>
date Fri, 08 Oct 2021 13:34:33 -0700
parents f38bf44e077f
children 6000f5b25c9b
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
1 # mail.py - mail sending bits for mercurial
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
2 #
46819
d4ba4d51f85f contributor: change mentions of mpm to olivia
Raphaël Gomès <rgomes@octobus.net>
parents: 46683
diff changeset
3 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
4 #
8225
46293a0c7e9f updated license to be explicit about GPL version 2
Martin Geisler <mg@lazybytes.net>
parents: 7948
diff changeset
5 # This software may be used and distributed according to the terms of the
10263
25e572394f5c Update license to GPLv2+
Matt Mackall <mpm@selenic.com>
parents: 9715
diff changeset
6 # GNU General Public License version 2 or any later version.
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
7
30325
f6369544bf85 mail: do not print(), use ui.debug() instead
Yuya Nishihara <yuya@tcha.org>
parents: 30089
diff changeset
8 from __future__ import absolute_import
25957
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
9
19790
53f16f4aff33 mail: correct import of email module
Augie Fackler <raf@durin42.com>
parents: 19050
diff changeset
10 import email
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
11 import email.charset
43156
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
diff changeset
12 import email.generator
30072
87b8e40eb812 mail: handle renamed email.Header
Pulkit Goyal <7895pulkit@gmail.com>
parents: 29285
diff changeset
13 import email.header
34310
2d0c306a88c2 mail: encode long unicode lines in emails properly (issue5687)
Igor Ippolitov <iippolitov@gmail.com>
parents: 33499
diff changeset
14 import email.message
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
15 import email.parser
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
16 import io
25957
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
17 import os
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
18 import smtplib
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
19 import socket
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
20 import time
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
21
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
22 from .i18n import _
43089
c59eb1560c44 py3: manually import getattr where it is needed
Gregory Szorc <gregory.szorc@gmail.com>
parents: 43085
diff changeset
23 from .pycompat import (
c59eb1560c44 py3: manually import getattr where it is needed
Gregory Szorc <gregory.szorc@gmail.com>
parents: 43085
diff changeset
24 getattr,
c59eb1560c44 py3: manually import getattr where it is needed
Gregory Szorc <gregory.szorc@gmail.com>
parents: 43085
diff changeset
25 open,
c59eb1560c44 py3: manually import getattr where it is needed
Gregory Szorc <gregory.szorc@gmail.com>
parents: 43085
diff changeset
26 )
25957
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
27 from . import (
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
28 encoding,
26587
56b2bcea2529 error: get Abort from 'error' instead of 'util'
Pierre-Yves David <pierre-yves.david@fb.com>
parents: 26098
diff changeset
29 error,
36047
1407c42b302c py3: pass system string to email.message.Message.set_type()
Gregory Szorc <gregory.szorc@gmail.com>
parents: 35151
diff changeset
30 pycompat,
25957
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
31 sslutil,
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
32 util,
ae21d51bdc43 mail: use absolute_import
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25842
diff changeset
33 )
37084
f0b6fbea00cf stringutil: bulk-replace call sites to point to new module
Yuya Nishihara <yuya@tcha.org>
parents: 36120
diff changeset
34 from .utils import (
37120
a8a902d7176e procutil: bulk-replace function calls to point to new module
Yuya Nishihara <yuya@tcha.org>
parents: 37084
diff changeset
35 procutil,
37084
f0b6fbea00cf stringutil: bulk-replace call sites to point to new module
Yuya Nishihara <yuya@tcha.org>
parents: 36120
diff changeset
36 stringutil,
46907
ffd3e823a7e5 urlutil: extract `url` related code from `util` into the new module
Pierre-Yves David <pierre-yves.david@octobus.net>
parents: 46819
diff changeset
37 urlutil,
37084
f0b6fbea00cf stringutil: bulk-replace call sites to point to new module
Yuya Nishihara <yuya@tcha.org>
parents: 36120
diff changeset
38 )
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
39
43773
7b14d649af1b typing: consolidate "if not globals():" trick
Yuya Nishihara <yuya@tcha.org>
parents: 43628
diff changeset
40 if pycompat.TYPE_CHECKING:
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
41 from typing import Any, List, Tuple, Union
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
42
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
43 # keep pyflakes happy
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
44 assert all((Any, List, Tuple, Union))
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
45
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
46
18885
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
47 class STARTTLS(smtplib.SMTP):
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
48 """Derived class to verify the peer certificate for STARTTLS.
18885
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
49
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
50 This class allows to pass any keyword arguments to SSL socket creation.
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
51 """
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
52
29251
31acc78c632a mail: remove use of sslkwargs
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29248
diff changeset
53 def __init__(self, ui, host=None, **kwargs):
18885
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
54 smtplib.SMTP.__init__(self, **kwargs)
29248
e6de6ef3e426 sslutil: remove ui from sslkwargs (API)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29227
diff changeset
55 self._ui = ui
28935
a4c5c23de1d3 mail: retain hostname for sslutil.wrapsocket (issue5203)
timeless <timeless@mozdev.org>
parents: 28341
diff changeset
56 self._host = host
18885
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
57
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
58 def starttls(self, keyfile=None, certfile=None):
43169
3941e7063d03 py3: call SMTP.has_extn() with an str
Denis Laxalde <denis@laxalde.org>
parents: 43156
diff changeset
59 if not self.has_extn("starttls"):
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
60 msg = b"STARTTLS extension not supported by server"
18885
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
61 raise smtplib.SMTPException(msg)
43170
a1801ee97840 py3: call SMTP.docmd() with an str
Denis Laxalde <denis@laxalde.org>
parents: 43169
diff changeset
62 (resp, reply) = self.docmd("STARTTLS")
18885
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
63 if resp == 220:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
64 self.sock = sslutil.wrapsocket(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
65 self.sock,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
66 keyfile,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
67 certfile,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
68 ui=self._ui,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
69 serverhostname=self._host,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
70 )
43171
54b06bec8914 py3: use socket.makefile() instead of dropped smtplib.SSLFakeFile
Denis Laxalde <denis@laxalde.org>
parents: 43170
diff changeset
71 self.file = self.sock.makefile("rb")
18885
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
72 self.helo_resp = None
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
73 self.ehlo_resp = None
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
74 self.esmtp_features = {}
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
75 self.does_esmtp = 0
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
76 return (resp, reply)
cf1304fbc184 smtp: add the class to verify the certificate of the SMTP server for STARTTLS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 17428
diff changeset
77
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
78
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
79 class SMTPS(smtplib.SMTP):
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
80 """Derived class to verify the peer certificate for SMTPS.
18886
14a60a0f7122 smtp: add the class to verify the certificate of the SMTP server for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18885
diff changeset
81
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
82 This class allows to pass any keyword arguments to SSL socket creation.
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
83 """
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
84
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
85 def __init__(self, ui, keyfile=None, certfile=None, host=None, **kwargs):
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
86 self.keyfile = keyfile
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
87 self.certfile = certfile
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
88 smtplib.SMTP.__init__(self, **kwargs)
28935
a4c5c23de1d3 mail: retain hostname for sslutil.wrapsocket (issue5203)
timeless <timeless@mozdev.org>
parents: 28341
diff changeset
89 self._host = host
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
90 self.default_port = smtplib.SMTP_SSL_PORT
29248
e6de6ef3e426 sslutil: remove ui from sslkwargs (API)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29227
diff changeset
91 self._ui = ui
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
92
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
93 def _get_socket(self, host, port, timeout):
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
94 if self.debuglevel > 0:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
95 self._ui.debug(b'connect: %r\n' % ((host, port),))
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
96 new_socket = socket.create_connection((host, port), timeout)
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
97 new_socket = sslutil.wrapsocket(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
98 new_socket,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
99 self.keyfile,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
100 self.certfile,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
101 ui=self._ui,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
102 serverhostname=self._host,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
103 )
43506
9f70512ae2cf cleanup: remove pointless r-prefixes on single-quoted strings
Augie Fackler <augie@google.com>
parents: 43498
diff changeset
104 self.file = new_socket.makefile('rb')
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
105 return new_socket
18886
14a60a0f7122 smtp: add the class to verify the certificate of the SMTP server for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18885
diff changeset
106
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
107
39025
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
108 def _pyhastls():
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
109 # type: () -> bool
39025
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
110 """Returns true iff Python has TLS support, false otherwise."""
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
111 try:
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
112 import ssl
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
113
39025
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
114 getattr(ssl, 'HAS_TLS', False)
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
115 return True
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
116 except ImportError:
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
117 return False
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
118
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
119
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
120 def _smtp(ui):
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
121 '''build an smtp connection and return a function to send mail'''
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
122 local_hostname = ui.config(b'smtp', b'local_hostname')
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
123 tls = ui.config(b'smtp', b'tls')
13201
f05250572467 smtp: fix for server doesn't support starttls extension
Zhigang Wang <zhigang.x.wang@oracle.com>
parents: 12091
diff changeset
124 # backward compatible: when tls = true, we use starttls.
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
125 starttls = tls == b'starttls' or stringutil.parsebool(tls)
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
126 smtps = tls == b'smtps'
39025
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
127 if (starttls or smtps) and not _pyhastls():
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
128 raise error.Abort(_(b"can't use TLS: Python SSL support not installed"))
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
129 mailhost = ui.config(b'smtp', b'host')
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
130 if not mailhost:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
131 raise error.Abort(_(b'smtp.host not configured - cannot send mail'))
18888
19d489404d79 smtp: verify the certificate of the SMTP server for STARTTLS/SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18886
diff changeset
132 if smtps:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
133 ui.note(_(b'(using smtps)\n'))
29251
31acc78c632a mail: remove use of sslkwargs
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29248
diff changeset
134 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
18888
19d489404d79 smtp: verify the certificate of the SMTP server for STARTTLS/SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18886
diff changeset
135 elif starttls:
29251
31acc78c632a mail: remove use of sslkwargs
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29248
diff changeset
136 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
18888
19d489404d79 smtp: verify the certificate of the SMTP server for STARTTLS/SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18886
diff changeset
137 else:
19d489404d79 smtp: verify the certificate of the SMTP server for STARTTLS/SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18886
diff changeset
138 s = smtplib.SMTP(local_hostname=local_hostname)
19050
601c1e226889 smtp: use 465 as default port for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18916
diff changeset
139 if smtps:
601c1e226889 smtp: use 465 as default port for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18916
diff changeset
140 defaultport = 465
601c1e226889 smtp: use 465 as default port for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18916
diff changeset
141 else:
601c1e226889 smtp: use 465 as default port for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18916
diff changeset
142 defaultport = 25
46907
ffd3e823a7e5 urlutil: extract `url` related code from `util` into the new module
Pierre-Yves David <pierre-yves.david@octobus.net>
parents: 46819
diff changeset
143 mailport = urlutil.getport(ui.config(b'smtp', b'port', defaultport))
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
144 ui.note(_(b'sending mail: smtp host %s, port %d\n') % (mailhost, mailport))
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
145 s.connect(host=mailhost, port=mailport)
13201
f05250572467 smtp: fix for server doesn't support starttls extension
Zhigang Wang <zhigang.x.wang@oracle.com>
parents: 12091
diff changeset
146 if starttls:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
147 ui.note(_(b'(using starttls)\n'))
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
148 s.ehlo()
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
149 s.starttls()
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
150 s.ehlo()
29285
63a3749147af mail: unsupport smtp.verifycert (BC)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29251
diff changeset
151 if starttls or smtps:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
152 ui.note(_(b'(verifying remote certificate)\n'))
29285
63a3749147af mail: unsupport smtp.verifycert (BC)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29251
diff changeset
153 sslutil.validatesocket(s.sock)
46991
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
154
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
155 try:
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
156 _smtp_login(ui, s, mailhost, mailport)
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
157 except smtplib.SMTPException as inst:
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
158 raise error.Abort(stringutil.forcebytestr(inst))
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
159
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
160 def send(sender, recipients, msg):
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
161 try:
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
162 return s.sendmail(sender, recipients, msg)
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
163 except smtplib.SMTPRecipientsRefused as inst:
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
164 recipients = [r[1] for r in inst.recipients.values()]
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
165 raise error.Abort(b'\n' + b'\n'.join(recipients))
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
166 except smtplib.SMTPException as inst:
46992
5fa019ceb499 mail: force SMTPException to bytes before wrapping in error.Abort
Matt Harbison <matt_harbison@yahoo.com>
parents: 46991
diff changeset
167 raise error.Abort(stringutil.forcebytestr(inst))
46991
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
168
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
169 return send
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
170
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
171
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
172 def _smtp_login(ui, smtp, mailhost, mailport):
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
173 """A hook for the keyring extension to perform the actual SMTP login.
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
174
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
175 An already connected SMTP object of the proper type is provided, based on
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
176 the current configuration. The host and port to which the connection was
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
177 established are provided for accessibility, since the SMTP object doesn't
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
178 provide an accessor. ``smtplib.SMTPException`` is raised on error.
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
179 """
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
180 username = ui.config(b'smtp', b'username')
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
181 password = ui.config(b'smtp', b'password')
43172
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
182 if username:
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
183 if password:
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
184 password = encoding.strfromlocal(password)
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
185 else:
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
186 password = ui.getpass()
45929
87e7dd8e7734 ui: ensure `getpass()` returns bytes
Matt Harbison <matt_harbison@yahoo.com>
parents: 43825
diff changeset
187 if password is not None:
87e7dd8e7734 ui: ensure `getpass()` returns bytes
Matt Harbison <matt_harbison@yahoo.com>
parents: 43825
diff changeset
188 password = encoding.strfromlocal(password)
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
189 if username and password:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
190 ui.note(_(b'(authenticating to mail server as %s)\n') % username)
43172
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
191 username = encoding.strfromlocal(username)
46991
83c0d144ef8d mail: split out the SMTP login to allow the keyring extension to wrap it
Matt Harbison <matt_harbison@yahoo.com>
parents: 46982
diff changeset
192 smtp.login(username, password)
5947
528c986f0162 Backed out changeset dc6ed2736c81
Bryan O'Sullivan <bos@serpentine.com>
parents: 5866
diff changeset
193
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
194
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
195 def _sendmail(ui, sender, recipients, msg):
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
196 '''send mail using sendmail.'''
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
197 program = ui.config(b'email', b'method')
43096
813aa8cc55d4 patchbomb: use procutil.shellquote() instead of shlex to escape email address
Yuya Nishihara <yuya@tcha.org>
parents: 43089
diff changeset
198
813aa8cc55d4 patchbomb: use procutil.shellquote() instead of shlex to escape email address
Yuya Nishihara <yuya@tcha.org>
parents: 43089
diff changeset
199 def stremail(x):
813aa8cc55d4 patchbomb: use procutil.shellquote() instead of shlex to escape email address
Yuya Nishihara <yuya@tcha.org>
parents: 43089
diff changeset
200 return procutil.shellquote(stringutil.email(encoding.strtolocal(x)))
813aa8cc55d4 patchbomb: use procutil.shellquote() instead of shlex to escape email address
Yuya Nishihara <yuya@tcha.org>
parents: 43089
diff changeset
201
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
202 cmdline = b'%s -f %s %s' % (
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
203 program,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
204 stremail(sender),
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
205 b' '.join(map(stremail, recipients)),
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
206 )
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
207 ui.note(_(b'sending mail: %s\n') % cmdline)
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
208 fp = procutil.popen(cmdline, b'wb')
37458
00e4bd97b095 procutil: always popen() in binary mode
Yuya Nishihara <yuya@tcha.org>
parents: 37120
diff changeset
209 fp.write(util.tonativeeol(msg))
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
210 ret = fp.close()
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
211 if ret:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
212 raise error.Abort(
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
213 b'%s %s'
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
214 % (
43825
8f26dd09aa78 mail: use procutil.shellsplit instead of bytes.split to parse command
Julien Cristau <jcristau@debian.org>
parents: 43773
diff changeset
215 os.path.basename(procutil.shellsplit(program)[0]),
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
216 procutil.explainexit(ret),
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
217 )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
218 )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
219
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
220
15560
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
221 def _mbox(mbox, sender, recipients, msg):
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
222 '''write mails to mbox'''
46997
106402245301 mail: add a TODO about proper mbox locking
Matt Harbison <matt_harbison@yahoo.com>
parents: 46992
diff changeset
223 # TODO: use python mbox library for proper locking
46982
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
224 with open(mbox, b'ab+') as fp:
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
225 # Should be time.asctime(), but Windows prints 2-characters day
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
226 # of month instead of one. Make them print the same thing.
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
227 date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
228 fp.write(
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
229 b'From %s %s\n'
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
230 % (encoding.strtolocal(sender), encoding.strtolocal(date))
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
231 )
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
232 fp.write(msg)
d467bae86b2d mail: use a context manager when writing to mbox
Matt Harbison <matt_harbison@yahoo.com>
parents: 46907
diff changeset
233 fp.write(b'\n\n')
15560
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
234
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
235
15560
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
236 def connect(ui, mbox=None):
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
237 """make a mail connection. return a function to send mail.
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
238 call as sendmail(sender, list-of-recipients, msg)."""
15560
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
239 if mbox:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
240 open(mbox, b'wb').close()
15560
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
241 return lambda s, r, m: _mbox(mbox, s, r, m)
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
242 if ui.config(b'email', b'method') == b'smtp':
5947
528c986f0162 Backed out changeset dc6ed2736c81
Bryan O'Sullivan <bos@serpentine.com>
parents: 5866
diff changeset
243 return _smtp(ui)
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
244 return lambda s, r, m: _sendmail(ui, s, r, m)
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
245
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
246
15561
ca572e94d8e7 notify: add option for writing to mbox
Mads Kiilerich <mads@kiilerich.com>
parents: 15560
diff changeset
247 def sendmail(ui, sender, recipients, msg, mbox=None):
ca572e94d8e7 notify: add option for writing to mbox
Mads Kiilerich <mads@kiilerich.com>
parents: 15560
diff changeset
248 send = connect(ui, mbox=mbox)
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
249 return send(sender, recipients, msg)
4489
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
250
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
251
4489
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
252 def validateconfig(ui):
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
253 '''determine if we have enough config data to try sending email.'''
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
254 method = ui.config(b'email', b'method')
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
255 if method == b'smtp':
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
256 if not ui.config(b'smtp', b'host'):
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
257 raise error.Abort(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
258 _(
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
259 b'smtp specified as email transport, '
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
260 b'but no smtp host configured'
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
261 )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
262 )
4489
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
263 else:
37120
a8a902d7176e procutil: bulk-replace function calls to point to new module
Yuya Nishihara <yuya@tcha.org>
parents: 37084
diff changeset
264 if not procutil.findexe(method):
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
265 raise error.Abort(
43117
8ff1ecfadcd1 cleanup: join string literals that are already on one line
Martin von Zweigbergk <martinvonz@google.com>
parents: 43096
diff changeset
266 _(b'%r specified as email transport, but not in PATH') % method
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
267 )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
268
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
269
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
270 def codec2iana(cs):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
271 # type: (str) -> str
47062
f38bf44e077f black: make codebase compatible with black v21.4b2 and v20.8b1
Kyle Lippincott <spectral@google.com>
parents: 46997
diff changeset
272 ''' '''
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
273 cs = email.charset.Charset(cs).input_charset.lower()
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
274
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
275 # "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1"
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
276 if cs.startswith("iso") and not cs.startswith("iso-"):
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
277 return "iso-" + cs[3:]
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
278 return cs
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
279
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
280
43627
af3e341dbf03 mail: use a native string for "subtype" value
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43626
diff changeset
281 def mimetextpatch(s, subtype='plain', display=False):
af3e341dbf03 mail: use a native string for "subtype" value
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43626
diff changeset
282 # type: (bytes, str, bool) -> email.message.Message
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
283 """Return MIME message suitable for a patch.
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
284 Charset will be detected by first trying to decode as us-ascii, then utf-8,
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
285 and finally the global encodings. If all those fail, fall back to
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
286 ISO-8859-1, an encoding with that allows all byte sequences.
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
287 Transfer encodings will be used if necessary."""
8332
3e544c074459 patchbomb: quoted-printable encode overly long lines
Rocco Rutte <pdmef@gmx.net>
parents: 8312
diff changeset
288
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
289 cs = [
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
290 'us-ascii',
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
291 'utf-8',
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
292 pycompat.sysstr(encoding.encoding),
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
293 pycompat.sysstr(encoding.fallbackencoding),
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
294 ]
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
295 if display:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
296 cs = ['us-ascii']
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
297 for charset in cs:
8332
3e544c074459 patchbomb: quoted-printable encode overly long lines
Rocco Rutte <pdmef@gmx.net>
parents: 8312
diff changeset
298 try:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
299 s.decode(charset)
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
300 return mimetextqp(s, subtype, codec2iana(charset))
8332
3e544c074459 patchbomb: quoted-printable encode overly long lines
Rocco Rutte <pdmef@gmx.net>
parents: 8312
diff changeset
301 except UnicodeDecodeError:
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
302 pass
8332
3e544c074459 patchbomb: quoted-printable encode overly long lines
Rocco Rutte <pdmef@gmx.net>
parents: 8312
diff changeset
303
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
304 return mimetextqp(s, subtype, "iso-8859-1")
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
305
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
306
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
307 def mimetextqp(body, subtype, charset):
43627
af3e341dbf03 mail: use a native string for "subtype" value
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43626
diff changeset
308 # type: (bytes, str, str) -> email.message.Message
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
309 """Return MIME message.
17424
e7cfe3587ea4 fix trivial spelling errors
Mads Kiilerich <mads@kiilerich.com>
parents: 15562
diff changeset
310 Quoted-printable transfer encoding will be used if necessary.
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
311 """
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
312 cs = email.charset.Charset(charset)
34310
2d0c306a88c2 mail: encode long unicode lines in emails properly (issue5687)
Igor Ippolitov <iippolitov@gmail.com>
parents: 33499
diff changeset
313 msg = email.message.Message()
43627
af3e341dbf03 mail: use a native string for "subtype" value
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43626
diff changeset
314 msg.set_type('text/' + subtype)
34310
2d0c306a88c2 mail: encode long unicode lines in emails properly (issue5687)
Igor Ippolitov <iippolitov@gmail.com>
parents: 33499
diff changeset
315
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
316 for line in body.splitlines():
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
317 if len(line) > 950:
34310
2d0c306a88c2 mail: encode long unicode lines in emails properly (issue5687)
Igor Ippolitov <iippolitov@gmail.com>
parents: 33499
diff changeset
318 cs.body_encoding = email.charset.QP
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
319 break
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
320
41405
9b3be572ff0c mail: document behavior of Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 39108
diff changeset
321 # On Python 2, this simply assigns a value. Python 3 inspects
9b3be572ff0c mail: document behavior of Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 39108
diff changeset
322 # body and does different things depending on whether it has
9b3be572ff0c mail: document behavior of Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 39108
diff changeset
323 # encode() or decode() attributes. We can get the old behavior
9b3be572ff0c mail: document behavior of Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 39108
diff changeset
324 # if we pass a str and charset is None and we call set_charset().
9b3be572ff0c mail: document behavior of Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 39108
diff changeset
325 # But we may get into trouble later due to Python attempting to
9b3be572ff0c mail: document behavior of Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 39108
diff changeset
326 # encode/decode using the registered charset (or attempting to
9b3be572ff0c mail: document behavior of Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 39108
diff changeset
327 # use ascii in the absence of a charset).
34310
2d0c306a88c2 mail: encode long unicode lines in emails properly (issue5687)
Igor Ippolitov <iippolitov@gmail.com>
parents: 33499
diff changeset
328 msg.set_payload(body, cs)
2d0c306a88c2 mail: encode long unicode lines in emails properly (issue5687)
Igor Ippolitov <iippolitov@gmail.com>
parents: 33499
diff changeset
329
8332
3e544c074459 patchbomb: quoted-printable encode overly long lines
Rocco Rutte <pdmef@gmx.net>
parents: 8312
diff changeset
330 return msg
7191
d14212218582 mail: mime-encode patches that are utf-8
Christian Ebert <blacktrash@gmx.net>
parents: 7114
diff changeset
331
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
332
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
333 def _charsets(ui):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
334 # type: (Any) -> List[str]
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
335 '''Obtains charsets to send mail parts not containing patches.'''
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
336 charsets = [
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
337 pycompat.sysstr(cs.lower())
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
338 for cs in ui.configlist(b'email', b'charsets')
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
339 ]
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
340 fallbacks = [
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
341 pycompat.sysstr(encoding.fallbackencoding.lower()),
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
342 pycompat.sysstr(encoding.encoding.lower()),
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
343 'utf-8',
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
344 ]
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
345 for cs in fallbacks: # find unique charsets while keeping order
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
346 if cs not in charsets:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
347 charsets.append(cs)
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
348 return [cs for cs in charsets if not cs.endswith('ascii')]
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
349
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
350
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
351 def _encode(ui, s, charsets):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
352 # type: (Any, bytes, List[str]) -> Tuple[bytes, str]
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
353 """Returns (converted) string, charset tuple.
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
354 Finds out best charset by cycling through sendcharsets in descending
7948
de377b1a9a84 move encoding bits from util to encoding
Matt Mackall <mpm@selenic.com>
parents: 7195
diff changeset
355 order. Tries both encoding and fallbackencoding for input. Only as
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
356 last resort send as is in fake ascii.
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
357 Caveat: Do not use for mail parts containing patches!"""
39023
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
358 sendcharsets = charsets or _charsets(ui)
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
359 if not isinstance(s, bytes):
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
360 # We have unicode data, which we need to try and encode to
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
361 # some reasonable-ish encoding. Try the encodings the user
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
362 # wants, and fall back to garbage-in-ascii.
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
363 for ocs in sendcharsets:
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
364 try:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
365 return s.encode(ocs), ocs
39023
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
366 except UnicodeEncodeError:
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
367 pass
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
368 except LookupError:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
369 ui.warn(
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
370 _(b'ignoring invalid sendcharset: %s\n')
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
371 % pycompat.sysbytes(ocs)
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
372 )
39023
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
373 else:
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
374 # Everything failed, ascii-armor what we've got and send it.
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
375 return s.encode('ascii', 'backslashreplace'), 'us-ascii'
39023
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
376 # We have a bytes of unknown encoding. We'll try and guess a valid
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
377 # encoding, falling back to pretending we had ascii even though we
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
378 # know that's wrong.
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
379 try:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
380 s.decode('ascii')
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
381 except UnicodeDecodeError:
7948
de377b1a9a84 move encoding bits from util to encoding
Matt Mackall <mpm@selenic.com>
parents: 7195
diff changeset
382 for ics in (encoding.encoding, encoding.fallbackencoding):
43330
910827a2cb20 py3: decode encoding literal before passing to .decode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43326
diff changeset
383 ics = pycompat.sysstr(ics)
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
384 try:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
385 u = s.decode(ics)
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
386 except UnicodeDecodeError:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
387 continue
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
388 for ocs in sendcharsets:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
389 try:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
390 return u.encode(ocs), ocs
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
391 except UnicodeEncodeError:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
392 pass
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
393 except LookupError:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
394 ui.warn(
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
395 _(b'ignoring invalid sendcharset: %s\n')
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
396 % pycompat.sysbytes(ocs)
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
397 )
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
398 # if ascii, or all conversion attempts fail, send (broken) ascii
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
399 return s, 'us-ascii'
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
400
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
401
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
402 def headencode(ui, s, charsets=None, display=False):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
403 # type: (Any, Union[bytes, str], List[str], bool) -> str
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
404 '''Returns RFC-2047 compliant header from given string.'''
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
405 if not display:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
406 # split into words?
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
407 s, cs = _encode(ui, s, charsets)
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
408 return email.header.Header(s, cs).encode()
43575
67b4439c09b2 mail: let headencode() return a native string
Denis Laxalde <denis@laxalde.org>
parents: 43506
diff changeset
409 return encoding.strfromlocal(s)
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
410
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
411
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
412 def _addressencode(ui, name, addr, charsets=None):
43628
ddb5d097d561 mail: move strtolocal call in _addressencode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43627
diff changeset
413 # type: (Any, str, str, List[str]) -> str
ddb5d097d561 mail: move strtolocal call in _addressencode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43627
diff changeset
414 addr = encoding.strtolocal(addr)
43575
67b4439c09b2 mail: let headencode() return a native string
Denis Laxalde <denis@laxalde.org>
parents: 43506
diff changeset
415 name = headencode(ui, name, charsets)
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
416 try:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
417 acc, dom = addr.split(b'@')
39107
c2327bb3505d mail: call s.decode('ascii') explicitly to see if s is an ascii bytes
Yuya Nishihara <yuya@tcha.org>
parents: 39106
diff changeset
418 acc.decode('ascii')
39108
d2d89d31cbb5 mail: convert encoding.encoding to sysstr
Yuya Nishihara <yuya@tcha.org>
parents: 39107
diff changeset
419 dom = dom.decode(pycompat.sysstr(encoding.encoding)).encode('idna')
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
420 addr = b'%s@%s' % (acc, dom)
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
421 except UnicodeDecodeError:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
422 raise error.Abort(_(b'invalid email address: %s') % addr)
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
423 except ValueError:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
424 try:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
425 # too strict?
39107
c2327bb3505d mail: call s.decode('ascii') explicitly to see if s is an ascii bytes
Yuya Nishihara <yuya@tcha.org>
parents: 39106
diff changeset
426 addr.decode('ascii')
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
427 except UnicodeDecodeError:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
428 raise error.Abort(_(b'invalid local address: %s') % addr)
43576
14b96072797d mail: let addressencode() / addrlistencode() return native strings
Denis Laxalde <denis@laxalde.org>
parents: 43575
diff changeset
429 return email.utils.formataddr((name, encoding.strfromlocal(addr)))
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
430
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
431
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
432 def addressencode(ui, address, charsets=None, display=False):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
433 # type: (Any, bytes, List[str], bool) -> str
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
434 '''Turns address into RFC-2047 compliant header.'''
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
435 if display or not address:
43576
14b96072797d mail: let addressencode() / addrlistencode() return native strings
Denis Laxalde <denis@laxalde.org>
parents: 43575
diff changeset
436 return encoding.strfromlocal(address or b'')
39024
eabdf3c25b8b mail: cope with Py3 unicode antics on email addresses
Augie Fackler <augie@google.com>
parents: 39023
diff changeset
437 name, addr = email.utils.parseaddr(encoding.strfromlocal(address))
43628
ddb5d097d561 mail: move strtolocal call in _addressencode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43627
diff changeset
438 return _addressencode(ui, name, addr, charsets)
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
439
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
440
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
441 def addrlistencode(ui, addrs, charsets=None, display=False):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
442 # type: (Any, List[bytes], List[str], bool) -> List[str]
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
443 """Turns a list of addresses into a list of RFC-2047 compliant headers.
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
444 A single element of input list may contain multiple addresses, but output
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
445 always has one address per item"""
43577
599e25add437 mail: convert addr to str early in addrlistencode()
Denis Laxalde <denis@laxalde.org>
parents: 43576
diff changeset
446 straddrs = []
39024
eabdf3c25b8b mail: cope with Py3 unicode antics on email addresses
Augie Fackler <augie@google.com>
parents: 39023
diff changeset
447 for a in addrs:
43506
9f70512ae2cf cleanup: remove pointless r-prefixes on single-quoted strings
Augie Fackler <augie@google.com>
parents: 43498
diff changeset
448 assert isinstance(a, bytes), '%r unexpectedly not a bytestr' % a
43577
599e25add437 mail: convert addr to str early in addrlistencode()
Denis Laxalde <denis@laxalde.org>
parents: 43576
diff changeset
449 straddrs.append(encoding.strfromlocal(a))
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
450 if display:
43577
599e25add437 mail: convert addr to str early in addrlistencode()
Denis Laxalde <denis@laxalde.org>
parents: 43576
diff changeset
451 return [a.strip() for a in straddrs if a.strip()]
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
452
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
453 result = []
43577
599e25add437 mail: convert addr to str early in addrlistencode()
Denis Laxalde <denis@laxalde.org>
parents: 43576
diff changeset
454 for name, addr in email.utils.getaddresses(straddrs):
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
455 if name or addr:
43628
ddb5d097d561 mail: move strtolocal call in _addressencode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43627
diff changeset
456 r = _addressencode(ui, name, addr, charsets)
39106
ebf54a34b7b7 mail: pass in addr to _addressencode() in bytes
Yuya Nishihara <yuya@tcha.org>
parents: 39105
diff changeset
457 result.append(r)
39105
f68ad9b4a43b mail: remove redundant bytesurl() from addrlistencode()
Yuya Nishihara <yuya@tcha.org>
parents: 39039
diff changeset
458 return result
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
459
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
460
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
461 def mimeencode(ui, s, charsets=None, display=False):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
462 # type: (Any, bytes, List[str], bool) -> email.message.Message
45942
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
463 """creates mime text object, encodes it if needed, and sets
89a2afe31e82 formating: upgrade to black 20.8b1
Augie Fackler <raf@durin42.com>
parents: 45929
diff changeset
464 charset and transfer-encoding accordingly."""
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
465 cs = 'us-ascii'
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
466 if not display:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
467 s, cs = _encode(ui, s, charsets)
43627
af3e341dbf03 mail: use a native string for "subtype" value
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43626
diff changeset
468 return mimetextqp(s, 'plain', cs)
28341
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
469
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
470
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
471 if pycompat.ispy3:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
472
43156
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
diff changeset
473 Generator = email.generator.BytesGenerator
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
diff changeset
474
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
475 def parse(fp):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
476 # type: (Any) -> email.message.Message
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
477 ep = email.parser.Parser()
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
478 # disable the "universal newlines" mode, which isn't binary safe.
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
479 # I have no idea if ascii/surrogateescape is correct, but that's
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
480 # what the standard Python email parser does.
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
481 fp = io.TextIOWrapper(
43506
9f70512ae2cf cleanup: remove pointless r-prefixes on single-quoted strings
Augie Fackler <augie@google.com>
parents: 43498
diff changeset
482 fp, encoding='ascii', errors='surrogateescape', newline=chr(10)
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
483 )
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
484 try:
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
485 return ep.parse(fp)
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
486 finally:
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
487 fp.detach()
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
488
43326
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
489 def parsebytes(data):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
490 # type: (bytes) -> email.message.Message
43326
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
491 ep = email.parser.BytesParser()
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
492 return ep.parsebytes(data)
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
493
43353
fdc3af52305b mail: black wants to add this blank line
Augie Fackler <augie@google.com>
parents: 43330
diff changeset
494
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
495 else:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
496
43156
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
diff changeset
497 Generator = email.generator.Generator
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
diff changeset
498
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
499 def parse(fp):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
500 # type: (Any) -> email.message.Message
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
501 ep = email.parser.Parser()
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
502 return ep.parse(fp)
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
503
43326
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
504 def parsebytes(data):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
505 # type: (str) -> email.message.Message
43326
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
506 ep = email.parser.Parser()
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
507 return ep.parsestr(data)
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
508
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
509
28341
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
510 def headdecode(s):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
511 # type: (Union[email.header.Header, bytes]) -> bytes
28341
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
512 '''Decodes RFC-2047 header'''
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
513 uparts = []
30072
87b8e40eb812 mail: handle renamed email.Header
Pulkit Goyal <7895pulkit@gmail.com>
parents: 29285
diff changeset
514 for part, charset in email.header.decode_header(s):
28341
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
515 if charset is not None:
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
516 try:
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
517 uparts.append(part.decode(charset))
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
518 continue
43324
866bd2cf764b mail: catch LookupError in headdecode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43172
diff changeset
519 except (UnicodeDecodeError, LookupError):
28341
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
520 pass
37469
7edf68862fe3 py3: work around weird handling of bytes/unicode in decode_header()
Yuya Nishihara <yuya@tcha.org>
parents: 37463
diff changeset
521 # On Python 3, decode_header() may return either bytes or unicode
7edf68862fe3 py3: work around weird handling of bytes/unicode in decode_header()
Yuya Nishihara <yuya@tcha.org>
parents: 37463
diff changeset
522 # depending on whether the header has =?<charset>? or not
7edf68862fe3 py3: work around weird handling of bytes/unicode in decode_header()
Yuya Nishihara <yuya@tcha.org>
parents: 37463
diff changeset
523 if isinstance(part, type(u'')):
7edf68862fe3 py3: work around weird handling of bytes/unicode in decode_header()
Yuya Nishihara <yuya@tcha.org>
parents: 37463
diff changeset
524 uparts.append(part)
7edf68862fe3 py3: work around weird handling of bytes/unicode in decode_header()
Yuya Nishihara <yuya@tcha.org>
parents: 37463
diff changeset
525 continue
28341
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
526 try:
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
527 uparts.append(part.decode('UTF-8'))
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
528 continue
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
529 except UnicodeDecodeError:
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
530 pass
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
531 uparts.append(part.decode('ISO-8859-1'))
31447
067add650129 encoding: factor out unicode variants of from/tolocal()
Yuya Nishihara <yuya@tcha.org>
parents: 30325
diff changeset
532 return encoding.unitolocal(u' '.join(uparts))