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