17db96d56Sopenharmony_ci# Copyright (C) 2001-2007 Python Software Foundation
27db96d56Sopenharmony_ci# Author: Barry Warsaw
37db96d56Sopenharmony_ci# Contact: email-sig@python.org
47db96d56Sopenharmony_ci
57db96d56Sopenharmony_ci"""Basic message object for the email package object model."""
67db96d56Sopenharmony_ci
77db96d56Sopenharmony_ci__all__ = ['Message', 'EmailMessage']
87db96d56Sopenharmony_ci
97db96d56Sopenharmony_ciimport binascii
107db96d56Sopenharmony_ciimport re
117db96d56Sopenharmony_ciimport quopri
127db96d56Sopenharmony_cifrom io import BytesIO, StringIO
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ci# Intrapackage imports
157db96d56Sopenharmony_cifrom email import utils
167db96d56Sopenharmony_cifrom email import errors
177db96d56Sopenharmony_cifrom email._policybase import Policy, compat32
187db96d56Sopenharmony_cifrom email import charset as _charset
197db96d56Sopenharmony_cifrom email._encoded_words import decode_b
207db96d56Sopenharmony_ciCharset = _charset.Charset
217db96d56Sopenharmony_ci
227db96d56Sopenharmony_ciSEMISPACE = '; '
237db96d56Sopenharmony_ci
247db96d56Sopenharmony_ci# Regular expression that matches `special' characters in parameters, the
257db96d56Sopenharmony_ci# existence of which force quoting of the parameter value.
267db96d56Sopenharmony_citspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
277db96d56Sopenharmony_ci
287db96d56Sopenharmony_ci
297db96d56Sopenharmony_cidef _splitparam(param):
307db96d56Sopenharmony_ci    # Split header parameters.  BAW: this may be too simple.  It isn't
317db96d56Sopenharmony_ci    # strictly RFC 2045 (section 5.1) compliant, but it catches most headers
327db96d56Sopenharmony_ci    # found in the wild.  We may eventually need a full fledged parser.
337db96d56Sopenharmony_ci    # RDM: we might have a Header here; for now just stringify it.
347db96d56Sopenharmony_ci    a, sep, b = str(param).partition(';')
357db96d56Sopenharmony_ci    if not sep:
367db96d56Sopenharmony_ci        return a.strip(), None
377db96d56Sopenharmony_ci    return a.strip(), b.strip()
387db96d56Sopenharmony_ci
397db96d56Sopenharmony_cidef _formatparam(param, value=None, quote=True):
407db96d56Sopenharmony_ci    """Convenience function to format and return a key=value pair.
417db96d56Sopenharmony_ci
427db96d56Sopenharmony_ci    This will quote the value if needed or if quote is true.  If value is a
437db96d56Sopenharmony_ci    three tuple (charset, language, value), it will be encoded according
447db96d56Sopenharmony_ci    to RFC2231 rules.  If it contains non-ascii characters it will likewise
457db96d56Sopenharmony_ci    be encoded according to RFC2231 rules, using the utf-8 charset and
467db96d56Sopenharmony_ci    a null language.
477db96d56Sopenharmony_ci    """
487db96d56Sopenharmony_ci    if value is not None and len(value) > 0:
497db96d56Sopenharmony_ci        # A tuple is used for RFC 2231 encoded parameter values where items
507db96d56Sopenharmony_ci        # are (charset, language, value).  charset is a string, not a Charset
517db96d56Sopenharmony_ci        # instance.  RFC 2231 encoded values are never quoted, per RFC.
527db96d56Sopenharmony_ci        if isinstance(value, tuple):
537db96d56Sopenharmony_ci            # Encode as per RFC 2231
547db96d56Sopenharmony_ci            param += '*'
557db96d56Sopenharmony_ci            value = utils.encode_rfc2231(value[2], value[0], value[1])
567db96d56Sopenharmony_ci            return '%s=%s' % (param, value)
577db96d56Sopenharmony_ci        else:
587db96d56Sopenharmony_ci            try:
597db96d56Sopenharmony_ci                value.encode('ascii')
607db96d56Sopenharmony_ci            except UnicodeEncodeError:
617db96d56Sopenharmony_ci                param += '*'
627db96d56Sopenharmony_ci                value = utils.encode_rfc2231(value, 'utf-8', '')
637db96d56Sopenharmony_ci                return '%s=%s' % (param, value)
647db96d56Sopenharmony_ci        # BAW: Please check this.  I think that if quote is set it should
657db96d56Sopenharmony_ci        # force quoting even if not necessary.
667db96d56Sopenharmony_ci        if quote or tspecials.search(value):
677db96d56Sopenharmony_ci            return '%s="%s"' % (param, utils.quote(value))
687db96d56Sopenharmony_ci        else:
697db96d56Sopenharmony_ci            return '%s=%s' % (param, value)
707db96d56Sopenharmony_ci    else:
717db96d56Sopenharmony_ci        return param
727db96d56Sopenharmony_ci
737db96d56Sopenharmony_cidef _parseparam(s):
747db96d56Sopenharmony_ci    # RDM This might be a Header, so for now stringify it.
757db96d56Sopenharmony_ci    s = ';' + str(s)
767db96d56Sopenharmony_ci    plist = []
777db96d56Sopenharmony_ci    while s[:1] == ';':
787db96d56Sopenharmony_ci        s = s[1:]
797db96d56Sopenharmony_ci        end = s.find(';')
807db96d56Sopenharmony_ci        while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
817db96d56Sopenharmony_ci            end = s.find(';', end + 1)
827db96d56Sopenharmony_ci        if end < 0:
837db96d56Sopenharmony_ci            end = len(s)
847db96d56Sopenharmony_ci        f = s[:end]
857db96d56Sopenharmony_ci        if '=' in f:
867db96d56Sopenharmony_ci            i = f.index('=')
877db96d56Sopenharmony_ci            f = f[:i].strip().lower() + '=' + f[i+1:].strip()
887db96d56Sopenharmony_ci        plist.append(f.strip())
897db96d56Sopenharmony_ci        s = s[end:]
907db96d56Sopenharmony_ci    return plist
917db96d56Sopenharmony_ci
927db96d56Sopenharmony_ci
937db96d56Sopenharmony_cidef _unquotevalue(value):
947db96d56Sopenharmony_ci    # This is different than utils.collapse_rfc2231_value() because it doesn't
957db96d56Sopenharmony_ci    # try to convert the value to a unicode.  Message.get_param() and
967db96d56Sopenharmony_ci    # Message.get_params() are both currently defined to return the tuple in
977db96d56Sopenharmony_ci    # the face of RFC 2231 parameters.
987db96d56Sopenharmony_ci    if isinstance(value, tuple):
997db96d56Sopenharmony_ci        return value[0], value[1], utils.unquote(value[2])
1007db96d56Sopenharmony_ci    else:
1017db96d56Sopenharmony_ci        return utils.unquote(value)
1027db96d56Sopenharmony_ci
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_cidef _decode_uu(encoded):
1057db96d56Sopenharmony_ci    """Decode uuencoded data."""
1067db96d56Sopenharmony_ci    decoded_lines = []
1077db96d56Sopenharmony_ci    encoded_lines_iter = iter(encoded.splitlines())
1087db96d56Sopenharmony_ci    for line in encoded_lines_iter:
1097db96d56Sopenharmony_ci        if line.startswith(b"begin "):
1107db96d56Sopenharmony_ci            mode, _, path = line.removeprefix(b"begin ").partition(b" ")
1117db96d56Sopenharmony_ci            try:
1127db96d56Sopenharmony_ci                int(mode, base=8)
1137db96d56Sopenharmony_ci            except ValueError:
1147db96d56Sopenharmony_ci                continue
1157db96d56Sopenharmony_ci            else:
1167db96d56Sopenharmony_ci                break
1177db96d56Sopenharmony_ci    else:
1187db96d56Sopenharmony_ci        raise ValueError("`begin` line not found")
1197db96d56Sopenharmony_ci    for line in encoded_lines_iter:
1207db96d56Sopenharmony_ci        if not line:
1217db96d56Sopenharmony_ci            raise ValueError("Truncated input")
1227db96d56Sopenharmony_ci        elif line.strip(b' \t\r\n\f') == b'end':
1237db96d56Sopenharmony_ci            break
1247db96d56Sopenharmony_ci        try:
1257db96d56Sopenharmony_ci            decoded_line = binascii.a2b_uu(line)
1267db96d56Sopenharmony_ci        except binascii.Error:
1277db96d56Sopenharmony_ci            # Workaround for broken uuencoders by /Fredrik Lundh
1287db96d56Sopenharmony_ci            nbytes = (((line[0]-32) & 63) * 4 + 5) // 3
1297db96d56Sopenharmony_ci            decoded_line = binascii.a2b_uu(line[:nbytes])
1307db96d56Sopenharmony_ci        decoded_lines.append(decoded_line)
1317db96d56Sopenharmony_ci
1327db96d56Sopenharmony_ci    return b''.join(decoded_lines)
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_ci
1357db96d56Sopenharmony_ciclass Message:
1367db96d56Sopenharmony_ci    """Basic message object.
1377db96d56Sopenharmony_ci
1387db96d56Sopenharmony_ci    A message object is defined as something that has a bunch of RFC 2822
1397db96d56Sopenharmony_ci    headers and a payload.  It may optionally have an envelope header
1407db96d56Sopenharmony_ci    (a.k.a. Unix-From or From_ header).  If the message is a container (i.e. a
1417db96d56Sopenharmony_ci    multipart or a message/rfc822), then the payload is a list of Message
1427db96d56Sopenharmony_ci    objects, otherwise it is a string.
1437db96d56Sopenharmony_ci
1447db96d56Sopenharmony_ci    Message objects implement part of the `mapping' interface, which assumes
1457db96d56Sopenharmony_ci    there is exactly one occurrence of the header per message.  Some headers
1467db96d56Sopenharmony_ci    do in fact appear multiple times (e.g. Received) and for those headers,
1477db96d56Sopenharmony_ci    you must use the explicit API to set or get all the headers.  Not all of
1487db96d56Sopenharmony_ci    the mapping methods are implemented.
1497db96d56Sopenharmony_ci    """
1507db96d56Sopenharmony_ci    def __init__(self, policy=compat32):
1517db96d56Sopenharmony_ci        self.policy = policy
1527db96d56Sopenharmony_ci        self._headers = []
1537db96d56Sopenharmony_ci        self._unixfrom = None
1547db96d56Sopenharmony_ci        self._payload = None
1557db96d56Sopenharmony_ci        self._charset = None
1567db96d56Sopenharmony_ci        # Defaults for multipart messages
1577db96d56Sopenharmony_ci        self.preamble = self.epilogue = None
1587db96d56Sopenharmony_ci        self.defects = []
1597db96d56Sopenharmony_ci        # Default content type
1607db96d56Sopenharmony_ci        self._default_type = 'text/plain'
1617db96d56Sopenharmony_ci
1627db96d56Sopenharmony_ci    def __str__(self):
1637db96d56Sopenharmony_ci        """Return the entire formatted message as a string.
1647db96d56Sopenharmony_ci        """
1657db96d56Sopenharmony_ci        return self.as_string()
1667db96d56Sopenharmony_ci
1677db96d56Sopenharmony_ci    def as_string(self, unixfrom=False, maxheaderlen=0, policy=None):
1687db96d56Sopenharmony_ci        """Return the entire formatted message as a string.
1697db96d56Sopenharmony_ci
1707db96d56Sopenharmony_ci        Optional 'unixfrom', when true, means include the Unix From_ envelope
1717db96d56Sopenharmony_ci        header.  For backward compatibility reasons, if maxheaderlen is
1727db96d56Sopenharmony_ci        not specified it defaults to 0, so you must override it explicitly
1737db96d56Sopenharmony_ci        if you want a different maxheaderlen.  'policy' is passed to the
1747db96d56Sopenharmony_ci        Generator instance used to serialize the message; if it is not
1757db96d56Sopenharmony_ci        specified the policy associated with the message instance is used.
1767db96d56Sopenharmony_ci
1777db96d56Sopenharmony_ci        If the message object contains binary data that is not encoded
1787db96d56Sopenharmony_ci        according to RFC standards, the non-compliant data will be replaced by
1797db96d56Sopenharmony_ci        unicode "unknown character" code points.
1807db96d56Sopenharmony_ci        """
1817db96d56Sopenharmony_ci        from email.generator import Generator
1827db96d56Sopenharmony_ci        policy = self.policy if policy is None else policy
1837db96d56Sopenharmony_ci        fp = StringIO()
1847db96d56Sopenharmony_ci        g = Generator(fp,
1857db96d56Sopenharmony_ci                      mangle_from_=False,
1867db96d56Sopenharmony_ci                      maxheaderlen=maxheaderlen,
1877db96d56Sopenharmony_ci                      policy=policy)
1887db96d56Sopenharmony_ci        g.flatten(self, unixfrom=unixfrom)
1897db96d56Sopenharmony_ci        return fp.getvalue()
1907db96d56Sopenharmony_ci
1917db96d56Sopenharmony_ci    def __bytes__(self):
1927db96d56Sopenharmony_ci        """Return the entire formatted message as a bytes object.
1937db96d56Sopenharmony_ci        """
1947db96d56Sopenharmony_ci        return self.as_bytes()
1957db96d56Sopenharmony_ci
1967db96d56Sopenharmony_ci    def as_bytes(self, unixfrom=False, policy=None):
1977db96d56Sopenharmony_ci        """Return the entire formatted message as a bytes object.
1987db96d56Sopenharmony_ci
1997db96d56Sopenharmony_ci        Optional 'unixfrom', when true, means include the Unix From_ envelope
2007db96d56Sopenharmony_ci        header.  'policy' is passed to the BytesGenerator instance used to
2017db96d56Sopenharmony_ci        serialize the message; if not specified the policy associated with
2027db96d56Sopenharmony_ci        the message instance is used.
2037db96d56Sopenharmony_ci        """
2047db96d56Sopenharmony_ci        from email.generator import BytesGenerator
2057db96d56Sopenharmony_ci        policy = self.policy if policy is None else policy
2067db96d56Sopenharmony_ci        fp = BytesIO()
2077db96d56Sopenharmony_ci        g = BytesGenerator(fp, mangle_from_=False, policy=policy)
2087db96d56Sopenharmony_ci        g.flatten(self, unixfrom=unixfrom)
2097db96d56Sopenharmony_ci        return fp.getvalue()
2107db96d56Sopenharmony_ci
2117db96d56Sopenharmony_ci    def is_multipart(self):
2127db96d56Sopenharmony_ci        """Return True if the message consists of multiple parts."""
2137db96d56Sopenharmony_ci        return isinstance(self._payload, list)
2147db96d56Sopenharmony_ci
2157db96d56Sopenharmony_ci    #
2167db96d56Sopenharmony_ci    # Unix From_ line
2177db96d56Sopenharmony_ci    #
2187db96d56Sopenharmony_ci    def set_unixfrom(self, unixfrom):
2197db96d56Sopenharmony_ci        self._unixfrom = unixfrom
2207db96d56Sopenharmony_ci
2217db96d56Sopenharmony_ci    def get_unixfrom(self):
2227db96d56Sopenharmony_ci        return self._unixfrom
2237db96d56Sopenharmony_ci
2247db96d56Sopenharmony_ci    #
2257db96d56Sopenharmony_ci    # Payload manipulation.
2267db96d56Sopenharmony_ci    #
2277db96d56Sopenharmony_ci    def attach(self, payload):
2287db96d56Sopenharmony_ci        """Add the given payload to the current payload.
2297db96d56Sopenharmony_ci
2307db96d56Sopenharmony_ci        The current payload will always be a list of objects after this method
2317db96d56Sopenharmony_ci        is called.  If you want to set the payload to a scalar object, use
2327db96d56Sopenharmony_ci        set_payload() instead.
2337db96d56Sopenharmony_ci        """
2347db96d56Sopenharmony_ci        if self._payload is None:
2357db96d56Sopenharmony_ci            self._payload = [payload]
2367db96d56Sopenharmony_ci        else:
2377db96d56Sopenharmony_ci            try:
2387db96d56Sopenharmony_ci                self._payload.append(payload)
2397db96d56Sopenharmony_ci            except AttributeError:
2407db96d56Sopenharmony_ci                raise TypeError("Attach is not valid on a message with a"
2417db96d56Sopenharmony_ci                                " non-multipart payload")
2427db96d56Sopenharmony_ci
2437db96d56Sopenharmony_ci    def get_payload(self, i=None, decode=False):
2447db96d56Sopenharmony_ci        """Return a reference to the payload.
2457db96d56Sopenharmony_ci
2467db96d56Sopenharmony_ci        The payload will either be a list object or a string.  If you mutate
2477db96d56Sopenharmony_ci        the list object, you modify the message's payload in place.  Optional
2487db96d56Sopenharmony_ci        i returns that index into the payload.
2497db96d56Sopenharmony_ci
2507db96d56Sopenharmony_ci        Optional decode is a flag indicating whether the payload should be
2517db96d56Sopenharmony_ci        decoded or not, according to the Content-Transfer-Encoding header
2527db96d56Sopenharmony_ci        (default is False).
2537db96d56Sopenharmony_ci
2547db96d56Sopenharmony_ci        When True and the message is not a multipart, the payload will be
2557db96d56Sopenharmony_ci        decoded if this header's value is `quoted-printable' or `base64'.  If
2567db96d56Sopenharmony_ci        some other encoding is used, or the header is missing, or if the
2577db96d56Sopenharmony_ci        payload has bogus data (i.e. bogus base64 or uuencoded data), the
2587db96d56Sopenharmony_ci        payload is returned as-is.
2597db96d56Sopenharmony_ci
2607db96d56Sopenharmony_ci        If the message is a multipart and the decode flag is True, then None
2617db96d56Sopenharmony_ci        is returned.
2627db96d56Sopenharmony_ci        """
2637db96d56Sopenharmony_ci        # Here is the logic table for this code, based on the email5.0.0 code:
2647db96d56Sopenharmony_ci        #   i     decode  is_multipart  result
2657db96d56Sopenharmony_ci        # ------  ------  ------------  ------------------------------
2667db96d56Sopenharmony_ci        #  None   True    True          None
2677db96d56Sopenharmony_ci        #   i     True    True          None
2687db96d56Sopenharmony_ci        #  None   False   True          _payload (a list)
2697db96d56Sopenharmony_ci        #   i     False   True          _payload element i (a Message)
2707db96d56Sopenharmony_ci        #   i     False   False         error (not a list)
2717db96d56Sopenharmony_ci        #   i     True    False         error (not a list)
2727db96d56Sopenharmony_ci        #  None   False   False         _payload
2737db96d56Sopenharmony_ci        #  None   True    False         _payload decoded (bytes)
2747db96d56Sopenharmony_ci        # Note that Barry planned to factor out the 'decode' case, but that
2757db96d56Sopenharmony_ci        # isn't so easy now that we handle the 8 bit data, which needs to be
2767db96d56Sopenharmony_ci        # converted in both the decode and non-decode path.
2777db96d56Sopenharmony_ci        if self.is_multipart():
2787db96d56Sopenharmony_ci            if decode:
2797db96d56Sopenharmony_ci                return None
2807db96d56Sopenharmony_ci            if i is None:
2817db96d56Sopenharmony_ci                return self._payload
2827db96d56Sopenharmony_ci            else:
2837db96d56Sopenharmony_ci                return self._payload[i]
2847db96d56Sopenharmony_ci        # For backward compatibility, Use isinstance and this error message
2857db96d56Sopenharmony_ci        # instead of the more logical is_multipart test.
2867db96d56Sopenharmony_ci        if i is not None and not isinstance(self._payload, list):
2877db96d56Sopenharmony_ci            raise TypeError('Expected list, got %s' % type(self._payload))
2887db96d56Sopenharmony_ci        payload = self._payload
2897db96d56Sopenharmony_ci        # cte might be a Header, so for now stringify it.
2907db96d56Sopenharmony_ci        cte = str(self.get('content-transfer-encoding', '')).lower()
2917db96d56Sopenharmony_ci        # payload may be bytes here.
2927db96d56Sopenharmony_ci        if isinstance(payload, str):
2937db96d56Sopenharmony_ci            if utils._has_surrogates(payload):
2947db96d56Sopenharmony_ci                bpayload = payload.encode('ascii', 'surrogateescape')
2957db96d56Sopenharmony_ci                if not decode:
2967db96d56Sopenharmony_ci                    try:
2977db96d56Sopenharmony_ci                        payload = bpayload.decode(self.get_param('charset', 'ascii'), 'replace')
2987db96d56Sopenharmony_ci                    except LookupError:
2997db96d56Sopenharmony_ci                        payload = bpayload.decode('ascii', 'replace')
3007db96d56Sopenharmony_ci            elif decode:
3017db96d56Sopenharmony_ci                try:
3027db96d56Sopenharmony_ci                    bpayload = payload.encode('ascii')
3037db96d56Sopenharmony_ci                except UnicodeError:
3047db96d56Sopenharmony_ci                    # This won't happen for RFC compliant messages (messages
3057db96d56Sopenharmony_ci                    # containing only ASCII code points in the unicode input).
3067db96d56Sopenharmony_ci                    # If it does happen, turn the string into bytes in a way
3077db96d56Sopenharmony_ci                    # guaranteed not to fail.
3087db96d56Sopenharmony_ci                    bpayload = payload.encode('raw-unicode-escape')
3097db96d56Sopenharmony_ci        if not decode:
3107db96d56Sopenharmony_ci            return payload
3117db96d56Sopenharmony_ci        if cte == 'quoted-printable':
3127db96d56Sopenharmony_ci            return quopri.decodestring(bpayload)
3137db96d56Sopenharmony_ci        elif cte == 'base64':
3147db96d56Sopenharmony_ci            # XXX: this is a bit of a hack; decode_b should probably be factored
3157db96d56Sopenharmony_ci            # out somewhere, but I haven't figured out where yet.
3167db96d56Sopenharmony_ci            value, defects = decode_b(b''.join(bpayload.splitlines()))
3177db96d56Sopenharmony_ci            for defect in defects:
3187db96d56Sopenharmony_ci                self.policy.handle_defect(self, defect)
3197db96d56Sopenharmony_ci            return value
3207db96d56Sopenharmony_ci        elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
3217db96d56Sopenharmony_ci            try:
3227db96d56Sopenharmony_ci                return _decode_uu(bpayload)
3237db96d56Sopenharmony_ci            except ValueError:
3247db96d56Sopenharmony_ci                # Some decoding problem.
3257db96d56Sopenharmony_ci                return bpayload
3267db96d56Sopenharmony_ci        if isinstance(payload, str):
3277db96d56Sopenharmony_ci            return bpayload
3287db96d56Sopenharmony_ci        return payload
3297db96d56Sopenharmony_ci
3307db96d56Sopenharmony_ci    def set_payload(self, payload, charset=None):
3317db96d56Sopenharmony_ci        """Set the payload to the given value.
3327db96d56Sopenharmony_ci
3337db96d56Sopenharmony_ci        Optional charset sets the message's default character set.  See
3347db96d56Sopenharmony_ci        set_charset() for details.
3357db96d56Sopenharmony_ci        """
3367db96d56Sopenharmony_ci        if hasattr(payload, 'encode'):
3377db96d56Sopenharmony_ci            if charset is None:
3387db96d56Sopenharmony_ci                self._payload = payload
3397db96d56Sopenharmony_ci                return
3407db96d56Sopenharmony_ci            if not isinstance(charset, Charset):
3417db96d56Sopenharmony_ci                charset = Charset(charset)
3427db96d56Sopenharmony_ci            payload = payload.encode(charset.output_charset)
3437db96d56Sopenharmony_ci        if hasattr(payload, 'decode'):
3447db96d56Sopenharmony_ci            self._payload = payload.decode('ascii', 'surrogateescape')
3457db96d56Sopenharmony_ci        else:
3467db96d56Sopenharmony_ci            self._payload = payload
3477db96d56Sopenharmony_ci        if charset is not None:
3487db96d56Sopenharmony_ci            self.set_charset(charset)
3497db96d56Sopenharmony_ci
3507db96d56Sopenharmony_ci    def set_charset(self, charset):
3517db96d56Sopenharmony_ci        """Set the charset of the payload to a given character set.
3527db96d56Sopenharmony_ci
3537db96d56Sopenharmony_ci        charset can be a Charset instance, a string naming a character set, or
3547db96d56Sopenharmony_ci        None.  If it is a string it will be converted to a Charset instance.
3557db96d56Sopenharmony_ci        If charset is None, the charset parameter will be removed from the
3567db96d56Sopenharmony_ci        Content-Type field.  Anything else will generate a TypeError.
3577db96d56Sopenharmony_ci
3587db96d56Sopenharmony_ci        The message will be assumed to be of type text/* encoded with
3597db96d56Sopenharmony_ci        charset.input_charset.  It will be converted to charset.output_charset
3607db96d56Sopenharmony_ci        and encoded properly, if needed, when generating the plain text
3617db96d56Sopenharmony_ci        representation of the message.  MIME headers (MIME-Version,
3627db96d56Sopenharmony_ci        Content-Type, Content-Transfer-Encoding) will be added as needed.
3637db96d56Sopenharmony_ci        """
3647db96d56Sopenharmony_ci        if charset is None:
3657db96d56Sopenharmony_ci            self.del_param('charset')
3667db96d56Sopenharmony_ci            self._charset = None
3677db96d56Sopenharmony_ci            return
3687db96d56Sopenharmony_ci        if not isinstance(charset, Charset):
3697db96d56Sopenharmony_ci            charset = Charset(charset)
3707db96d56Sopenharmony_ci        self._charset = charset
3717db96d56Sopenharmony_ci        if 'MIME-Version' not in self:
3727db96d56Sopenharmony_ci            self.add_header('MIME-Version', '1.0')
3737db96d56Sopenharmony_ci        if 'Content-Type' not in self:
3747db96d56Sopenharmony_ci            self.add_header('Content-Type', 'text/plain',
3757db96d56Sopenharmony_ci                            charset=charset.get_output_charset())
3767db96d56Sopenharmony_ci        else:
3777db96d56Sopenharmony_ci            self.set_param('charset', charset.get_output_charset())
3787db96d56Sopenharmony_ci        if charset != charset.get_output_charset():
3797db96d56Sopenharmony_ci            self._payload = charset.body_encode(self._payload)
3807db96d56Sopenharmony_ci        if 'Content-Transfer-Encoding' not in self:
3817db96d56Sopenharmony_ci            cte = charset.get_body_encoding()
3827db96d56Sopenharmony_ci            try:
3837db96d56Sopenharmony_ci                cte(self)
3847db96d56Sopenharmony_ci            except TypeError:
3857db96d56Sopenharmony_ci                # This 'if' is for backward compatibility, it allows unicode
3867db96d56Sopenharmony_ci                # through even though that won't work correctly if the
3877db96d56Sopenharmony_ci                # message is serialized.
3887db96d56Sopenharmony_ci                payload = self._payload
3897db96d56Sopenharmony_ci                if payload:
3907db96d56Sopenharmony_ci                    try:
3917db96d56Sopenharmony_ci                        payload = payload.encode('ascii', 'surrogateescape')
3927db96d56Sopenharmony_ci                    except UnicodeError:
3937db96d56Sopenharmony_ci                        payload = payload.encode(charset.output_charset)
3947db96d56Sopenharmony_ci                self._payload = charset.body_encode(payload)
3957db96d56Sopenharmony_ci                self.add_header('Content-Transfer-Encoding', cte)
3967db96d56Sopenharmony_ci
3977db96d56Sopenharmony_ci    def get_charset(self):
3987db96d56Sopenharmony_ci        """Return the Charset instance associated with the message's payload.
3997db96d56Sopenharmony_ci        """
4007db96d56Sopenharmony_ci        return self._charset
4017db96d56Sopenharmony_ci
4027db96d56Sopenharmony_ci    #
4037db96d56Sopenharmony_ci    # MAPPING INTERFACE (partial)
4047db96d56Sopenharmony_ci    #
4057db96d56Sopenharmony_ci    def __len__(self):
4067db96d56Sopenharmony_ci        """Return the total number of headers, including duplicates."""
4077db96d56Sopenharmony_ci        return len(self._headers)
4087db96d56Sopenharmony_ci
4097db96d56Sopenharmony_ci    def __getitem__(self, name):
4107db96d56Sopenharmony_ci        """Get a header value.
4117db96d56Sopenharmony_ci
4127db96d56Sopenharmony_ci        Return None if the header is missing instead of raising an exception.
4137db96d56Sopenharmony_ci
4147db96d56Sopenharmony_ci        Note that if the header appeared multiple times, exactly which
4157db96d56Sopenharmony_ci        occurrence gets returned is undefined.  Use get_all() to get all
4167db96d56Sopenharmony_ci        the values matching a header field name.
4177db96d56Sopenharmony_ci        """
4187db96d56Sopenharmony_ci        return self.get(name)
4197db96d56Sopenharmony_ci
4207db96d56Sopenharmony_ci    def __setitem__(self, name, val):
4217db96d56Sopenharmony_ci        """Set the value of a header.
4227db96d56Sopenharmony_ci
4237db96d56Sopenharmony_ci        Note: this does not overwrite an existing header with the same field
4247db96d56Sopenharmony_ci        name.  Use __delitem__() first to delete any existing headers.
4257db96d56Sopenharmony_ci        """
4267db96d56Sopenharmony_ci        max_count = self.policy.header_max_count(name)
4277db96d56Sopenharmony_ci        if max_count:
4287db96d56Sopenharmony_ci            lname = name.lower()
4297db96d56Sopenharmony_ci            found = 0
4307db96d56Sopenharmony_ci            for k, v in self._headers:
4317db96d56Sopenharmony_ci                if k.lower() == lname:
4327db96d56Sopenharmony_ci                    found += 1
4337db96d56Sopenharmony_ci                    if found >= max_count:
4347db96d56Sopenharmony_ci                        raise ValueError("There may be at most {} {} headers "
4357db96d56Sopenharmony_ci                                         "in a message".format(max_count, name))
4367db96d56Sopenharmony_ci        self._headers.append(self.policy.header_store_parse(name, val))
4377db96d56Sopenharmony_ci
4387db96d56Sopenharmony_ci    def __delitem__(self, name):
4397db96d56Sopenharmony_ci        """Delete all occurrences of a header, if present.
4407db96d56Sopenharmony_ci
4417db96d56Sopenharmony_ci        Does not raise an exception if the header is missing.
4427db96d56Sopenharmony_ci        """
4437db96d56Sopenharmony_ci        name = name.lower()
4447db96d56Sopenharmony_ci        newheaders = []
4457db96d56Sopenharmony_ci        for k, v in self._headers:
4467db96d56Sopenharmony_ci            if k.lower() != name:
4477db96d56Sopenharmony_ci                newheaders.append((k, v))
4487db96d56Sopenharmony_ci        self._headers = newheaders
4497db96d56Sopenharmony_ci
4507db96d56Sopenharmony_ci    def __contains__(self, name):
4517db96d56Sopenharmony_ci        return name.lower() in [k.lower() for k, v in self._headers]
4527db96d56Sopenharmony_ci
4537db96d56Sopenharmony_ci    def __iter__(self):
4547db96d56Sopenharmony_ci        for field, value in self._headers:
4557db96d56Sopenharmony_ci            yield field
4567db96d56Sopenharmony_ci
4577db96d56Sopenharmony_ci    def keys(self):
4587db96d56Sopenharmony_ci        """Return a list of all the message's header field names.
4597db96d56Sopenharmony_ci
4607db96d56Sopenharmony_ci        These will be sorted in the order they appeared in the original
4617db96d56Sopenharmony_ci        message, or were added to the message, and may contain duplicates.
4627db96d56Sopenharmony_ci        Any fields deleted and re-inserted are always appended to the header
4637db96d56Sopenharmony_ci        list.
4647db96d56Sopenharmony_ci        """
4657db96d56Sopenharmony_ci        return [k for k, v in self._headers]
4667db96d56Sopenharmony_ci
4677db96d56Sopenharmony_ci    def values(self):
4687db96d56Sopenharmony_ci        """Return a list of all the message's header values.
4697db96d56Sopenharmony_ci
4707db96d56Sopenharmony_ci        These will be sorted in the order they appeared in the original
4717db96d56Sopenharmony_ci        message, or were added to the message, and may contain duplicates.
4727db96d56Sopenharmony_ci        Any fields deleted and re-inserted are always appended to the header
4737db96d56Sopenharmony_ci        list.
4747db96d56Sopenharmony_ci        """
4757db96d56Sopenharmony_ci        return [self.policy.header_fetch_parse(k, v)
4767db96d56Sopenharmony_ci                for k, v in self._headers]
4777db96d56Sopenharmony_ci
4787db96d56Sopenharmony_ci    def items(self):
4797db96d56Sopenharmony_ci        """Get all the message's header fields and values.
4807db96d56Sopenharmony_ci
4817db96d56Sopenharmony_ci        These will be sorted in the order they appeared in the original
4827db96d56Sopenharmony_ci        message, or were added to the message, and may contain duplicates.
4837db96d56Sopenharmony_ci        Any fields deleted and re-inserted are always appended to the header
4847db96d56Sopenharmony_ci        list.
4857db96d56Sopenharmony_ci        """
4867db96d56Sopenharmony_ci        return [(k, self.policy.header_fetch_parse(k, v))
4877db96d56Sopenharmony_ci                for k, v in self._headers]
4887db96d56Sopenharmony_ci
4897db96d56Sopenharmony_ci    def get(self, name, failobj=None):
4907db96d56Sopenharmony_ci        """Get a header value.
4917db96d56Sopenharmony_ci
4927db96d56Sopenharmony_ci        Like __getitem__() but return failobj instead of None when the field
4937db96d56Sopenharmony_ci        is missing.
4947db96d56Sopenharmony_ci        """
4957db96d56Sopenharmony_ci        name = name.lower()
4967db96d56Sopenharmony_ci        for k, v in self._headers:
4977db96d56Sopenharmony_ci            if k.lower() == name:
4987db96d56Sopenharmony_ci                return self.policy.header_fetch_parse(k, v)
4997db96d56Sopenharmony_ci        return failobj
5007db96d56Sopenharmony_ci
5017db96d56Sopenharmony_ci    #
5027db96d56Sopenharmony_ci    # "Internal" methods (public API, but only intended for use by a parser
5037db96d56Sopenharmony_ci    # or generator, not normal application code.
5047db96d56Sopenharmony_ci    #
5057db96d56Sopenharmony_ci
5067db96d56Sopenharmony_ci    def set_raw(self, name, value):
5077db96d56Sopenharmony_ci        """Store name and value in the model without modification.
5087db96d56Sopenharmony_ci
5097db96d56Sopenharmony_ci        This is an "internal" API, intended only for use by a parser.
5107db96d56Sopenharmony_ci        """
5117db96d56Sopenharmony_ci        self._headers.append((name, value))
5127db96d56Sopenharmony_ci
5137db96d56Sopenharmony_ci    def raw_items(self):
5147db96d56Sopenharmony_ci        """Return the (name, value) header pairs without modification.
5157db96d56Sopenharmony_ci
5167db96d56Sopenharmony_ci        This is an "internal" API, intended only for use by a generator.
5177db96d56Sopenharmony_ci        """
5187db96d56Sopenharmony_ci        return iter(self._headers.copy())
5197db96d56Sopenharmony_ci
5207db96d56Sopenharmony_ci    #
5217db96d56Sopenharmony_ci    # Additional useful stuff
5227db96d56Sopenharmony_ci    #
5237db96d56Sopenharmony_ci
5247db96d56Sopenharmony_ci    def get_all(self, name, failobj=None):
5257db96d56Sopenharmony_ci        """Return a list of all the values for the named field.
5267db96d56Sopenharmony_ci
5277db96d56Sopenharmony_ci        These will be sorted in the order they appeared in the original
5287db96d56Sopenharmony_ci        message, and may contain duplicates.  Any fields deleted and
5297db96d56Sopenharmony_ci        re-inserted are always appended to the header list.
5307db96d56Sopenharmony_ci
5317db96d56Sopenharmony_ci        If no such fields exist, failobj is returned (defaults to None).
5327db96d56Sopenharmony_ci        """
5337db96d56Sopenharmony_ci        values = []
5347db96d56Sopenharmony_ci        name = name.lower()
5357db96d56Sopenharmony_ci        for k, v in self._headers:
5367db96d56Sopenharmony_ci            if k.lower() == name:
5377db96d56Sopenharmony_ci                values.append(self.policy.header_fetch_parse(k, v))
5387db96d56Sopenharmony_ci        if not values:
5397db96d56Sopenharmony_ci            return failobj
5407db96d56Sopenharmony_ci        return values
5417db96d56Sopenharmony_ci
5427db96d56Sopenharmony_ci    def add_header(self, _name, _value, **_params):
5437db96d56Sopenharmony_ci        """Extended header setting.
5447db96d56Sopenharmony_ci
5457db96d56Sopenharmony_ci        name is the header field to add.  keyword arguments can be used to set
5467db96d56Sopenharmony_ci        additional parameters for the header field, with underscores converted
5477db96d56Sopenharmony_ci        to dashes.  Normally the parameter will be added as key="value" unless
5487db96d56Sopenharmony_ci        value is None, in which case only the key will be added.  If a
5497db96d56Sopenharmony_ci        parameter value contains non-ASCII characters it can be specified as a
5507db96d56Sopenharmony_ci        three-tuple of (charset, language, value), in which case it will be
5517db96d56Sopenharmony_ci        encoded according to RFC2231 rules.  Otherwise it will be encoded using
5527db96d56Sopenharmony_ci        the utf-8 charset and a language of ''.
5537db96d56Sopenharmony_ci
5547db96d56Sopenharmony_ci        Examples:
5557db96d56Sopenharmony_ci
5567db96d56Sopenharmony_ci        msg.add_header('content-disposition', 'attachment', filename='bud.gif')
5577db96d56Sopenharmony_ci        msg.add_header('content-disposition', 'attachment',
5587db96d56Sopenharmony_ci                       filename=('utf-8', '', Fußballer.ppt'))
5597db96d56Sopenharmony_ci        msg.add_header('content-disposition', 'attachment',
5607db96d56Sopenharmony_ci                       filename='Fußballer.ppt'))
5617db96d56Sopenharmony_ci        """
5627db96d56Sopenharmony_ci        parts = []
5637db96d56Sopenharmony_ci        for k, v in _params.items():
5647db96d56Sopenharmony_ci            if v is None:
5657db96d56Sopenharmony_ci                parts.append(k.replace('_', '-'))
5667db96d56Sopenharmony_ci            else:
5677db96d56Sopenharmony_ci                parts.append(_formatparam(k.replace('_', '-'), v))
5687db96d56Sopenharmony_ci        if _value is not None:
5697db96d56Sopenharmony_ci            parts.insert(0, _value)
5707db96d56Sopenharmony_ci        self[_name] = SEMISPACE.join(parts)
5717db96d56Sopenharmony_ci
5727db96d56Sopenharmony_ci    def replace_header(self, _name, _value):
5737db96d56Sopenharmony_ci        """Replace a header.
5747db96d56Sopenharmony_ci
5757db96d56Sopenharmony_ci        Replace the first matching header found in the message, retaining
5767db96d56Sopenharmony_ci        header order and case.  If no matching header was found, a KeyError is
5777db96d56Sopenharmony_ci        raised.
5787db96d56Sopenharmony_ci        """
5797db96d56Sopenharmony_ci        _name = _name.lower()
5807db96d56Sopenharmony_ci        for i, (k, v) in zip(range(len(self._headers)), self._headers):
5817db96d56Sopenharmony_ci            if k.lower() == _name:
5827db96d56Sopenharmony_ci                self._headers[i] = self.policy.header_store_parse(k, _value)
5837db96d56Sopenharmony_ci                break
5847db96d56Sopenharmony_ci        else:
5857db96d56Sopenharmony_ci            raise KeyError(_name)
5867db96d56Sopenharmony_ci
5877db96d56Sopenharmony_ci    #
5887db96d56Sopenharmony_ci    # Use these three methods instead of the three above.
5897db96d56Sopenharmony_ci    #
5907db96d56Sopenharmony_ci
5917db96d56Sopenharmony_ci    def get_content_type(self):
5927db96d56Sopenharmony_ci        """Return the message's content type.
5937db96d56Sopenharmony_ci
5947db96d56Sopenharmony_ci        The returned string is coerced to lower case of the form
5957db96d56Sopenharmony_ci        `maintype/subtype'.  If there was no Content-Type header in the
5967db96d56Sopenharmony_ci        message, the default type as given by get_default_type() will be
5977db96d56Sopenharmony_ci        returned.  Since according to RFC 2045, messages always have a default
5987db96d56Sopenharmony_ci        type this will always return a value.
5997db96d56Sopenharmony_ci
6007db96d56Sopenharmony_ci        RFC 2045 defines a message's default type to be text/plain unless it
6017db96d56Sopenharmony_ci        appears inside a multipart/digest container, in which case it would be
6027db96d56Sopenharmony_ci        message/rfc822.
6037db96d56Sopenharmony_ci        """
6047db96d56Sopenharmony_ci        missing = object()
6057db96d56Sopenharmony_ci        value = self.get('content-type', missing)
6067db96d56Sopenharmony_ci        if value is missing:
6077db96d56Sopenharmony_ci            # This should have no parameters
6087db96d56Sopenharmony_ci            return self.get_default_type()
6097db96d56Sopenharmony_ci        ctype = _splitparam(value)[0].lower()
6107db96d56Sopenharmony_ci        # RFC 2045, section 5.2 says if its invalid, use text/plain
6117db96d56Sopenharmony_ci        if ctype.count('/') != 1:
6127db96d56Sopenharmony_ci            return 'text/plain'
6137db96d56Sopenharmony_ci        return ctype
6147db96d56Sopenharmony_ci
6157db96d56Sopenharmony_ci    def get_content_maintype(self):
6167db96d56Sopenharmony_ci        """Return the message's main content type.
6177db96d56Sopenharmony_ci
6187db96d56Sopenharmony_ci        This is the `maintype' part of the string returned by
6197db96d56Sopenharmony_ci        get_content_type().
6207db96d56Sopenharmony_ci        """
6217db96d56Sopenharmony_ci        ctype = self.get_content_type()
6227db96d56Sopenharmony_ci        return ctype.split('/')[0]
6237db96d56Sopenharmony_ci
6247db96d56Sopenharmony_ci    def get_content_subtype(self):
6257db96d56Sopenharmony_ci        """Returns the message's sub-content type.
6267db96d56Sopenharmony_ci
6277db96d56Sopenharmony_ci        This is the `subtype' part of the string returned by
6287db96d56Sopenharmony_ci        get_content_type().
6297db96d56Sopenharmony_ci        """
6307db96d56Sopenharmony_ci        ctype = self.get_content_type()
6317db96d56Sopenharmony_ci        return ctype.split('/')[1]
6327db96d56Sopenharmony_ci
6337db96d56Sopenharmony_ci    def get_default_type(self):
6347db96d56Sopenharmony_ci        """Return the `default' content type.
6357db96d56Sopenharmony_ci
6367db96d56Sopenharmony_ci        Most messages have a default content type of text/plain, except for
6377db96d56Sopenharmony_ci        messages that are subparts of multipart/digest containers.  Such
6387db96d56Sopenharmony_ci        subparts have a default content type of message/rfc822.
6397db96d56Sopenharmony_ci        """
6407db96d56Sopenharmony_ci        return self._default_type
6417db96d56Sopenharmony_ci
6427db96d56Sopenharmony_ci    def set_default_type(self, ctype):
6437db96d56Sopenharmony_ci        """Set the `default' content type.
6447db96d56Sopenharmony_ci
6457db96d56Sopenharmony_ci        ctype should be either "text/plain" or "message/rfc822", although this
6467db96d56Sopenharmony_ci        is not enforced.  The default content type is not stored in the
6477db96d56Sopenharmony_ci        Content-Type header.
6487db96d56Sopenharmony_ci        """
6497db96d56Sopenharmony_ci        self._default_type = ctype
6507db96d56Sopenharmony_ci
6517db96d56Sopenharmony_ci    def _get_params_preserve(self, failobj, header):
6527db96d56Sopenharmony_ci        # Like get_params() but preserves the quoting of values.  BAW:
6537db96d56Sopenharmony_ci        # should this be part of the public interface?
6547db96d56Sopenharmony_ci        missing = object()
6557db96d56Sopenharmony_ci        value = self.get(header, missing)
6567db96d56Sopenharmony_ci        if value is missing:
6577db96d56Sopenharmony_ci            return failobj
6587db96d56Sopenharmony_ci        params = []
6597db96d56Sopenharmony_ci        for p in _parseparam(value):
6607db96d56Sopenharmony_ci            try:
6617db96d56Sopenharmony_ci                name, val = p.split('=', 1)
6627db96d56Sopenharmony_ci                name = name.strip()
6637db96d56Sopenharmony_ci                val = val.strip()
6647db96d56Sopenharmony_ci            except ValueError:
6657db96d56Sopenharmony_ci                # Must have been a bare attribute
6667db96d56Sopenharmony_ci                name = p.strip()
6677db96d56Sopenharmony_ci                val = ''
6687db96d56Sopenharmony_ci            params.append((name, val))
6697db96d56Sopenharmony_ci        params = utils.decode_params(params)
6707db96d56Sopenharmony_ci        return params
6717db96d56Sopenharmony_ci
6727db96d56Sopenharmony_ci    def get_params(self, failobj=None, header='content-type', unquote=True):
6737db96d56Sopenharmony_ci        """Return the message's Content-Type parameters, as a list.
6747db96d56Sopenharmony_ci
6757db96d56Sopenharmony_ci        The elements of the returned list are 2-tuples of key/value pairs, as
6767db96d56Sopenharmony_ci        split on the `=' sign.  The left hand side of the `=' is the key,
6777db96d56Sopenharmony_ci        while the right hand side is the value.  If there is no `=' sign in
6787db96d56Sopenharmony_ci        the parameter the value is the empty string.  The value is as
6797db96d56Sopenharmony_ci        described in the get_param() method.
6807db96d56Sopenharmony_ci
6817db96d56Sopenharmony_ci        Optional failobj is the object to return if there is no Content-Type
6827db96d56Sopenharmony_ci        header.  Optional header is the header to search instead of
6837db96d56Sopenharmony_ci        Content-Type.  If unquote is True, the value is unquoted.
6847db96d56Sopenharmony_ci        """
6857db96d56Sopenharmony_ci        missing = object()
6867db96d56Sopenharmony_ci        params = self._get_params_preserve(missing, header)
6877db96d56Sopenharmony_ci        if params is missing:
6887db96d56Sopenharmony_ci            return failobj
6897db96d56Sopenharmony_ci        if unquote:
6907db96d56Sopenharmony_ci            return [(k, _unquotevalue(v)) for k, v in params]
6917db96d56Sopenharmony_ci        else:
6927db96d56Sopenharmony_ci            return params
6937db96d56Sopenharmony_ci
6947db96d56Sopenharmony_ci    def get_param(self, param, failobj=None, header='content-type',
6957db96d56Sopenharmony_ci                  unquote=True):
6967db96d56Sopenharmony_ci        """Return the parameter value if found in the Content-Type header.
6977db96d56Sopenharmony_ci
6987db96d56Sopenharmony_ci        Optional failobj is the object to return if there is no Content-Type
6997db96d56Sopenharmony_ci        header, or the Content-Type header has no such parameter.  Optional
7007db96d56Sopenharmony_ci        header is the header to search instead of Content-Type.
7017db96d56Sopenharmony_ci
7027db96d56Sopenharmony_ci        Parameter keys are always compared case insensitively.  The return
7037db96d56Sopenharmony_ci        value can either be a string, or a 3-tuple if the parameter was RFC
7047db96d56Sopenharmony_ci        2231 encoded.  When it's a 3-tuple, the elements of the value are of
7057db96d56Sopenharmony_ci        the form (CHARSET, LANGUAGE, VALUE).  Note that both CHARSET and
7067db96d56Sopenharmony_ci        LANGUAGE can be None, in which case you should consider VALUE to be
7077db96d56Sopenharmony_ci        encoded in the us-ascii charset.  You can usually ignore LANGUAGE.
7087db96d56Sopenharmony_ci        The parameter value (either the returned string, or the VALUE item in
7097db96d56Sopenharmony_ci        the 3-tuple) is always unquoted, unless unquote is set to False.
7107db96d56Sopenharmony_ci
7117db96d56Sopenharmony_ci        If your application doesn't care whether the parameter was RFC 2231
7127db96d56Sopenharmony_ci        encoded, it can turn the return value into a string as follows:
7137db96d56Sopenharmony_ci
7147db96d56Sopenharmony_ci            rawparam = msg.get_param('foo')
7157db96d56Sopenharmony_ci            param = email.utils.collapse_rfc2231_value(rawparam)
7167db96d56Sopenharmony_ci
7177db96d56Sopenharmony_ci        """
7187db96d56Sopenharmony_ci        if header not in self:
7197db96d56Sopenharmony_ci            return failobj
7207db96d56Sopenharmony_ci        for k, v in self._get_params_preserve(failobj, header):
7217db96d56Sopenharmony_ci            if k.lower() == param.lower():
7227db96d56Sopenharmony_ci                if unquote:
7237db96d56Sopenharmony_ci                    return _unquotevalue(v)
7247db96d56Sopenharmony_ci                else:
7257db96d56Sopenharmony_ci                    return v
7267db96d56Sopenharmony_ci        return failobj
7277db96d56Sopenharmony_ci
7287db96d56Sopenharmony_ci    def set_param(self, param, value, header='Content-Type', requote=True,
7297db96d56Sopenharmony_ci                  charset=None, language='', replace=False):
7307db96d56Sopenharmony_ci        """Set a parameter in the Content-Type header.
7317db96d56Sopenharmony_ci
7327db96d56Sopenharmony_ci        If the parameter already exists in the header, its value will be
7337db96d56Sopenharmony_ci        replaced with the new value.
7347db96d56Sopenharmony_ci
7357db96d56Sopenharmony_ci        If header is Content-Type and has not yet been defined for this
7367db96d56Sopenharmony_ci        message, it will be set to "text/plain" and the new parameter and
7377db96d56Sopenharmony_ci        value will be appended as per RFC 2045.
7387db96d56Sopenharmony_ci
7397db96d56Sopenharmony_ci        An alternate header can be specified in the header argument, and all
7407db96d56Sopenharmony_ci        parameters will be quoted as necessary unless requote is False.
7417db96d56Sopenharmony_ci
7427db96d56Sopenharmony_ci        If charset is specified, the parameter will be encoded according to RFC
7437db96d56Sopenharmony_ci        2231.  Optional language specifies the RFC 2231 language, defaulting
7447db96d56Sopenharmony_ci        to the empty string.  Both charset and language should be strings.
7457db96d56Sopenharmony_ci        """
7467db96d56Sopenharmony_ci        if not isinstance(value, tuple) and charset:
7477db96d56Sopenharmony_ci            value = (charset, language, value)
7487db96d56Sopenharmony_ci
7497db96d56Sopenharmony_ci        if header not in self and header.lower() == 'content-type':
7507db96d56Sopenharmony_ci            ctype = 'text/plain'
7517db96d56Sopenharmony_ci        else:
7527db96d56Sopenharmony_ci            ctype = self.get(header)
7537db96d56Sopenharmony_ci        if not self.get_param(param, header=header):
7547db96d56Sopenharmony_ci            if not ctype:
7557db96d56Sopenharmony_ci                ctype = _formatparam(param, value, requote)
7567db96d56Sopenharmony_ci            else:
7577db96d56Sopenharmony_ci                ctype = SEMISPACE.join(
7587db96d56Sopenharmony_ci                    [ctype, _formatparam(param, value, requote)])
7597db96d56Sopenharmony_ci        else:
7607db96d56Sopenharmony_ci            ctype = ''
7617db96d56Sopenharmony_ci            for old_param, old_value in self.get_params(header=header,
7627db96d56Sopenharmony_ci                                                        unquote=requote):
7637db96d56Sopenharmony_ci                append_param = ''
7647db96d56Sopenharmony_ci                if old_param.lower() == param.lower():
7657db96d56Sopenharmony_ci                    append_param = _formatparam(param, value, requote)
7667db96d56Sopenharmony_ci                else:
7677db96d56Sopenharmony_ci                    append_param = _formatparam(old_param, old_value, requote)
7687db96d56Sopenharmony_ci                if not ctype:
7697db96d56Sopenharmony_ci                    ctype = append_param
7707db96d56Sopenharmony_ci                else:
7717db96d56Sopenharmony_ci                    ctype = SEMISPACE.join([ctype, append_param])
7727db96d56Sopenharmony_ci        if ctype != self.get(header):
7737db96d56Sopenharmony_ci            if replace:
7747db96d56Sopenharmony_ci                self.replace_header(header, ctype)
7757db96d56Sopenharmony_ci            else:
7767db96d56Sopenharmony_ci                del self[header]
7777db96d56Sopenharmony_ci                self[header] = ctype
7787db96d56Sopenharmony_ci
7797db96d56Sopenharmony_ci    def del_param(self, param, header='content-type', requote=True):
7807db96d56Sopenharmony_ci        """Remove the given parameter completely from the Content-Type header.
7817db96d56Sopenharmony_ci
7827db96d56Sopenharmony_ci        The header will be re-written in place without the parameter or its
7837db96d56Sopenharmony_ci        value. All values will be quoted as necessary unless requote is
7847db96d56Sopenharmony_ci        False.  Optional header specifies an alternative to the Content-Type
7857db96d56Sopenharmony_ci        header.
7867db96d56Sopenharmony_ci        """
7877db96d56Sopenharmony_ci        if header not in self:
7887db96d56Sopenharmony_ci            return
7897db96d56Sopenharmony_ci        new_ctype = ''
7907db96d56Sopenharmony_ci        for p, v in self.get_params(header=header, unquote=requote):
7917db96d56Sopenharmony_ci            if p.lower() != param.lower():
7927db96d56Sopenharmony_ci                if not new_ctype:
7937db96d56Sopenharmony_ci                    new_ctype = _formatparam(p, v, requote)
7947db96d56Sopenharmony_ci                else:
7957db96d56Sopenharmony_ci                    new_ctype = SEMISPACE.join([new_ctype,
7967db96d56Sopenharmony_ci                                                _formatparam(p, v, requote)])
7977db96d56Sopenharmony_ci        if new_ctype != self.get(header):
7987db96d56Sopenharmony_ci            del self[header]
7997db96d56Sopenharmony_ci            self[header] = new_ctype
8007db96d56Sopenharmony_ci
8017db96d56Sopenharmony_ci    def set_type(self, type, header='Content-Type', requote=True):
8027db96d56Sopenharmony_ci        """Set the main type and subtype for the Content-Type header.
8037db96d56Sopenharmony_ci
8047db96d56Sopenharmony_ci        type must be a string in the form "maintype/subtype", otherwise a
8057db96d56Sopenharmony_ci        ValueError is raised.
8067db96d56Sopenharmony_ci
8077db96d56Sopenharmony_ci        This method replaces the Content-Type header, keeping all the
8087db96d56Sopenharmony_ci        parameters in place.  If requote is False, this leaves the existing
8097db96d56Sopenharmony_ci        header's quoting as is.  Otherwise, the parameters will be quoted (the
8107db96d56Sopenharmony_ci        default).
8117db96d56Sopenharmony_ci
8127db96d56Sopenharmony_ci        An alternative header can be specified in the header argument.  When
8137db96d56Sopenharmony_ci        the Content-Type header is set, we'll always also add a MIME-Version
8147db96d56Sopenharmony_ci        header.
8157db96d56Sopenharmony_ci        """
8167db96d56Sopenharmony_ci        # BAW: should we be strict?
8177db96d56Sopenharmony_ci        if not type.count('/') == 1:
8187db96d56Sopenharmony_ci            raise ValueError
8197db96d56Sopenharmony_ci        # Set the Content-Type, you get a MIME-Version
8207db96d56Sopenharmony_ci        if header.lower() == 'content-type':
8217db96d56Sopenharmony_ci            del self['mime-version']
8227db96d56Sopenharmony_ci            self['MIME-Version'] = '1.0'
8237db96d56Sopenharmony_ci        if header not in self:
8247db96d56Sopenharmony_ci            self[header] = type
8257db96d56Sopenharmony_ci            return
8267db96d56Sopenharmony_ci        params = self.get_params(header=header, unquote=requote)
8277db96d56Sopenharmony_ci        del self[header]
8287db96d56Sopenharmony_ci        self[header] = type
8297db96d56Sopenharmony_ci        # Skip the first param; it's the old type.
8307db96d56Sopenharmony_ci        for p, v in params[1:]:
8317db96d56Sopenharmony_ci            self.set_param(p, v, header, requote)
8327db96d56Sopenharmony_ci
8337db96d56Sopenharmony_ci    def get_filename(self, failobj=None):
8347db96d56Sopenharmony_ci        """Return the filename associated with the payload if present.
8357db96d56Sopenharmony_ci
8367db96d56Sopenharmony_ci        The filename is extracted from the Content-Disposition header's
8377db96d56Sopenharmony_ci        `filename' parameter, and it is unquoted.  If that header is missing
8387db96d56Sopenharmony_ci        the `filename' parameter, this method falls back to looking for the
8397db96d56Sopenharmony_ci        `name' parameter.
8407db96d56Sopenharmony_ci        """
8417db96d56Sopenharmony_ci        missing = object()
8427db96d56Sopenharmony_ci        filename = self.get_param('filename', missing, 'content-disposition')
8437db96d56Sopenharmony_ci        if filename is missing:
8447db96d56Sopenharmony_ci            filename = self.get_param('name', missing, 'content-type')
8457db96d56Sopenharmony_ci        if filename is missing:
8467db96d56Sopenharmony_ci            return failobj
8477db96d56Sopenharmony_ci        return utils.collapse_rfc2231_value(filename).strip()
8487db96d56Sopenharmony_ci
8497db96d56Sopenharmony_ci    def get_boundary(self, failobj=None):
8507db96d56Sopenharmony_ci        """Return the boundary associated with the payload if present.
8517db96d56Sopenharmony_ci
8527db96d56Sopenharmony_ci        The boundary is extracted from the Content-Type header's `boundary'
8537db96d56Sopenharmony_ci        parameter, and it is unquoted.
8547db96d56Sopenharmony_ci        """
8557db96d56Sopenharmony_ci        missing = object()
8567db96d56Sopenharmony_ci        boundary = self.get_param('boundary', missing)
8577db96d56Sopenharmony_ci        if boundary is missing:
8587db96d56Sopenharmony_ci            return failobj
8597db96d56Sopenharmony_ci        # RFC 2046 says that boundaries may begin but not end in w/s
8607db96d56Sopenharmony_ci        return utils.collapse_rfc2231_value(boundary).rstrip()
8617db96d56Sopenharmony_ci
8627db96d56Sopenharmony_ci    def set_boundary(self, boundary):
8637db96d56Sopenharmony_ci        """Set the boundary parameter in Content-Type to 'boundary'.
8647db96d56Sopenharmony_ci
8657db96d56Sopenharmony_ci        This is subtly different than deleting the Content-Type header and
8667db96d56Sopenharmony_ci        adding a new one with a new boundary parameter via add_header().  The
8677db96d56Sopenharmony_ci        main difference is that using the set_boundary() method preserves the
8687db96d56Sopenharmony_ci        order of the Content-Type header in the original message.
8697db96d56Sopenharmony_ci
8707db96d56Sopenharmony_ci        HeaderParseError is raised if the message has no Content-Type header.
8717db96d56Sopenharmony_ci        """
8727db96d56Sopenharmony_ci        missing = object()
8737db96d56Sopenharmony_ci        params = self._get_params_preserve(missing, 'content-type')
8747db96d56Sopenharmony_ci        if params is missing:
8757db96d56Sopenharmony_ci            # There was no Content-Type header, and we don't know what type
8767db96d56Sopenharmony_ci            # to set it to, so raise an exception.
8777db96d56Sopenharmony_ci            raise errors.HeaderParseError('No Content-Type header found')
8787db96d56Sopenharmony_ci        newparams = []
8797db96d56Sopenharmony_ci        foundp = False
8807db96d56Sopenharmony_ci        for pk, pv in params:
8817db96d56Sopenharmony_ci            if pk.lower() == 'boundary':
8827db96d56Sopenharmony_ci                newparams.append(('boundary', '"%s"' % boundary))
8837db96d56Sopenharmony_ci                foundp = True
8847db96d56Sopenharmony_ci            else:
8857db96d56Sopenharmony_ci                newparams.append((pk, pv))
8867db96d56Sopenharmony_ci        if not foundp:
8877db96d56Sopenharmony_ci            # The original Content-Type header had no boundary attribute.
8887db96d56Sopenharmony_ci            # Tack one on the end.  BAW: should we raise an exception
8897db96d56Sopenharmony_ci            # instead???
8907db96d56Sopenharmony_ci            newparams.append(('boundary', '"%s"' % boundary))
8917db96d56Sopenharmony_ci        # Replace the existing Content-Type header with the new value
8927db96d56Sopenharmony_ci        newheaders = []
8937db96d56Sopenharmony_ci        for h, v in self._headers:
8947db96d56Sopenharmony_ci            if h.lower() == 'content-type':
8957db96d56Sopenharmony_ci                parts = []
8967db96d56Sopenharmony_ci                for k, v in newparams:
8977db96d56Sopenharmony_ci                    if v == '':
8987db96d56Sopenharmony_ci                        parts.append(k)
8997db96d56Sopenharmony_ci                    else:
9007db96d56Sopenharmony_ci                        parts.append('%s=%s' % (k, v))
9017db96d56Sopenharmony_ci                val = SEMISPACE.join(parts)
9027db96d56Sopenharmony_ci                newheaders.append(self.policy.header_store_parse(h, val))
9037db96d56Sopenharmony_ci
9047db96d56Sopenharmony_ci            else:
9057db96d56Sopenharmony_ci                newheaders.append((h, v))
9067db96d56Sopenharmony_ci        self._headers = newheaders
9077db96d56Sopenharmony_ci
9087db96d56Sopenharmony_ci    def get_content_charset(self, failobj=None):
9097db96d56Sopenharmony_ci        """Return the charset parameter of the Content-Type header.
9107db96d56Sopenharmony_ci
9117db96d56Sopenharmony_ci        The returned string is always coerced to lower case.  If there is no
9127db96d56Sopenharmony_ci        Content-Type header, or if that header has no charset parameter,
9137db96d56Sopenharmony_ci        failobj is returned.
9147db96d56Sopenharmony_ci        """
9157db96d56Sopenharmony_ci        missing = object()
9167db96d56Sopenharmony_ci        charset = self.get_param('charset', missing)
9177db96d56Sopenharmony_ci        if charset is missing:
9187db96d56Sopenharmony_ci            return failobj
9197db96d56Sopenharmony_ci        if isinstance(charset, tuple):
9207db96d56Sopenharmony_ci            # RFC 2231 encoded, so decode it, and it better end up as ascii.
9217db96d56Sopenharmony_ci            pcharset = charset[0] or 'us-ascii'
9227db96d56Sopenharmony_ci            try:
9237db96d56Sopenharmony_ci                # LookupError will be raised if the charset isn't known to
9247db96d56Sopenharmony_ci                # Python.  UnicodeError will be raised if the encoded text
9257db96d56Sopenharmony_ci                # contains a character not in the charset.
9267db96d56Sopenharmony_ci                as_bytes = charset[2].encode('raw-unicode-escape')
9277db96d56Sopenharmony_ci                charset = str(as_bytes, pcharset)
9287db96d56Sopenharmony_ci            except (LookupError, UnicodeError):
9297db96d56Sopenharmony_ci                charset = charset[2]
9307db96d56Sopenharmony_ci        # charset characters must be in us-ascii range
9317db96d56Sopenharmony_ci        try:
9327db96d56Sopenharmony_ci            charset.encode('us-ascii')
9337db96d56Sopenharmony_ci        except UnicodeError:
9347db96d56Sopenharmony_ci            return failobj
9357db96d56Sopenharmony_ci        # RFC 2046, $4.1.2 says charsets are not case sensitive
9367db96d56Sopenharmony_ci        return charset.lower()
9377db96d56Sopenharmony_ci
9387db96d56Sopenharmony_ci    def get_charsets(self, failobj=None):
9397db96d56Sopenharmony_ci        """Return a list containing the charset(s) used in this message.
9407db96d56Sopenharmony_ci
9417db96d56Sopenharmony_ci        The returned list of items describes the Content-Type headers'
9427db96d56Sopenharmony_ci        charset parameter for this message and all the subparts in its
9437db96d56Sopenharmony_ci        payload.
9447db96d56Sopenharmony_ci
9457db96d56Sopenharmony_ci        Each item will either be a string (the value of the charset parameter
9467db96d56Sopenharmony_ci        in the Content-Type header of that part) or the value of the
9477db96d56Sopenharmony_ci        'failobj' parameter (defaults to None), if the part does not have a
9487db96d56Sopenharmony_ci        main MIME type of "text", or the charset is not defined.
9497db96d56Sopenharmony_ci
9507db96d56Sopenharmony_ci        The list will contain one string for each part of the message, plus
9517db96d56Sopenharmony_ci        one for the container message (i.e. self), so that a non-multipart
9527db96d56Sopenharmony_ci        message will still return a list of length 1.
9537db96d56Sopenharmony_ci        """
9547db96d56Sopenharmony_ci        return [part.get_content_charset(failobj) for part in self.walk()]
9557db96d56Sopenharmony_ci
9567db96d56Sopenharmony_ci    def get_content_disposition(self):
9577db96d56Sopenharmony_ci        """Return the message's content-disposition if it exists, or None.
9587db96d56Sopenharmony_ci
9597db96d56Sopenharmony_ci        The return values can be either 'inline', 'attachment' or None
9607db96d56Sopenharmony_ci        according to the rfc2183.
9617db96d56Sopenharmony_ci        """
9627db96d56Sopenharmony_ci        value = self.get('content-disposition')
9637db96d56Sopenharmony_ci        if value is None:
9647db96d56Sopenharmony_ci            return None
9657db96d56Sopenharmony_ci        c_d = _splitparam(value)[0].lower()
9667db96d56Sopenharmony_ci        return c_d
9677db96d56Sopenharmony_ci
9687db96d56Sopenharmony_ci    # I.e. def walk(self): ...
9697db96d56Sopenharmony_ci    from email.iterators import walk
9707db96d56Sopenharmony_ci
9717db96d56Sopenharmony_ci
9727db96d56Sopenharmony_ciclass MIMEPart(Message):
9737db96d56Sopenharmony_ci
9747db96d56Sopenharmony_ci    def __init__(self, policy=None):
9757db96d56Sopenharmony_ci        if policy is None:
9767db96d56Sopenharmony_ci            from email.policy import default
9777db96d56Sopenharmony_ci            policy = default
9787db96d56Sopenharmony_ci        super().__init__(policy)
9797db96d56Sopenharmony_ci
9807db96d56Sopenharmony_ci
9817db96d56Sopenharmony_ci    def as_string(self, unixfrom=False, maxheaderlen=None, policy=None):
9827db96d56Sopenharmony_ci        """Return the entire formatted message as a string.
9837db96d56Sopenharmony_ci
9847db96d56Sopenharmony_ci        Optional 'unixfrom', when true, means include the Unix From_ envelope
9857db96d56Sopenharmony_ci        header.  maxheaderlen is retained for backward compatibility with the
9867db96d56Sopenharmony_ci        base Message class, but defaults to None, meaning that the policy value
9877db96d56Sopenharmony_ci        for max_line_length controls the header maximum length.  'policy' is
9887db96d56Sopenharmony_ci        passed to the Generator instance used to serialize the message; if it
9897db96d56Sopenharmony_ci        is not specified the policy associated with the message instance is
9907db96d56Sopenharmony_ci        used.
9917db96d56Sopenharmony_ci        """
9927db96d56Sopenharmony_ci        policy = self.policy if policy is None else policy
9937db96d56Sopenharmony_ci        if maxheaderlen is None:
9947db96d56Sopenharmony_ci            maxheaderlen = policy.max_line_length
9957db96d56Sopenharmony_ci        return super().as_string(unixfrom, maxheaderlen, policy)
9967db96d56Sopenharmony_ci
9977db96d56Sopenharmony_ci    def __str__(self):
9987db96d56Sopenharmony_ci        return self.as_string(policy=self.policy.clone(utf8=True))
9997db96d56Sopenharmony_ci
10007db96d56Sopenharmony_ci    def is_attachment(self):
10017db96d56Sopenharmony_ci        c_d = self.get('content-disposition')
10027db96d56Sopenharmony_ci        return False if c_d is None else c_d.content_disposition == 'attachment'
10037db96d56Sopenharmony_ci
10047db96d56Sopenharmony_ci    def _find_body(self, part, preferencelist):
10057db96d56Sopenharmony_ci        if part.is_attachment():
10067db96d56Sopenharmony_ci            return
10077db96d56Sopenharmony_ci        maintype, subtype = part.get_content_type().split('/')
10087db96d56Sopenharmony_ci        if maintype == 'text':
10097db96d56Sopenharmony_ci            if subtype in preferencelist:
10107db96d56Sopenharmony_ci                yield (preferencelist.index(subtype), part)
10117db96d56Sopenharmony_ci            return
10127db96d56Sopenharmony_ci        if maintype != 'multipart' or not self.is_multipart():
10137db96d56Sopenharmony_ci            return
10147db96d56Sopenharmony_ci        if subtype != 'related':
10157db96d56Sopenharmony_ci            for subpart in part.iter_parts():
10167db96d56Sopenharmony_ci                yield from self._find_body(subpart, preferencelist)
10177db96d56Sopenharmony_ci            return
10187db96d56Sopenharmony_ci        if 'related' in preferencelist:
10197db96d56Sopenharmony_ci            yield (preferencelist.index('related'), part)
10207db96d56Sopenharmony_ci        candidate = None
10217db96d56Sopenharmony_ci        start = part.get_param('start')
10227db96d56Sopenharmony_ci        if start:
10237db96d56Sopenharmony_ci            for subpart in part.iter_parts():
10247db96d56Sopenharmony_ci                if subpart['content-id'] == start:
10257db96d56Sopenharmony_ci                    candidate = subpart
10267db96d56Sopenharmony_ci                    break
10277db96d56Sopenharmony_ci        if candidate is None:
10287db96d56Sopenharmony_ci            subparts = part.get_payload()
10297db96d56Sopenharmony_ci            candidate = subparts[0] if subparts else None
10307db96d56Sopenharmony_ci        if candidate is not None:
10317db96d56Sopenharmony_ci            yield from self._find_body(candidate, preferencelist)
10327db96d56Sopenharmony_ci
10337db96d56Sopenharmony_ci    def get_body(self, preferencelist=('related', 'html', 'plain')):
10347db96d56Sopenharmony_ci        """Return best candidate mime part for display as 'body' of message.
10357db96d56Sopenharmony_ci
10367db96d56Sopenharmony_ci        Do a depth first search, starting with self, looking for the first part
10377db96d56Sopenharmony_ci        matching each of the items in preferencelist, and return the part
10387db96d56Sopenharmony_ci        corresponding to the first item that has a match, or None if no items
10397db96d56Sopenharmony_ci        have a match.  If 'related' is not included in preferencelist, consider
10407db96d56Sopenharmony_ci        the root part of any multipart/related encountered as a candidate
10417db96d56Sopenharmony_ci        match.  Ignore parts with 'Content-Disposition: attachment'.
10427db96d56Sopenharmony_ci        """
10437db96d56Sopenharmony_ci        best_prio = len(preferencelist)
10447db96d56Sopenharmony_ci        body = None
10457db96d56Sopenharmony_ci        for prio, part in self._find_body(self, preferencelist):
10467db96d56Sopenharmony_ci            if prio < best_prio:
10477db96d56Sopenharmony_ci                best_prio = prio
10487db96d56Sopenharmony_ci                body = part
10497db96d56Sopenharmony_ci                if prio == 0:
10507db96d56Sopenharmony_ci                    break
10517db96d56Sopenharmony_ci        return body
10527db96d56Sopenharmony_ci
10537db96d56Sopenharmony_ci    _body_types = {('text', 'plain'),
10547db96d56Sopenharmony_ci                   ('text', 'html'),
10557db96d56Sopenharmony_ci                   ('multipart', 'related'),
10567db96d56Sopenharmony_ci                   ('multipart', 'alternative')}
10577db96d56Sopenharmony_ci    def iter_attachments(self):
10587db96d56Sopenharmony_ci        """Return an iterator over the non-main parts of a multipart.
10597db96d56Sopenharmony_ci
10607db96d56Sopenharmony_ci        Skip the first of each occurrence of text/plain, text/html,
10617db96d56Sopenharmony_ci        multipart/related, or multipart/alternative in the multipart (unless
10627db96d56Sopenharmony_ci        they have a 'Content-Disposition: attachment' header) and include all
10637db96d56Sopenharmony_ci        remaining subparts in the returned iterator.  When applied to a
10647db96d56Sopenharmony_ci        multipart/related, return all parts except the root part.  Return an
10657db96d56Sopenharmony_ci        empty iterator when applied to a multipart/alternative or a
10667db96d56Sopenharmony_ci        non-multipart.
10677db96d56Sopenharmony_ci        """
10687db96d56Sopenharmony_ci        maintype, subtype = self.get_content_type().split('/')
10697db96d56Sopenharmony_ci        if maintype != 'multipart' or subtype == 'alternative':
10707db96d56Sopenharmony_ci            return
10717db96d56Sopenharmony_ci        payload = self.get_payload()
10727db96d56Sopenharmony_ci        # Certain malformed messages can have content type set to `multipart/*`
10737db96d56Sopenharmony_ci        # but still have single part body, in which case payload.copy() can
10747db96d56Sopenharmony_ci        # fail with AttributeError.
10757db96d56Sopenharmony_ci        try:
10767db96d56Sopenharmony_ci            parts = payload.copy()
10777db96d56Sopenharmony_ci        except AttributeError:
10787db96d56Sopenharmony_ci            # payload is not a list, it is most probably a string.
10797db96d56Sopenharmony_ci            return
10807db96d56Sopenharmony_ci
10817db96d56Sopenharmony_ci        if maintype == 'multipart' and subtype == 'related':
10827db96d56Sopenharmony_ci            # For related, we treat everything but the root as an attachment.
10837db96d56Sopenharmony_ci            # The root may be indicated by 'start'; if there's no start or we
10847db96d56Sopenharmony_ci            # can't find the named start, treat the first subpart as the root.
10857db96d56Sopenharmony_ci            start = self.get_param('start')
10867db96d56Sopenharmony_ci            if start:
10877db96d56Sopenharmony_ci                found = False
10887db96d56Sopenharmony_ci                attachments = []
10897db96d56Sopenharmony_ci                for part in parts:
10907db96d56Sopenharmony_ci                    if part.get('content-id') == start:
10917db96d56Sopenharmony_ci                        found = True
10927db96d56Sopenharmony_ci                    else:
10937db96d56Sopenharmony_ci                        attachments.append(part)
10947db96d56Sopenharmony_ci                if found:
10957db96d56Sopenharmony_ci                    yield from attachments
10967db96d56Sopenharmony_ci                    return
10977db96d56Sopenharmony_ci            parts.pop(0)
10987db96d56Sopenharmony_ci            yield from parts
10997db96d56Sopenharmony_ci            return
11007db96d56Sopenharmony_ci        # Otherwise we more or less invert the remaining logic in get_body.
11017db96d56Sopenharmony_ci        # This only really works in edge cases (ex: non-text related or
11027db96d56Sopenharmony_ci        # alternatives) if the sending agent sets content-disposition.
11037db96d56Sopenharmony_ci        seen = []   # Only skip the first example of each candidate type.
11047db96d56Sopenharmony_ci        for part in parts:
11057db96d56Sopenharmony_ci            maintype, subtype = part.get_content_type().split('/')
11067db96d56Sopenharmony_ci            if ((maintype, subtype) in self._body_types and
11077db96d56Sopenharmony_ci                    not part.is_attachment() and subtype not in seen):
11087db96d56Sopenharmony_ci                seen.append(subtype)
11097db96d56Sopenharmony_ci                continue
11107db96d56Sopenharmony_ci            yield part
11117db96d56Sopenharmony_ci
11127db96d56Sopenharmony_ci    def iter_parts(self):
11137db96d56Sopenharmony_ci        """Return an iterator over all immediate subparts of a multipart.
11147db96d56Sopenharmony_ci
11157db96d56Sopenharmony_ci        Return an empty iterator for a non-multipart.
11167db96d56Sopenharmony_ci        """
11177db96d56Sopenharmony_ci        if self.is_multipart():
11187db96d56Sopenharmony_ci            yield from self.get_payload()
11197db96d56Sopenharmony_ci
11207db96d56Sopenharmony_ci    def get_content(self, *args, content_manager=None, **kw):
11217db96d56Sopenharmony_ci        if content_manager is None:
11227db96d56Sopenharmony_ci            content_manager = self.policy.content_manager
11237db96d56Sopenharmony_ci        return content_manager.get_content(self, *args, **kw)
11247db96d56Sopenharmony_ci
11257db96d56Sopenharmony_ci    def set_content(self, *args, content_manager=None, **kw):
11267db96d56Sopenharmony_ci        if content_manager is None:
11277db96d56Sopenharmony_ci            content_manager = self.policy.content_manager
11287db96d56Sopenharmony_ci        content_manager.set_content(self, *args, **kw)
11297db96d56Sopenharmony_ci
11307db96d56Sopenharmony_ci    def _make_multipart(self, subtype, disallowed_subtypes, boundary):
11317db96d56Sopenharmony_ci        if self.get_content_maintype() == 'multipart':
11327db96d56Sopenharmony_ci            existing_subtype = self.get_content_subtype()
11337db96d56Sopenharmony_ci            disallowed_subtypes = disallowed_subtypes + (subtype,)
11347db96d56Sopenharmony_ci            if existing_subtype in disallowed_subtypes:
11357db96d56Sopenharmony_ci                raise ValueError("Cannot convert {} to {}".format(
11367db96d56Sopenharmony_ci                    existing_subtype, subtype))
11377db96d56Sopenharmony_ci        keep_headers = []
11387db96d56Sopenharmony_ci        part_headers = []
11397db96d56Sopenharmony_ci        for name, value in self._headers:
11407db96d56Sopenharmony_ci            if name.lower().startswith('content-'):
11417db96d56Sopenharmony_ci                part_headers.append((name, value))
11427db96d56Sopenharmony_ci            else:
11437db96d56Sopenharmony_ci                keep_headers.append((name, value))
11447db96d56Sopenharmony_ci        if part_headers:
11457db96d56Sopenharmony_ci            # There is existing content, move it to the first subpart.
11467db96d56Sopenharmony_ci            part = type(self)(policy=self.policy)
11477db96d56Sopenharmony_ci            part._headers = part_headers
11487db96d56Sopenharmony_ci            part._payload = self._payload
11497db96d56Sopenharmony_ci            self._payload = [part]
11507db96d56Sopenharmony_ci        else:
11517db96d56Sopenharmony_ci            self._payload = []
11527db96d56Sopenharmony_ci        self._headers = keep_headers
11537db96d56Sopenharmony_ci        self['Content-Type'] = 'multipart/' + subtype
11547db96d56Sopenharmony_ci        if boundary is not None:
11557db96d56Sopenharmony_ci            self.set_param('boundary', boundary)
11567db96d56Sopenharmony_ci
11577db96d56Sopenharmony_ci    def make_related(self, boundary=None):
11587db96d56Sopenharmony_ci        self._make_multipart('related', ('alternative', 'mixed'), boundary)
11597db96d56Sopenharmony_ci
11607db96d56Sopenharmony_ci    def make_alternative(self, boundary=None):
11617db96d56Sopenharmony_ci        self._make_multipart('alternative', ('mixed',), boundary)
11627db96d56Sopenharmony_ci
11637db96d56Sopenharmony_ci    def make_mixed(self, boundary=None):
11647db96d56Sopenharmony_ci        self._make_multipart('mixed', (), boundary)
11657db96d56Sopenharmony_ci
11667db96d56Sopenharmony_ci    def _add_multipart(self, _subtype, *args, _disp=None, **kw):
11677db96d56Sopenharmony_ci        if (self.get_content_maintype() != 'multipart' or
11687db96d56Sopenharmony_ci                self.get_content_subtype() != _subtype):
11697db96d56Sopenharmony_ci            getattr(self, 'make_' + _subtype)()
11707db96d56Sopenharmony_ci        part = type(self)(policy=self.policy)
11717db96d56Sopenharmony_ci        part.set_content(*args, **kw)
11727db96d56Sopenharmony_ci        if _disp and 'content-disposition' not in part:
11737db96d56Sopenharmony_ci            part['Content-Disposition'] = _disp
11747db96d56Sopenharmony_ci        self.attach(part)
11757db96d56Sopenharmony_ci
11767db96d56Sopenharmony_ci    def add_related(self, *args, **kw):
11777db96d56Sopenharmony_ci        self._add_multipart('related', *args, _disp='inline', **kw)
11787db96d56Sopenharmony_ci
11797db96d56Sopenharmony_ci    def add_alternative(self, *args, **kw):
11807db96d56Sopenharmony_ci        self._add_multipart('alternative', *args, **kw)
11817db96d56Sopenharmony_ci
11827db96d56Sopenharmony_ci    def add_attachment(self, *args, **kw):
11837db96d56Sopenharmony_ci        self._add_multipart('mixed', *args, _disp='attachment', **kw)
11847db96d56Sopenharmony_ci
11857db96d56Sopenharmony_ci    def clear(self):
11867db96d56Sopenharmony_ci        self._headers = []
11877db96d56Sopenharmony_ci        self._payload = None
11887db96d56Sopenharmony_ci
11897db96d56Sopenharmony_ci    def clear_content(self):
11907db96d56Sopenharmony_ci        self._headers = [(n, v) for n, v in self._headers
11917db96d56Sopenharmony_ci                         if not n.lower().startswith('content-')]
11927db96d56Sopenharmony_ci        self._payload = None
11937db96d56Sopenharmony_ci
11947db96d56Sopenharmony_ci
11957db96d56Sopenharmony_ciclass EmailMessage(MIMEPart):
11967db96d56Sopenharmony_ci
11977db96d56Sopenharmony_ci    def set_content(self, *args, **kw):
11987db96d56Sopenharmony_ci        super().set_content(*args, **kw)
11997db96d56Sopenharmony_ci        if 'MIME-Version' not in self:
12007db96d56Sopenharmony_ci            self['MIME-Version'] = '1.0'
1201