mercurial/mail.py
author Pierre-Yves David <pierre-yves.david@octobus.net>
Fri, 06 Mar 2020 23:27:28 +0100
branchstable
changeset 44450 7d5455b988ec
parent 43825 8f26dd09aa78
child 45929 87e7dd8e7734
permissions -rw-r--r--
discovery: avoid wrong detection of multiple branch heads (issue6256) This fix the code using obsolescence markers to remove "to be obsoleted" heads during the detection of new head creation from push. The code turned out to not use the branch information at all. This lead changeset from different branch to be detected as new head on unrelated branch. The code fix is actually quite simple. New tests have been added to covers these cases. Differential Revision: https://phab.mercurial-scm.org/D8259
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
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
#
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
     3
# Copyright 2006 Matt Mackall <mpm@selenic.com>
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,
f0b6fbea00cf stringutil: bulk-replace call sites to point to new module
Yuya Nishihara <yuya@tcha.org>
parents: 36120
diff changeset
    37
)
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
    38
43773
7b14d649af1b typing: consolidate "if not globals():" trick
Yuya Nishihara <yuya@tcha.org>
parents: 43628
diff changeset
    39
if pycompat.TYPE_CHECKING:
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
    40
    from typing import Any, List, Tuple, Union
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
    41
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
    42
    # keep pyflakes happy
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
    43
    assert all((Any, List, Tuple, Union))
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
    44
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    45
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
    46
class STARTTLS(smtplib.SMTP):
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
    '''Derived class to verify the peer certificate for STARTTLS.
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
    48
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
    This class allows to pass any keyword arguments to SSL socket creation.
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
    '''
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    51
29251
31acc78c632a mail: remove use of sslkwargs
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29248
diff changeset
    52
    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
    53
        smtplib.SMTP.__init__(self, **kwargs)
29248
e6de6ef3e426 sslutil: remove ui from sslkwargs (API)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29227
diff changeset
    54
        self._ui = ui
28935
a4c5c23de1d3 mail: retain hostname for sslutil.wrapsocket (issue5203)
timeless <timeless@mozdev.org>
parents: 28341
diff changeset
    55
        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
    56
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
    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
    58
        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
    59
            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
    60
            raise smtplib.SMTPException(msg)
43170
a1801ee97840 py3: call SMTP.docmd() with an str
Denis Laxalde <denis@laxalde.org>
parents: 43169
diff changeset
    61
        (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
    62
        if resp == 220:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    63
            self.sock = sslutil.wrapsocket(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    64
                self.sock,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    65
                keyfile,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    66
                certfile,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    67
                ui=self._ui,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    68
                serverhostname=self._host,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    69
            )
43171
54b06bec8914 py3: use socket.makefile() instead of dropped smtplib.SSLFakeFile
Denis Laxalde <denis@laxalde.org>
parents: 43170
diff changeset
    70
            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
    71
            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
    72
            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
    73
            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
    74
            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
    75
        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
    76
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    77
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    78
class SMTPS(smtplib.SMTP):
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    79
    '''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
    80
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    81
    This class allows to pass any keyword arguments to SSL socket creation.
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    82
    '''
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    83
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    84
    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
    85
        self.keyfile = keyfile
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    86
        self.certfile = certfile
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    87
        smtplib.SMTP.__init__(self, **kwargs)
28935
a4c5c23de1d3 mail: retain hostname for sslutil.wrapsocket (issue5203)
timeless <timeless@mozdev.org>
parents: 28341
diff changeset
    88
        self._host = host
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    89
        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
    90
        self._ui = ui
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    91
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    92
    def _get_socket(self, host, port, timeout):
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    93
        if self.debuglevel > 0:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
    94
            self._ui.debug(b'connect: %r\n' % ((host, port),))
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
    95
        new_socket = socket.create_connection((host, port), timeout)
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    96
        new_socket = sslutil.wrapsocket(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    97
            new_socket,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    98
            self.keyfile,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
    99
            self.certfile,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   100
            ui=self._ui,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   101
            serverhostname=self._host,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   102
        )
43506
9f70512ae2cf cleanup: remove pointless r-prefixes on single-quoted strings
Augie Fackler <augie@google.com>
parents: 43498
diff changeset
   103
        self.file = new_socket.makefile('rb')
26673
ab1af5e7d734 mail: drop python 2.5 support
timeless@mozdev.org
parents: 26587
diff changeset
   104
        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
   105
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   106
39025
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   107
def _pyhastls():
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
   108
    # type: () -> bool
39025
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   109
    """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
   110
    try:
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   111
        import ssl
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   112
39025
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   113
        getattr(ssl, 'HAS_TLS', False)
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   114
        return True
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   115
    except ImportError:
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   116
        return False
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   117
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   118
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   119
def _smtp(ui):
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   120
    '''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
   121
    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
   122
    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
   123
    # 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
   124
    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
   125
    smtps = tls == b'smtps'
39025
569d662816de mail: modernize check for Python-with-TLS
Augie Fackler <augie@google.com>
parents: 39024
diff changeset
   126
    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
   127
        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
   128
    mailhost = ui.config(b'smtp', b'host')
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   129
    if not mailhost:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   130
        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
   131
    if smtps:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   132
        ui.note(_(b'(using smtps)\n'))
29251
31acc78c632a mail: remove use of sslkwargs
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29248
diff changeset
   133
        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
   134
    elif starttls:
29251
31acc78c632a mail: remove use of sslkwargs
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29248
diff changeset
   135
        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
   136
    else:
19d489404d79 smtp: verify the certificate of the SMTP server for STARTTLS/SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18886
diff changeset
   137
        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
   138
    if smtps:
601c1e226889 smtp: use 465 as default port for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18916
diff changeset
   139
        defaultport = 465
601c1e226889 smtp: use 465 as default port for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18916
diff changeset
   140
    else:
601c1e226889 smtp: use 465 as default port for SMTPS
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents: 18916
diff changeset
   141
        defaultport = 25
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   142
    mailport = util.getport(ui.config(b'smtp', b'port', defaultport))
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   143
    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
   144
    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
   145
    if starttls:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   146
        ui.note(_(b'(using starttls)\n'))
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   147
        s.ehlo()
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   148
        s.starttls()
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   149
        s.ehlo()
29285
63a3749147af mail: unsupport smtp.verifycert (BC)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29251
diff changeset
   150
    if starttls or smtps:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   151
        ui.note(_(b'(verifying remote certificate)\n'))
29285
63a3749147af mail: unsupport smtp.verifycert (BC)
Gregory Szorc <gregory.szorc@gmail.com>
parents: 29251
diff changeset
   152
        sslutil.validatesocket(s.sock)
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   153
    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
   154
    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
   155
    if username:
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
   156
        if password:
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
   157
            password = encoding.strfromlocal(password)
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
   158
        else:
9145abd8b96d py3: decode username and password before SMTP login
Denis Laxalde <denis@laxalde.org>
parents: 43171
diff changeset
   159
            password = ui.getpass()
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   160
    if username and password:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   161
        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
   162
        username = encoding.strfromlocal(username)
9246
2de7d96593db email: Catch exceptions during send.
David Soria Parra <dsp@php.net>
parents: 8343
diff changeset
   163
        try:
2de7d96593db email: Catch exceptions during send.
David Soria Parra <dsp@php.net>
parents: 8343
diff changeset
   164
            s.login(username, password)
25660
328739ea70c3 global: mass rewrite to use modern exception syntax
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25463
diff changeset
   165
        except smtplib.SMTPException as inst:
26587
56b2bcea2529 error: get Abort from 'error' instead of 'util'
Pierre-Yves David <pierre-yves.david@fb.com>
parents: 26098
diff changeset
   166
            raise error.Abort(inst)
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   167
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   168
    def send(sender, recipients, msg):
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   169
        try:
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   170
            return s.sendmail(sender, recipients, msg)
25660
328739ea70c3 global: mass rewrite to use modern exception syntax
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25463
diff changeset
   171
        except smtplib.SMTPRecipientsRefused as inst:
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   172
            recipients = [r[1] for r in inst.recipients.values()]
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   173
            raise error.Abort(b'\n' + b'\n'.join(recipients))
25660
328739ea70c3 global: mass rewrite to use modern exception syntax
Gregory Szorc <gregory.szorc@gmail.com>
parents: 25463
diff changeset
   174
        except smtplib.SMTPException as inst:
26587
56b2bcea2529 error: get Abort from 'error' instead of 'util'
Pierre-Yves David <pierre-yves.david@fb.com>
parents: 26098
diff changeset
   175
            raise error.Abort(inst)
5947
528c986f0162 Backed out changeset dc6ed2736c81
Bryan O'Sullivan <bos@serpentine.com>
parents: 5866
diff changeset
   176
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   177
    return send
5947
528c986f0162 Backed out changeset dc6ed2736c81
Bryan O'Sullivan <bos@serpentine.com>
parents: 5866
diff changeset
   178
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   179
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   180
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
   181
    '''send mail using sendmail.'''
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   182
    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
   183
813aa8cc55d4 patchbomb: use procutil.shellquote() instead of shlex to escape email address
Yuya Nishihara <yuya@tcha.org>
parents: 43089
diff changeset
   184
    def stremail(x):
813aa8cc55d4 patchbomb: use procutil.shellquote() instead of shlex to escape email address
Yuya Nishihara <yuya@tcha.org>
parents: 43089
diff changeset
   185
        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
   186
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   187
    cmdline = b'%s -f %s %s' % (
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   188
        program,
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   189
        stremail(sender),
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   190
        b' '.join(map(stremail, recipients)),
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   191
    )
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   192
    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
   193
    fp = procutil.popen(cmdline, b'wb')
37458
00e4bd97b095 procutil: always popen() in binary mode
Yuya Nishihara <yuya@tcha.org>
parents: 37120
diff changeset
   194
    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
   195
    ret = fp.close()
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   196
    if ret:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   197
        raise error.Abort(
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   198
            b'%s %s'
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   199
            % (
43825
8f26dd09aa78 mail: use procutil.shellsplit instead of bytes.split to parse command
Julien Cristau <jcristau@debian.org>
parents: 43773
diff changeset
   200
                os.path.basename(procutil.shellsplit(program)[0]),
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   201
                procutil.explainexit(ret),
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   202
            )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   203
        )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   204
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   205
15560
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
   206
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
   207
    '''write mails to mbox'''
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   208
    fp = open(mbox, b'ab+')
15560
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
   209
    # Should be time.asctime(), but Windows prints 2-characters day
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
   210
    # of month instead of one. Make them print the same thing.
43506
9f70512ae2cf cleanup: remove pointless r-prefixes on single-quoted strings
Augie Fackler <augie@google.com>
parents: 43498
diff changeset
   211
    date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   212
    fp.write(
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   213
        b'From %s %s\n'
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   214
        % (encoding.strtolocal(sender), encoding.strtolocal(date))
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   215
    )
15560
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
   216
    fp.write(msg)
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   217
    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
   218
    fp.close()
cc58c228503e mail: mbox handling as a part of mail handling, refactored from patchbomb
Mads Kiilerich <mads@kiilerich.com>
parents: 14965
diff changeset
   219
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
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 connect(ui, mbox=None):
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   222
    '''make a mail connection. return a function to send mail.
2889
20b95aef3fe0 Move ui.sendmail to mail.connect/sendmail
Matt Mackall <mpm@selenic.com>
parents:
diff changeset
   223
    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
   224
    if mbox:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   225
        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
   226
        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
   227
    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
   228
        return _smtp(ui)
5973
ea77f6f77514 patchbomb: undo backout and fix bugs in the earlier patch
Matt Mackall <mpm@selenic.com>
parents: 5947
diff changeset
   229
    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
   230
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   231
15561
ca572e94d8e7 notify: add option for writing to mbox
Mads Kiilerich <mads@kiilerich.com>
parents: 15560
diff changeset
   232
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
   233
    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
   234
    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
   235
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   236
4489
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
   237
def validateconfig(ui):
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
   238
    '''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
   239
    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
   240
    if method == b'smtp':
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   241
        if not ui.config(b'smtp', b'host'):
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   242
            raise error.Abort(
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   243
                _(
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   244
                    b'smtp specified as email transport, '
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   245
                    b'but no smtp host configured'
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   246
                )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   247
            )
4489
a11e13d50645 patchbomb: Validate email config before we start prompting for info.
Bryan O'Sullivan <bos@serpentine.com>
parents: 4096
diff changeset
   248
    else:
37120
a8a902d7176e procutil: bulk-replace function calls to point to new module
Yuya Nishihara <yuya@tcha.org>
parents: 37084
diff changeset
   249
        if not procutil.findexe(method):
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   250
            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
   251
                _(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
   252
            )
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   253
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   254
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   255
def codec2iana(cs):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   256
    # type: (str) -> str
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   257
    ''''''
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   258
    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
   259
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   260
    # "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
   261
    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
   262
        return "iso-" + cs[3:]
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   263
    return cs
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   264
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   265
43627
af3e341dbf03 mail: use a native string for "subtype" value
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43626
diff changeset
   266
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
   267
    # type: (bytes, str, bool) -> email.message.Message
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
   268
    '''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
   269
    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
   270
    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
   271
    ISO-8859-1, an encoding with that allows all byte sequences.
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
   272
    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
   273
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   274
    cs = [
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   275
        'us-ascii',
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   276
        'utf-8',
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   277
        pycompat.sysstr(encoding.encoding),
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   278
        pycompat.sysstr(encoding.fallbackencoding),
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   279
    ]
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   280
    if display:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   281
        cs = ['us-ascii']
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   282
    for charset in cs:
8332
3e544c074459 patchbomb: quoted-printable encode overly long lines
Rocco Rutte <pdmef@gmx.net>
parents: 8312
diff changeset
   283
        try:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   284
            s.decode(charset)
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   285
            return mimetextqp(s, subtype, codec2iana(charset))
8332
3e544c074459 patchbomb: quoted-printable encode overly long lines
Rocco Rutte <pdmef@gmx.net>
parents: 8312
diff changeset
   286
        except UnicodeDecodeError:
30089
040f23ed6963 mail: take --encoding and HGENCODING into account
Gábor Stefanik <gabor.stefanik@nng.com>
parents: 30072
diff changeset
   287
            pass
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
    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
   290
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   291
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
   292
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
   293
    # type: (bytes, str, str) -> email.message.Message
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
   294
    '''Return MIME message.
17424
e7cfe3587ea4 fix trivial spelling errors
Mads Kiilerich <mads@kiilerich.com>
parents: 15562
diff changeset
   295
    Quoted-printable transfer encoding will be used if necessary.
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
   296
    '''
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   297
    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
   298
    msg = email.message.Message()
43627
af3e341dbf03 mail: use a native string for "subtype" value
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43626
diff changeset
   299
    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
   300
15562
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
   301
    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
   302
        if len(line) > 950:
34310
2d0c306a88c2 mail: encode long unicode lines in emails properly (issue5687)
Igor Ippolitov <iippolitov@gmail.com>
parents: 33499
diff changeset
   303
            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
   304
            break
a82b6038ff08 mail: use quoted-printable for mime encoding to avoid too long lines (issue3075)
Mads Kiilerich <mads@kiilerich.com>
parents: 15561
diff changeset
   305
41405
9b3be572ff0c mail: document behavior of Python 3
Gregory Szorc <gregory.szorc@gmail.com>
parents: 39108
diff changeset
   306
    # 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
   307
    # 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
   308
    # 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
   309
    # 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
   310
    # 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
   311
    # 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
   312
    # 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
   313
    msg.set_payload(body, cs)
2d0c306a88c2 mail: encode long unicode lines in emails properly (issue5687)
Igor Ippolitov <iippolitov@gmail.com>
parents: 33499
diff changeset
   314
8332
3e544c074459 patchbomb: quoted-printable encode overly long lines
Rocco Rutte <pdmef@gmx.net>
parents: 8312
diff changeset
   315
    return msg
7191
d14212218582 mail: mime-encode patches that are utf-8
Christian Ebert <blacktrash@gmx.net>
parents: 7114
diff changeset
   316
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   317
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   318
def _charsets(ui):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   319
    # type: (Any) -> List[str]
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   320
    '''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
   321
    charsets = [
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   322
        pycompat.sysstr(cs.lower())
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   323
        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
   324
    ]
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   325
    fallbacks = [
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   326
        pycompat.sysstr(encoding.fallbackencoding.lower()),
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   327
        pycompat.sysstr(encoding.encoding.lower()),
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   328
        'utf-8',
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   329
    ]
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   330
    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
   331
        if cs not in charsets:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   332
            charsets.append(cs)
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   333
    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
   334
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   335
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   336
def _encode(ui, s, charsets):
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   337
    # type: (Any, bytes, List[str]) -> Tuple[bytes, str]
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   338
    '''Returns (converted) string, charset tuple.
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   339
    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
   340
    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
   341
    last resort send as is in fake ascii.
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   342
    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
   343
    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
   344
    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
   345
        # 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
   346
        # 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
   347
        # 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
   348
        for ocs in sendcharsets:
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
   349
            try:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   350
                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
   351
            except UnicodeEncodeError:
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
   352
                pass
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
   353
            except LookupError:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   354
                ui.warn(
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   355
                    _(b'ignoring invalid sendcharset: %s\n')
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   356
                    % pycompat.sysbytes(ocs)
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   357
                )
39023
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
   358
        else:
858fe9625dab mail: fix _encode to be more correct on Python 3
Augie Fackler <augie@google.com>
parents: 38332
diff changeset
   359
            # 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
   360
            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
   361
    # 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
   362
    # 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
   363
    # know that's wrong.
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   364
    try:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   365
        s.decode('ascii')
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   366
    except UnicodeDecodeError:
7948
de377b1a9a84 move encoding bits from util to encoding
Matt Mackall <mpm@selenic.com>
parents: 7195
diff changeset
   367
        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
   368
            ics = pycompat.sysstr(ics)
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   369
            try:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   370
                u = s.decode(ics)
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   371
            except UnicodeDecodeError:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   372
                continue
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   373
            for ocs in sendcharsets:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   374
                try:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   375
                    return u.encode(ocs), ocs
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   376
                except UnicodeEncodeError:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   377
                    pass
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   378
                except LookupError:
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   379
                    ui.warn(
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   380
                        _(b'ignoring invalid sendcharset: %s\n')
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   381
                        % pycompat.sysbytes(ocs)
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   382
                    )
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   383
    # 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
   384
    return s, 'us-ascii'
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   385
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   386
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   387
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
   388
    # 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
   389
    '''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
   390
    if not display:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   391
        # split into words?
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   392
        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
   393
        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
   394
    return encoding.strfromlocal(s)
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   395
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   396
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
   397
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
   398
    # type: (Any, str, str, List[str]) -> str
ddb5d097d561 mail: move strtolocal call in _addressencode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43627
diff changeset
   399
    addr = encoding.strtolocal(addr)
43575
67b4439c09b2 mail: let headencode() return a native string
Denis Laxalde <denis@laxalde.org>
parents: 43506
diff changeset
   400
    name = headencode(ui, name, charsets)
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   401
    try:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   402
        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
   403
        acc.decode('ascii')
39108
d2d89d31cbb5 mail: convert encoding.encoding to sysstr
Yuya Nishihara <yuya@tcha.org>
parents: 39107
diff changeset
   404
        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
   405
        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
   406
    except UnicodeDecodeError:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   407
        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
   408
    except ValueError:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   409
        try:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   410
            # 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
   411
            addr.decode('ascii')
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   412
        except UnicodeDecodeError:
43077
687b865b95ad formatting: byteify all mercurial/ and hgext/ string literals
Augie Fackler <augie@google.com>
parents: 43076
diff changeset
   413
            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
   414
    return email.utils.formataddr((name, encoding.strfromlocal(addr)))
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   415
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   416
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
   417
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
   418
    # 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
   419
    '''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
   420
    if display or not address:
43576
14b96072797d mail: let addressencode() / addrlistencode() return native strings
Denis Laxalde <denis@laxalde.org>
parents: 43575
diff changeset
   421
        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
   422
    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
   423
    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
   424
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   425
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
   426
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
   427
    # type: (Any, List[bytes], List[str], bool) -> List[str]
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
   428
    '''Turns a list of addresses into a list of RFC-2047 compliant headers.
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
   429
    A single element of input list may contain multiple addresses, but output
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
   430
    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
   431
    straddrs = []
39024
eabdf3c25b8b mail: cope with Py3 unicode antics on email addresses
Augie Fackler <augie@google.com>
parents: 39023
diff changeset
   432
    for a in addrs:
43506
9f70512ae2cf cleanup: remove pointless r-prefixes on single-quoted strings
Augie Fackler <augie@google.com>
parents: 43498
diff changeset
   433
        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
   434
        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
   435
    if display:
43577
599e25add437 mail: convert addr to str early in addrlistencode()
Denis Laxalde <denis@laxalde.org>
parents: 43576
diff changeset
   436
        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
   437
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
   438
    result = []
43577
599e25add437 mail: convert addr to str early in addrlistencode()
Denis Laxalde <denis@laxalde.org>
parents: 43576
diff changeset
   439
    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
   440
        if name or addr:
43628
ddb5d097d561 mail: move strtolocal call in _addressencode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43627
diff changeset
   441
            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
   442
            result.append(r)
39105
f68ad9b4a43b mail: remove redundant bytesurl() from addrlistencode()
Yuya Nishihara <yuya@tcha.org>
parents: 39039
diff changeset
   443
    return result
9948
e5b44a7986d0 mail: add parseaddrlist() function for parsing many addresses at once
Marti Raudsepp <marti@juffo.org>
parents: 9715
diff changeset
   444
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   445
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   446
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
   447
    # type: (Any, bytes, List[str], bool) -> email.message.Message
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   448
    '''creates mime text object, encodes it if needed, and sets
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   449
    charset and transfer-encoding accordingly.'''
43626
bdb0ddab7bb3 mail: let all charset values be native strings
Denis Laxalde <denis@laxalde.org>
parents: 43625
diff changeset
   450
    cs = 'us-ascii'
7114
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   451
    if not display:
30e49d54c537 mail: add methods to handle non-ascii chars
Christian Ebert <blacktrash@gmx.net>
parents: 6548
diff changeset
   452
        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
   453
    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
   454
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   455
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
   456
if pycompat.ispy3:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   457
43156
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
diff changeset
   458
    Generator = email.generator.BytesGenerator
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
diff changeset
   459
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
   460
    def parse(fp):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
   461
        # 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
   462
        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
   463
        # 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
   464
        # 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
   465
        # what the standard Python email parser does.
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   466
        fp = io.TextIOWrapper(
43506
9f70512ae2cf cleanup: remove pointless r-prefixes on single-quoted strings
Augie Fackler <augie@google.com>
parents: 43498
diff changeset
   467
            fp, encoding='ascii', errors='surrogateescape', newline=chr(10)
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   468
        )
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
   469
        try:
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
   470
            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
   471
        finally:
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
   472
            fp.detach()
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   473
43326
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
   474
    def parsebytes(data):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
   475
        # type: (bytes) -> email.message.Message
43326
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
   476
        ep = email.parser.BytesParser()
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
   477
        return ep.parsebytes(data)
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   478
43353
fdc3af52305b mail: black wants to add this blank line
Augie Fackler <augie@google.com>
parents: 43330
diff changeset
   479
38332
7b12a2d2eedc py3: ditch email.parser.BytesParser which appears to be plain crap
Yuya Nishihara <yuya@tcha.org>
parents: 37469
diff changeset
   480
else:
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   481
43156
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
diff changeset
   482
    Generator = email.generator.Generator
0e6a7ce81dde py3: use email.generator.BytesGenerator in patch.split()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43117
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
    def parse(fp):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
   485
        # 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
   486
        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
   487
        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
   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: (str) -> 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.Parser()
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
   492
        return ep.parsestr(data)
ef81de93143e py3: use a BytesParser in notify extension
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43325
diff changeset
   493
43076
2372284d9457 formatting: blacken the codebase
Augie Fackler <augie@google.com>
parents: 43019
diff changeset
   494
28341
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
   495
def headdecode(s):
43625
8d9e2c2b6058 mail: add type hints for pytype
Denis Laxalde <denis@laxalde.org>
parents: 43624
diff changeset
   496
    # 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
   497
    '''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
   498
    uparts = []
30072
87b8e40eb812 mail: handle renamed email.Header
Pulkit Goyal <7895pulkit@gmail.com>
parents: 29285
diff changeset
   499
    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
   500
        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
   501
            try:
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
   502
                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
   503
                continue
43324
866bd2cf764b mail: catch LookupError in headdecode()
Denis Laxalde <denis.laxalde@logilab.fr>
parents: 43172
diff changeset
   504
            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
   505
                pass
37469
7edf68862fe3 py3: work around weird handling of bytes/unicode in decode_header()
Yuya Nishihara <yuya@tcha.org>
parents: 37463
diff changeset
   506
        # 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
   507
        # 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
   508
        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
   509
            uparts.append(part)
7edf68862fe3 py3: work around weird handling of bytes/unicode in decode_header()
Yuya Nishihara <yuya@tcha.org>
parents: 37463
diff changeset
   510
            continue
28341
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
   511
        try:
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
   512
            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
   513
            continue
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
   514
        except UnicodeDecodeError:
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
   515
            pass
8286f551b7ee patch: when importing from email, RFC2047-decode From/Subject headers
Julien Cristau <julien.cristau@logilab.fr>
parents: 27619
diff changeset
   516
        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
   517
    return encoding.unitolocal(u' '.join(uparts))