1*cda5da8dSAndroid Build Coastguard Worker# Copyright (C) 2001-2007 Python Software Foundation 2*cda5da8dSAndroid Build Coastguard Worker# Author: Barry Warsaw 3*cda5da8dSAndroid Build Coastguard Worker# Contact: [email protected] 4*cda5da8dSAndroid Build Coastguard Worker 5*cda5da8dSAndroid Build Coastguard Worker"""Basic message object for the email package object model.""" 6*cda5da8dSAndroid Build Coastguard Worker 7*cda5da8dSAndroid Build Coastguard Worker__all__ = ['Message', 'EmailMessage'] 8*cda5da8dSAndroid Build Coastguard Worker 9*cda5da8dSAndroid Build Coastguard Workerimport binascii 10*cda5da8dSAndroid Build Coastguard Workerimport re 11*cda5da8dSAndroid Build Coastguard Workerimport quopri 12*cda5da8dSAndroid Build Coastguard Workerfrom io import BytesIO, StringIO 13*cda5da8dSAndroid Build Coastguard Worker 14*cda5da8dSAndroid Build Coastguard Worker# Intrapackage imports 15*cda5da8dSAndroid Build Coastguard Workerfrom email import utils 16*cda5da8dSAndroid Build Coastguard Workerfrom email import errors 17*cda5da8dSAndroid Build Coastguard Workerfrom email._policybase import Policy, compat32 18*cda5da8dSAndroid Build Coastguard Workerfrom email import charset as _charset 19*cda5da8dSAndroid Build Coastguard Workerfrom email._encoded_words import decode_b 20*cda5da8dSAndroid Build Coastguard WorkerCharset = _charset.Charset 21*cda5da8dSAndroid Build Coastguard Worker 22*cda5da8dSAndroid Build Coastguard WorkerSEMISPACE = '; ' 23*cda5da8dSAndroid Build Coastguard Worker 24*cda5da8dSAndroid Build Coastguard Worker# Regular expression that matches `special' characters in parameters, the 25*cda5da8dSAndroid Build Coastguard Worker# existence of which force quoting of the parameter value. 26*cda5da8dSAndroid Build Coastguard Workertspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') 27*cda5da8dSAndroid Build Coastguard Worker 28*cda5da8dSAndroid Build Coastguard Worker 29*cda5da8dSAndroid Build Coastguard Workerdef _splitparam(param): 30*cda5da8dSAndroid Build Coastguard Worker # Split header parameters. BAW: this may be too simple. It isn't 31*cda5da8dSAndroid Build Coastguard Worker # strictly RFC 2045 (section 5.1) compliant, but it catches most headers 32*cda5da8dSAndroid Build Coastguard Worker # found in the wild. We may eventually need a full fledged parser. 33*cda5da8dSAndroid Build Coastguard Worker # RDM: we might have a Header here; for now just stringify it. 34*cda5da8dSAndroid Build Coastguard Worker a, sep, b = str(param).partition(';') 35*cda5da8dSAndroid Build Coastguard Worker if not sep: 36*cda5da8dSAndroid Build Coastguard Worker return a.strip(), None 37*cda5da8dSAndroid Build Coastguard Worker return a.strip(), b.strip() 38*cda5da8dSAndroid Build Coastguard Worker 39*cda5da8dSAndroid Build Coastguard Workerdef _formatparam(param, value=None, quote=True): 40*cda5da8dSAndroid Build Coastguard Worker """Convenience function to format and return a key=value pair. 41*cda5da8dSAndroid Build Coastguard Worker 42*cda5da8dSAndroid Build Coastguard Worker This will quote the value if needed or if quote is true. If value is a 43*cda5da8dSAndroid Build Coastguard Worker three tuple (charset, language, value), it will be encoded according 44*cda5da8dSAndroid Build Coastguard Worker to RFC2231 rules. If it contains non-ascii characters it will likewise 45*cda5da8dSAndroid Build Coastguard Worker be encoded according to RFC2231 rules, using the utf-8 charset and 46*cda5da8dSAndroid Build Coastguard Worker a null language. 47*cda5da8dSAndroid Build Coastguard Worker """ 48*cda5da8dSAndroid Build Coastguard Worker if value is not None and len(value) > 0: 49*cda5da8dSAndroid Build Coastguard Worker # A tuple is used for RFC 2231 encoded parameter values where items 50*cda5da8dSAndroid Build Coastguard Worker # are (charset, language, value). charset is a string, not a Charset 51*cda5da8dSAndroid Build Coastguard Worker # instance. RFC 2231 encoded values are never quoted, per RFC. 52*cda5da8dSAndroid Build Coastguard Worker if isinstance(value, tuple): 53*cda5da8dSAndroid Build Coastguard Worker # Encode as per RFC 2231 54*cda5da8dSAndroid Build Coastguard Worker param += '*' 55*cda5da8dSAndroid Build Coastguard Worker value = utils.encode_rfc2231(value[2], value[0], value[1]) 56*cda5da8dSAndroid Build Coastguard Worker return '%s=%s' % (param, value) 57*cda5da8dSAndroid Build Coastguard Worker else: 58*cda5da8dSAndroid Build Coastguard Worker try: 59*cda5da8dSAndroid Build Coastguard Worker value.encode('ascii') 60*cda5da8dSAndroid Build Coastguard Worker except UnicodeEncodeError: 61*cda5da8dSAndroid Build Coastguard Worker param += '*' 62*cda5da8dSAndroid Build Coastguard Worker value = utils.encode_rfc2231(value, 'utf-8', '') 63*cda5da8dSAndroid Build Coastguard Worker return '%s=%s' % (param, value) 64*cda5da8dSAndroid Build Coastguard Worker # BAW: Please check this. I think that if quote is set it should 65*cda5da8dSAndroid Build Coastguard Worker # force quoting even if not necessary. 66*cda5da8dSAndroid Build Coastguard Worker if quote or tspecials.search(value): 67*cda5da8dSAndroid Build Coastguard Worker return '%s="%s"' % (param, utils.quote(value)) 68*cda5da8dSAndroid Build Coastguard Worker else: 69*cda5da8dSAndroid Build Coastguard Worker return '%s=%s' % (param, value) 70*cda5da8dSAndroid Build Coastguard Worker else: 71*cda5da8dSAndroid Build Coastguard Worker return param 72*cda5da8dSAndroid Build Coastguard Worker 73*cda5da8dSAndroid Build Coastguard Workerdef _parseparam(s): 74*cda5da8dSAndroid Build Coastguard Worker # RDM This might be a Header, so for now stringify it. 75*cda5da8dSAndroid Build Coastguard Worker s = ';' + str(s) 76*cda5da8dSAndroid Build Coastguard Worker plist = [] 77*cda5da8dSAndroid Build Coastguard Worker while s[:1] == ';': 78*cda5da8dSAndroid Build Coastguard Worker s = s[1:] 79*cda5da8dSAndroid Build Coastguard Worker end = s.find(';') 80*cda5da8dSAndroid Build Coastguard Worker while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2: 81*cda5da8dSAndroid Build Coastguard Worker end = s.find(';', end + 1) 82*cda5da8dSAndroid Build Coastguard Worker if end < 0: 83*cda5da8dSAndroid Build Coastguard Worker end = len(s) 84*cda5da8dSAndroid Build Coastguard Worker f = s[:end] 85*cda5da8dSAndroid Build Coastguard Worker if '=' in f: 86*cda5da8dSAndroid Build Coastguard Worker i = f.index('=') 87*cda5da8dSAndroid Build Coastguard Worker f = f[:i].strip().lower() + '=' + f[i+1:].strip() 88*cda5da8dSAndroid Build Coastguard Worker plist.append(f.strip()) 89*cda5da8dSAndroid Build Coastguard Worker s = s[end:] 90*cda5da8dSAndroid Build Coastguard Worker return plist 91*cda5da8dSAndroid Build Coastguard Worker 92*cda5da8dSAndroid Build Coastguard Worker 93*cda5da8dSAndroid Build Coastguard Workerdef _unquotevalue(value): 94*cda5da8dSAndroid Build Coastguard Worker # This is different than utils.collapse_rfc2231_value() because it doesn't 95*cda5da8dSAndroid Build Coastguard Worker # try to convert the value to a unicode. Message.get_param() and 96*cda5da8dSAndroid Build Coastguard Worker # Message.get_params() are both currently defined to return the tuple in 97*cda5da8dSAndroid Build Coastguard Worker # the face of RFC 2231 parameters. 98*cda5da8dSAndroid Build Coastguard Worker if isinstance(value, tuple): 99*cda5da8dSAndroid Build Coastguard Worker return value[0], value[1], utils.unquote(value[2]) 100*cda5da8dSAndroid Build Coastguard Worker else: 101*cda5da8dSAndroid Build Coastguard Worker return utils.unquote(value) 102*cda5da8dSAndroid Build Coastguard Worker 103*cda5da8dSAndroid Build Coastguard Worker 104*cda5da8dSAndroid Build Coastguard Workerdef _decode_uu(encoded): 105*cda5da8dSAndroid Build Coastguard Worker """Decode uuencoded data.""" 106*cda5da8dSAndroid Build Coastguard Worker decoded_lines = [] 107*cda5da8dSAndroid Build Coastguard Worker encoded_lines_iter = iter(encoded.splitlines()) 108*cda5da8dSAndroid Build Coastguard Worker for line in encoded_lines_iter: 109*cda5da8dSAndroid Build Coastguard Worker if line.startswith(b"begin "): 110*cda5da8dSAndroid Build Coastguard Worker mode, _, path = line.removeprefix(b"begin ").partition(b" ") 111*cda5da8dSAndroid Build Coastguard Worker try: 112*cda5da8dSAndroid Build Coastguard Worker int(mode, base=8) 113*cda5da8dSAndroid Build Coastguard Worker except ValueError: 114*cda5da8dSAndroid Build Coastguard Worker continue 115*cda5da8dSAndroid Build Coastguard Worker else: 116*cda5da8dSAndroid Build Coastguard Worker break 117*cda5da8dSAndroid Build Coastguard Worker else: 118*cda5da8dSAndroid Build Coastguard Worker raise ValueError("`begin` line not found") 119*cda5da8dSAndroid Build Coastguard Worker for line in encoded_lines_iter: 120*cda5da8dSAndroid Build Coastguard Worker if not line: 121*cda5da8dSAndroid Build Coastguard Worker raise ValueError("Truncated input") 122*cda5da8dSAndroid Build Coastguard Worker elif line.strip(b' \t\r\n\f') == b'end': 123*cda5da8dSAndroid Build Coastguard Worker break 124*cda5da8dSAndroid Build Coastguard Worker try: 125*cda5da8dSAndroid Build Coastguard Worker decoded_line = binascii.a2b_uu(line) 126*cda5da8dSAndroid Build Coastguard Worker except binascii.Error: 127*cda5da8dSAndroid Build Coastguard Worker # Workaround for broken uuencoders by /Fredrik Lundh 128*cda5da8dSAndroid Build Coastguard Worker nbytes = (((line[0]-32) & 63) * 4 + 5) // 3 129*cda5da8dSAndroid Build Coastguard Worker decoded_line = binascii.a2b_uu(line[:nbytes]) 130*cda5da8dSAndroid Build Coastguard Worker decoded_lines.append(decoded_line) 131*cda5da8dSAndroid Build Coastguard Worker 132*cda5da8dSAndroid Build Coastguard Worker return b''.join(decoded_lines) 133*cda5da8dSAndroid Build Coastguard Worker 134*cda5da8dSAndroid Build Coastguard Worker 135*cda5da8dSAndroid Build Coastguard Workerclass Message: 136*cda5da8dSAndroid Build Coastguard Worker """Basic message object. 137*cda5da8dSAndroid Build Coastguard Worker 138*cda5da8dSAndroid Build Coastguard Worker A message object is defined as something that has a bunch of RFC 2822 139*cda5da8dSAndroid Build Coastguard Worker headers and a payload. It may optionally have an envelope header 140*cda5da8dSAndroid Build Coastguard Worker (a.k.a. Unix-From or From_ header). If the message is a container (i.e. a 141*cda5da8dSAndroid Build Coastguard Worker multipart or a message/rfc822), then the payload is a list of Message 142*cda5da8dSAndroid Build Coastguard Worker objects, otherwise it is a string. 143*cda5da8dSAndroid Build Coastguard Worker 144*cda5da8dSAndroid Build Coastguard Worker Message objects implement part of the `mapping' interface, which assumes 145*cda5da8dSAndroid Build Coastguard Worker there is exactly one occurrence of the header per message. Some headers 146*cda5da8dSAndroid Build Coastguard Worker do in fact appear multiple times (e.g. Received) and for those headers, 147*cda5da8dSAndroid Build Coastguard Worker you must use the explicit API to set or get all the headers. Not all of 148*cda5da8dSAndroid Build Coastguard Worker the mapping methods are implemented. 149*cda5da8dSAndroid Build Coastguard Worker """ 150*cda5da8dSAndroid Build Coastguard Worker def __init__(self, policy=compat32): 151*cda5da8dSAndroid Build Coastguard Worker self.policy = policy 152*cda5da8dSAndroid Build Coastguard Worker self._headers = [] 153*cda5da8dSAndroid Build Coastguard Worker self._unixfrom = None 154*cda5da8dSAndroid Build Coastguard Worker self._payload = None 155*cda5da8dSAndroid Build Coastguard Worker self._charset = None 156*cda5da8dSAndroid Build Coastguard Worker # Defaults for multipart messages 157*cda5da8dSAndroid Build Coastguard Worker self.preamble = self.epilogue = None 158*cda5da8dSAndroid Build Coastguard Worker self.defects = [] 159*cda5da8dSAndroid Build Coastguard Worker # Default content type 160*cda5da8dSAndroid Build Coastguard Worker self._default_type = 'text/plain' 161*cda5da8dSAndroid Build Coastguard Worker 162*cda5da8dSAndroid Build Coastguard Worker def __str__(self): 163*cda5da8dSAndroid Build Coastguard Worker """Return the entire formatted message as a string. 164*cda5da8dSAndroid Build Coastguard Worker """ 165*cda5da8dSAndroid Build Coastguard Worker return self.as_string() 166*cda5da8dSAndroid Build Coastguard Worker 167*cda5da8dSAndroid Build Coastguard Worker def as_string(self, unixfrom=False, maxheaderlen=0, policy=None): 168*cda5da8dSAndroid Build Coastguard Worker """Return the entire formatted message as a string. 169*cda5da8dSAndroid Build Coastguard Worker 170*cda5da8dSAndroid Build Coastguard Worker Optional 'unixfrom', when true, means include the Unix From_ envelope 171*cda5da8dSAndroid Build Coastguard Worker header. For backward compatibility reasons, if maxheaderlen is 172*cda5da8dSAndroid Build Coastguard Worker not specified it defaults to 0, so you must override it explicitly 173*cda5da8dSAndroid Build Coastguard Worker if you want a different maxheaderlen. 'policy' is passed to the 174*cda5da8dSAndroid Build Coastguard Worker Generator instance used to serialize the message; if it is not 175*cda5da8dSAndroid Build Coastguard Worker specified the policy associated with the message instance is used. 176*cda5da8dSAndroid Build Coastguard Worker 177*cda5da8dSAndroid Build Coastguard Worker If the message object contains binary data that is not encoded 178*cda5da8dSAndroid Build Coastguard Worker according to RFC standards, the non-compliant data will be replaced by 179*cda5da8dSAndroid Build Coastguard Worker unicode "unknown character" code points. 180*cda5da8dSAndroid Build Coastguard Worker """ 181*cda5da8dSAndroid Build Coastguard Worker from email.generator import Generator 182*cda5da8dSAndroid Build Coastguard Worker policy = self.policy if policy is None else policy 183*cda5da8dSAndroid Build Coastguard Worker fp = StringIO() 184*cda5da8dSAndroid Build Coastguard Worker g = Generator(fp, 185*cda5da8dSAndroid Build Coastguard Worker mangle_from_=False, 186*cda5da8dSAndroid Build Coastguard Worker maxheaderlen=maxheaderlen, 187*cda5da8dSAndroid Build Coastguard Worker policy=policy) 188*cda5da8dSAndroid Build Coastguard Worker g.flatten(self, unixfrom=unixfrom) 189*cda5da8dSAndroid Build Coastguard Worker return fp.getvalue() 190*cda5da8dSAndroid Build Coastguard Worker 191*cda5da8dSAndroid Build Coastguard Worker def __bytes__(self): 192*cda5da8dSAndroid Build Coastguard Worker """Return the entire formatted message as a bytes object. 193*cda5da8dSAndroid Build Coastguard Worker """ 194*cda5da8dSAndroid Build Coastguard Worker return self.as_bytes() 195*cda5da8dSAndroid Build Coastguard Worker 196*cda5da8dSAndroid Build Coastguard Worker def as_bytes(self, unixfrom=False, policy=None): 197*cda5da8dSAndroid Build Coastguard Worker """Return the entire formatted message as a bytes object. 198*cda5da8dSAndroid Build Coastguard Worker 199*cda5da8dSAndroid Build Coastguard Worker Optional 'unixfrom', when true, means include the Unix From_ envelope 200*cda5da8dSAndroid Build Coastguard Worker header. 'policy' is passed to the BytesGenerator instance used to 201*cda5da8dSAndroid Build Coastguard Worker serialize the message; if not specified the policy associated with 202*cda5da8dSAndroid Build Coastguard Worker the message instance is used. 203*cda5da8dSAndroid Build Coastguard Worker """ 204*cda5da8dSAndroid Build Coastguard Worker from email.generator import BytesGenerator 205*cda5da8dSAndroid Build Coastguard Worker policy = self.policy if policy is None else policy 206*cda5da8dSAndroid Build Coastguard Worker fp = BytesIO() 207*cda5da8dSAndroid Build Coastguard Worker g = BytesGenerator(fp, mangle_from_=False, policy=policy) 208*cda5da8dSAndroid Build Coastguard Worker g.flatten(self, unixfrom=unixfrom) 209*cda5da8dSAndroid Build Coastguard Worker return fp.getvalue() 210*cda5da8dSAndroid Build Coastguard Worker 211*cda5da8dSAndroid Build Coastguard Worker def is_multipart(self): 212*cda5da8dSAndroid Build Coastguard Worker """Return True if the message consists of multiple parts.""" 213*cda5da8dSAndroid Build Coastguard Worker return isinstance(self._payload, list) 214*cda5da8dSAndroid Build Coastguard Worker 215*cda5da8dSAndroid Build Coastguard Worker # 216*cda5da8dSAndroid Build Coastguard Worker # Unix From_ line 217*cda5da8dSAndroid Build Coastguard Worker # 218*cda5da8dSAndroid Build Coastguard Worker def set_unixfrom(self, unixfrom): 219*cda5da8dSAndroid Build Coastguard Worker self._unixfrom = unixfrom 220*cda5da8dSAndroid Build Coastguard Worker 221*cda5da8dSAndroid Build Coastguard Worker def get_unixfrom(self): 222*cda5da8dSAndroid Build Coastguard Worker return self._unixfrom 223*cda5da8dSAndroid Build Coastguard Worker 224*cda5da8dSAndroid Build Coastguard Worker # 225*cda5da8dSAndroid Build Coastguard Worker # Payload manipulation. 226*cda5da8dSAndroid Build Coastguard Worker # 227*cda5da8dSAndroid Build Coastguard Worker def attach(self, payload): 228*cda5da8dSAndroid Build Coastguard Worker """Add the given payload to the current payload. 229*cda5da8dSAndroid Build Coastguard Worker 230*cda5da8dSAndroid Build Coastguard Worker The current payload will always be a list of objects after this method 231*cda5da8dSAndroid Build Coastguard Worker is called. If you want to set the payload to a scalar object, use 232*cda5da8dSAndroid Build Coastguard Worker set_payload() instead. 233*cda5da8dSAndroid Build Coastguard Worker """ 234*cda5da8dSAndroid Build Coastguard Worker if self._payload is None: 235*cda5da8dSAndroid Build Coastguard Worker self._payload = [payload] 236*cda5da8dSAndroid Build Coastguard Worker else: 237*cda5da8dSAndroid Build Coastguard Worker try: 238*cda5da8dSAndroid Build Coastguard Worker self._payload.append(payload) 239*cda5da8dSAndroid Build Coastguard Worker except AttributeError: 240*cda5da8dSAndroid Build Coastguard Worker raise TypeError("Attach is not valid on a message with a" 241*cda5da8dSAndroid Build Coastguard Worker " non-multipart payload") 242*cda5da8dSAndroid Build Coastguard Worker 243*cda5da8dSAndroid Build Coastguard Worker def get_payload(self, i=None, decode=False): 244*cda5da8dSAndroid Build Coastguard Worker """Return a reference to the payload. 245*cda5da8dSAndroid Build Coastguard Worker 246*cda5da8dSAndroid Build Coastguard Worker The payload will either be a list object or a string. If you mutate 247*cda5da8dSAndroid Build Coastguard Worker the list object, you modify the message's payload in place. Optional 248*cda5da8dSAndroid Build Coastguard Worker i returns that index into the payload. 249*cda5da8dSAndroid Build Coastguard Worker 250*cda5da8dSAndroid Build Coastguard Worker Optional decode is a flag indicating whether the payload should be 251*cda5da8dSAndroid Build Coastguard Worker decoded or not, according to the Content-Transfer-Encoding header 252*cda5da8dSAndroid Build Coastguard Worker (default is False). 253*cda5da8dSAndroid Build Coastguard Worker 254*cda5da8dSAndroid Build Coastguard Worker When True and the message is not a multipart, the payload will be 255*cda5da8dSAndroid Build Coastguard Worker decoded if this header's value is `quoted-printable' or `base64'. If 256*cda5da8dSAndroid Build Coastguard Worker some other encoding is used, or the header is missing, or if the 257*cda5da8dSAndroid Build Coastguard Worker payload has bogus data (i.e. bogus base64 or uuencoded data), the 258*cda5da8dSAndroid Build Coastguard Worker payload is returned as-is. 259*cda5da8dSAndroid Build Coastguard Worker 260*cda5da8dSAndroid Build Coastguard Worker If the message is a multipart and the decode flag is True, then None 261*cda5da8dSAndroid Build Coastguard Worker is returned. 262*cda5da8dSAndroid Build Coastguard Worker """ 263*cda5da8dSAndroid Build Coastguard Worker # Here is the logic table for this code, based on the email5.0.0 code: 264*cda5da8dSAndroid Build Coastguard Worker # i decode is_multipart result 265*cda5da8dSAndroid Build Coastguard Worker # ------ ------ ------------ ------------------------------ 266*cda5da8dSAndroid Build Coastguard Worker # None True True None 267*cda5da8dSAndroid Build Coastguard Worker # i True True None 268*cda5da8dSAndroid Build Coastguard Worker # None False True _payload (a list) 269*cda5da8dSAndroid Build Coastguard Worker # i False True _payload element i (a Message) 270*cda5da8dSAndroid Build Coastguard Worker # i False False error (not a list) 271*cda5da8dSAndroid Build Coastguard Worker # i True False error (not a list) 272*cda5da8dSAndroid Build Coastguard Worker # None False False _payload 273*cda5da8dSAndroid Build Coastguard Worker # None True False _payload decoded (bytes) 274*cda5da8dSAndroid Build Coastguard Worker # Note that Barry planned to factor out the 'decode' case, but that 275*cda5da8dSAndroid Build Coastguard Worker # isn't so easy now that we handle the 8 bit data, which needs to be 276*cda5da8dSAndroid Build Coastguard Worker # converted in both the decode and non-decode path. 277*cda5da8dSAndroid Build Coastguard Worker if self.is_multipart(): 278*cda5da8dSAndroid Build Coastguard Worker if decode: 279*cda5da8dSAndroid Build Coastguard Worker return None 280*cda5da8dSAndroid Build Coastguard Worker if i is None: 281*cda5da8dSAndroid Build Coastguard Worker return self._payload 282*cda5da8dSAndroid Build Coastguard Worker else: 283*cda5da8dSAndroid Build Coastguard Worker return self._payload[i] 284*cda5da8dSAndroid Build Coastguard Worker # For backward compatibility, Use isinstance and this error message 285*cda5da8dSAndroid Build Coastguard Worker # instead of the more logical is_multipart test. 286*cda5da8dSAndroid Build Coastguard Worker if i is not None and not isinstance(self._payload, list): 287*cda5da8dSAndroid Build Coastguard Worker raise TypeError('Expected list, got %s' % type(self._payload)) 288*cda5da8dSAndroid Build Coastguard Worker payload = self._payload 289*cda5da8dSAndroid Build Coastguard Worker # cte might be a Header, so for now stringify it. 290*cda5da8dSAndroid Build Coastguard Worker cte = str(self.get('content-transfer-encoding', '')).lower() 291*cda5da8dSAndroid Build Coastguard Worker # payload may be bytes here. 292*cda5da8dSAndroid Build Coastguard Worker if isinstance(payload, str): 293*cda5da8dSAndroid Build Coastguard Worker if utils._has_surrogates(payload): 294*cda5da8dSAndroid Build Coastguard Worker bpayload = payload.encode('ascii', 'surrogateescape') 295*cda5da8dSAndroid Build Coastguard Worker if not decode: 296*cda5da8dSAndroid Build Coastguard Worker try: 297*cda5da8dSAndroid Build Coastguard Worker payload = bpayload.decode(self.get_param('charset', 'ascii'), 'replace') 298*cda5da8dSAndroid Build Coastguard Worker except LookupError: 299*cda5da8dSAndroid Build Coastguard Worker payload = bpayload.decode('ascii', 'replace') 300*cda5da8dSAndroid Build Coastguard Worker elif decode: 301*cda5da8dSAndroid Build Coastguard Worker try: 302*cda5da8dSAndroid Build Coastguard Worker bpayload = payload.encode('ascii') 303*cda5da8dSAndroid Build Coastguard Worker except UnicodeError: 304*cda5da8dSAndroid Build Coastguard Worker # This won't happen for RFC compliant messages (messages 305*cda5da8dSAndroid Build Coastguard Worker # containing only ASCII code points in the unicode input). 306*cda5da8dSAndroid Build Coastguard Worker # If it does happen, turn the string into bytes in a way 307*cda5da8dSAndroid Build Coastguard Worker # guaranteed not to fail. 308*cda5da8dSAndroid Build Coastguard Worker bpayload = payload.encode('raw-unicode-escape') 309*cda5da8dSAndroid Build Coastguard Worker if not decode: 310*cda5da8dSAndroid Build Coastguard Worker return payload 311*cda5da8dSAndroid Build Coastguard Worker if cte == 'quoted-printable': 312*cda5da8dSAndroid Build Coastguard Worker return quopri.decodestring(bpayload) 313*cda5da8dSAndroid Build Coastguard Worker elif cte == 'base64': 314*cda5da8dSAndroid Build Coastguard Worker # XXX: this is a bit of a hack; decode_b should probably be factored 315*cda5da8dSAndroid Build Coastguard Worker # out somewhere, but I haven't figured out where yet. 316*cda5da8dSAndroid Build Coastguard Worker value, defects = decode_b(b''.join(bpayload.splitlines())) 317*cda5da8dSAndroid Build Coastguard Worker for defect in defects: 318*cda5da8dSAndroid Build Coastguard Worker self.policy.handle_defect(self, defect) 319*cda5da8dSAndroid Build Coastguard Worker return value 320*cda5da8dSAndroid Build Coastguard Worker elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'): 321*cda5da8dSAndroid Build Coastguard Worker try: 322*cda5da8dSAndroid Build Coastguard Worker return _decode_uu(bpayload) 323*cda5da8dSAndroid Build Coastguard Worker except ValueError: 324*cda5da8dSAndroid Build Coastguard Worker # Some decoding problem. 325*cda5da8dSAndroid Build Coastguard Worker return bpayload 326*cda5da8dSAndroid Build Coastguard Worker if isinstance(payload, str): 327*cda5da8dSAndroid Build Coastguard Worker return bpayload 328*cda5da8dSAndroid Build Coastguard Worker return payload 329*cda5da8dSAndroid Build Coastguard Worker 330*cda5da8dSAndroid Build Coastguard Worker def set_payload(self, payload, charset=None): 331*cda5da8dSAndroid Build Coastguard Worker """Set the payload to the given value. 332*cda5da8dSAndroid Build Coastguard Worker 333*cda5da8dSAndroid Build Coastguard Worker Optional charset sets the message's default character set. See 334*cda5da8dSAndroid Build Coastguard Worker set_charset() for details. 335*cda5da8dSAndroid Build Coastguard Worker """ 336*cda5da8dSAndroid Build Coastguard Worker if hasattr(payload, 'encode'): 337*cda5da8dSAndroid Build Coastguard Worker if charset is None: 338*cda5da8dSAndroid Build Coastguard Worker self._payload = payload 339*cda5da8dSAndroid Build Coastguard Worker return 340*cda5da8dSAndroid Build Coastguard Worker if not isinstance(charset, Charset): 341*cda5da8dSAndroid Build Coastguard Worker charset = Charset(charset) 342*cda5da8dSAndroid Build Coastguard Worker payload = payload.encode(charset.output_charset) 343*cda5da8dSAndroid Build Coastguard Worker if hasattr(payload, 'decode'): 344*cda5da8dSAndroid Build Coastguard Worker self._payload = payload.decode('ascii', 'surrogateescape') 345*cda5da8dSAndroid Build Coastguard Worker else: 346*cda5da8dSAndroid Build Coastguard Worker self._payload = payload 347*cda5da8dSAndroid Build Coastguard Worker if charset is not None: 348*cda5da8dSAndroid Build Coastguard Worker self.set_charset(charset) 349*cda5da8dSAndroid Build Coastguard Worker 350*cda5da8dSAndroid Build Coastguard Worker def set_charset(self, charset): 351*cda5da8dSAndroid Build Coastguard Worker """Set the charset of the payload to a given character set. 352*cda5da8dSAndroid Build Coastguard Worker 353*cda5da8dSAndroid Build Coastguard Worker charset can be a Charset instance, a string naming a character set, or 354*cda5da8dSAndroid Build Coastguard Worker None. If it is a string it will be converted to a Charset instance. 355*cda5da8dSAndroid Build Coastguard Worker If charset is None, the charset parameter will be removed from the 356*cda5da8dSAndroid Build Coastguard Worker Content-Type field. Anything else will generate a TypeError. 357*cda5da8dSAndroid Build Coastguard Worker 358*cda5da8dSAndroid Build Coastguard Worker The message will be assumed to be of type text/* encoded with 359*cda5da8dSAndroid Build Coastguard Worker charset.input_charset. It will be converted to charset.output_charset 360*cda5da8dSAndroid Build Coastguard Worker and encoded properly, if needed, when generating the plain text 361*cda5da8dSAndroid Build Coastguard Worker representation of the message. MIME headers (MIME-Version, 362*cda5da8dSAndroid Build Coastguard Worker Content-Type, Content-Transfer-Encoding) will be added as needed. 363*cda5da8dSAndroid Build Coastguard Worker """ 364*cda5da8dSAndroid Build Coastguard Worker if charset is None: 365*cda5da8dSAndroid Build Coastguard Worker self.del_param('charset') 366*cda5da8dSAndroid Build Coastguard Worker self._charset = None 367*cda5da8dSAndroid Build Coastguard Worker return 368*cda5da8dSAndroid Build Coastguard Worker if not isinstance(charset, Charset): 369*cda5da8dSAndroid Build Coastguard Worker charset = Charset(charset) 370*cda5da8dSAndroid Build Coastguard Worker self._charset = charset 371*cda5da8dSAndroid Build Coastguard Worker if 'MIME-Version' not in self: 372*cda5da8dSAndroid Build Coastguard Worker self.add_header('MIME-Version', '1.0') 373*cda5da8dSAndroid Build Coastguard Worker if 'Content-Type' not in self: 374*cda5da8dSAndroid Build Coastguard Worker self.add_header('Content-Type', 'text/plain', 375*cda5da8dSAndroid Build Coastguard Worker charset=charset.get_output_charset()) 376*cda5da8dSAndroid Build Coastguard Worker else: 377*cda5da8dSAndroid Build Coastguard Worker self.set_param('charset', charset.get_output_charset()) 378*cda5da8dSAndroid Build Coastguard Worker if charset != charset.get_output_charset(): 379*cda5da8dSAndroid Build Coastguard Worker self._payload = charset.body_encode(self._payload) 380*cda5da8dSAndroid Build Coastguard Worker if 'Content-Transfer-Encoding' not in self: 381*cda5da8dSAndroid Build Coastguard Worker cte = charset.get_body_encoding() 382*cda5da8dSAndroid Build Coastguard Worker try: 383*cda5da8dSAndroid Build Coastguard Worker cte(self) 384*cda5da8dSAndroid Build Coastguard Worker except TypeError: 385*cda5da8dSAndroid Build Coastguard Worker # This 'if' is for backward compatibility, it allows unicode 386*cda5da8dSAndroid Build Coastguard Worker # through even though that won't work correctly if the 387*cda5da8dSAndroid Build Coastguard Worker # message is serialized. 388*cda5da8dSAndroid Build Coastguard Worker payload = self._payload 389*cda5da8dSAndroid Build Coastguard Worker if payload: 390*cda5da8dSAndroid Build Coastguard Worker try: 391*cda5da8dSAndroid Build Coastguard Worker payload = payload.encode('ascii', 'surrogateescape') 392*cda5da8dSAndroid Build Coastguard Worker except UnicodeError: 393*cda5da8dSAndroid Build Coastguard Worker payload = payload.encode(charset.output_charset) 394*cda5da8dSAndroid Build Coastguard Worker self._payload = charset.body_encode(payload) 395*cda5da8dSAndroid Build Coastguard Worker self.add_header('Content-Transfer-Encoding', cte) 396*cda5da8dSAndroid Build Coastguard Worker 397*cda5da8dSAndroid Build Coastguard Worker def get_charset(self): 398*cda5da8dSAndroid Build Coastguard Worker """Return the Charset instance associated with the message's payload. 399*cda5da8dSAndroid Build Coastguard Worker """ 400*cda5da8dSAndroid Build Coastguard Worker return self._charset 401*cda5da8dSAndroid Build Coastguard Worker 402*cda5da8dSAndroid Build Coastguard Worker # 403*cda5da8dSAndroid Build Coastguard Worker # MAPPING INTERFACE (partial) 404*cda5da8dSAndroid Build Coastguard Worker # 405*cda5da8dSAndroid Build Coastguard Worker def __len__(self): 406*cda5da8dSAndroid Build Coastguard Worker """Return the total number of headers, including duplicates.""" 407*cda5da8dSAndroid Build Coastguard Worker return len(self._headers) 408*cda5da8dSAndroid Build Coastguard Worker 409*cda5da8dSAndroid Build Coastguard Worker def __getitem__(self, name): 410*cda5da8dSAndroid Build Coastguard Worker """Get a header value. 411*cda5da8dSAndroid Build Coastguard Worker 412*cda5da8dSAndroid Build Coastguard Worker Return None if the header is missing instead of raising an exception. 413*cda5da8dSAndroid Build Coastguard Worker 414*cda5da8dSAndroid Build Coastguard Worker Note that if the header appeared multiple times, exactly which 415*cda5da8dSAndroid Build Coastguard Worker occurrence gets returned is undefined. Use get_all() to get all 416*cda5da8dSAndroid Build Coastguard Worker the values matching a header field name. 417*cda5da8dSAndroid Build Coastguard Worker """ 418*cda5da8dSAndroid Build Coastguard Worker return self.get(name) 419*cda5da8dSAndroid Build Coastguard Worker 420*cda5da8dSAndroid Build Coastguard Worker def __setitem__(self, name, val): 421*cda5da8dSAndroid Build Coastguard Worker """Set the value of a header. 422*cda5da8dSAndroid Build Coastguard Worker 423*cda5da8dSAndroid Build Coastguard Worker Note: this does not overwrite an existing header with the same field 424*cda5da8dSAndroid Build Coastguard Worker name. Use __delitem__() first to delete any existing headers. 425*cda5da8dSAndroid Build Coastguard Worker """ 426*cda5da8dSAndroid Build Coastguard Worker max_count = self.policy.header_max_count(name) 427*cda5da8dSAndroid Build Coastguard Worker if max_count: 428*cda5da8dSAndroid Build Coastguard Worker lname = name.lower() 429*cda5da8dSAndroid Build Coastguard Worker found = 0 430*cda5da8dSAndroid Build Coastguard Worker for k, v in self._headers: 431*cda5da8dSAndroid Build Coastguard Worker if k.lower() == lname: 432*cda5da8dSAndroid Build Coastguard Worker found += 1 433*cda5da8dSAndroid Build Coastguard Worker if found >= max_count: 434*cda5da8dSAndroid Build Coastguard Worker raise ValueError("There may be at most {} {} headers " 435*cda5da8dSAndroid Build Coastguard Worker "in a message".format(max_count, name)) 436*cda5da8dSAndroid Build Coastguard Worker self._headers.append(self.policy.header_store_parse(name, val)) 437*cda5da8dSAndroid Build Coastguard Worker 438*cda5da8dSAndroid Build Coastguard Worker def __delitem__(self, name): 439*cda5da8dSAndroid Build Coastguard Worker """Delete all occurrences of a header, if present. 440*cda5da8dSAndroid Build Coastguard Worker 441*cda5da8dSAndroid Build Coastguard Worker Does not raise an exception if the header is missing. 442*cda5da8dSAndroid Build Coastguard Worker """ 443*cda5da8dSAndroid Build Coastguard Worker name = name.lower() 444*cda5da8dSAndroid Build Coastguard Worker newheaders = [] 445*cda5da8dSAndroid Build Coastguard Worker for k, v in self._headers: 446*cda5da8dSAndroid Build Coastguard Worker if k.lower() != name: 447*cda5da8dSAndroid Build Coastguard Worker newheaders.append((k, v)) 448*cda5da8dSAndroid Build Coastguard Worker self._headers = newheaders 449*cda5da8dSAndroid Build Coastguard Worker 450*cda5da8dSAndroid Build Coastguard Worker def __contains__(self, name): 451*cda5da8dSAndroid Build Coastguard Worker return name.lower() in [k.lower() for k, v in self._headers] 452*cda5da8dSAndroid Build Coastguard Worker 453*cda5da8dSAndroid Build Coastguard Worker def __iter__(self): 454*cda5da8dSAndroid Build Coastguard Worker for field, value in self._headers: 455*cda5da8dSAndroid Build Coastguard Worker yield field 456*cda5da8dSAndroid Build Coastguard Worker 457*cda5da8dSAndroid Build Coastguard Worker def keys(self): 458*cda5da8dSAndroid Build Coastguard Worker """Return a list of all the message's header field names. 459*cda5da8dSAndroid Build Coastguard Worker 460*cda5da8dSAndroid Build Coastguard Worker These will be sorted in the order they appeared in the original 461*cda5da8dSAndroid Build Coastguard Worker message, or were added to the message, and may contain duplicates. 462*cda5da8dSAndroid Build Coastguard Worker Any fields deleted and re-inserted are always appended to the header 463*cda5da8dSAndroid Build Coastguard Worker list. 464*cda5da8dSAndroid Build Coastguard Worker """ 465*cda5da8dSAndroid Build Coastguard Worker return [k for k, v in self._headers] 466*cda5da8dSAndroid Build Coastguard Worker 467*cda5da8dSAndroid Build Coastguard Worker def values(self): 468*cda5da8dSAndroid Build Coastguard Worker """Return a list of all the message's header values. 469*cda5da8dSAndroid Build Coastguard Worker 470*cda5da8dSAndroid Build Coastguard Worker These will be sorted in the order they appeared in the original 471*cda5da8dSAndroid Build Coastguard Worker message, or were added to the message, and may contain duplicates. 472*cda5da8dSAndroid Build Coastguard Worker Any fields deleted and re-inserted are always appended to the header 473*cda5da8dSAndroid Build Coastguard Worker list. 474*cda5da8dSAndroid Build Coastguard Worker """ 475*cda5da8dSAndroid Build Coastguard Worker return [self.policy.header_fetch_parse(k, v) 476*cda5da8dSAndroid Build Coastguard Worker for k, v in self._headers] 477*cda5da8dSAndroid Build Coastguard Worker 478*cda5da8dSAndroid Build Coastguard Worker def items(self): 479*cda5da8dSAndroid Build Coastguard Worker """Get all the message's header fields and values. 480*cda5da8dSAndroid Build Coastguard Worker 481*cda5da8dSAndroid Build Coastguard Worker These will be sorted in the order they appeared in the original 482*cda5da8dSAndroid Build Coastguard Worker message, or were added to the message, and may contain duplicates. 483*cda5da8dSAndroid Build Coastguard Worker Any fields deleted and re-inserted are always appended to the header 484*cda5da8dSAndroid Build Coastguard Worker list. 485*cda5da8dSAndroid Build Coastguard Worker """ 486*cda5da8dSAndroid Build Coastguard Worker return [(k, self.policy.header_fetch_parse(k, v)) 487*cda5da8dSAndroid Build Coastguard Worker for k, v in self._headers] 488*cda5da8dSAndroid Build Coastguard Worker 489*cda5da8dSAndroid Build Coastguard Worker def get(self, name, failobj=None): 490*cda5da8dSAndroid Build Coastguard Worker """Get a header value. 491*cda5da8dSAndroid Build Coastguard Worker 492*cda5da8dSAndroid Build Coastguard Worker Like __getitem__() but return failobj instead of None when the field 493*cda5da8dSAndroid Build Coastguard Worker is missing. 494*cda5da8dSAndroid Build Coastguard Worker """ 495*cda5da8dSAndroid Build Coastguard Worker name = name.lower() 496*cda5da8dSAndroid Build Coastguard Worker for k, v in self._headers: 497*cda5da8dSAndroid Build Coastguard Worker if k.lower() == name: 498*cda5da8dSAndroid Build Coastguard Worker return self.policy.header_fetch_parse(k, v) 499*cda5da8dSAndroid Build Coastguard Worker return failobj 500*cda5da8dSAndroid Build Coastguard Worker 501*cda5da8dSAndroid Build Coastguard Worker # 502*cda5da8dSAndroid Build Coastguard Worker # "Internal" methods (public API, but only intended for use by a parser 503*cda5da8dSAndroid Build Coastguard Worker # or generator, not normal application code. 504*cda5da8dSAndroid Build Coastguard Worker # 505*cda5da8dSAndroid Build Coastguard Worker 506*cda5da8dSAndroid Build Coastguard Worker def set_raw(self, name, value): 507*cda5da8dSAndroid Build Coastguard Worker """Store name and value in the model without modification. 508*cda5da8dSAndroid Build Coastguard Worker 509*cda5da8dSAndroid Build Coastguard Worker This is an "internal" API, intended only for use by a parser. 510*cda5da8dSAndroid Build Coastguard Worker """ 511*cda5da8dSAndroid Build Coastguard Worker self._headers.append((name, value)) 512*cda5da8dSAndroid Build Coastguard Worker 513*cda5da8dSAndroid Build Coastguard Worker def raw_items(self): 514*cda5da8dSAndroid Build Coastguard Worker """Return the (name, value) header pairs without modification. 515*cda5da8dSAndroid Build Coastguard Worker 516*cda5da8dSAndroid Build Coastguard Worker This is an "internal" API, intended only for use by a generator. 517*cda5da8dSAndroid Build Coastguard Worker """ 518*cda5da8dSAndroid Build Coastguard Worker return iter(self._headers.copy()) 519*cda5da8dSAndroid Build Coastguard Worker 520*cda5da8dSAndroid Build Coastguard Worker # 521*cda5da8dSAndroid Build Coastguard Worker # Additional useful stuff 522*cda5da8dSAndroid Build Coastguard Worker # 523*cda5da8dSAndroid Build Coastguard Worker 524*cda5da8dSAndroid Build Coastguard Worker def get_all(self, name, failobj=None): 525*cda5da8dSAndroid Build Coastguard Worker """Return a list of all the values for the named field. 526*cda5da8dSAndroid Build Coastguard Worker 527*cda5da8dSAndroid Build Coastguard Worker These will be sorted in the order they appeared in the original 528*cda5da8dSAndroid Build Coastguard Worker message, and may contain duplicates. Any fields deleted and 529*cda5da8dSAndroid Build Coastguard Worker re-inserted are always appended to the header list. 530*cda5da8dSAndroid Build Coastguard Worker 531*cda5da8dSAndroid Build Coastguard Worker If no such fields exist, failobj is returned (defaults to None). 532*cda5da8dSAndroid Build Coastguard Worker """ 533*cda5da8dSAndroid Build Coastguard Worker values = [] 534*cda5da8dSAndroid Build Coastguard Worker name = name.lower() 535*cda5da8dSAndroid Build Coastguard Worker for k, v in self._headers: 536*cda5da8dSAndroid Build Coastguard Worker if k.lower() == name: 537*cda5da8dSAndroid Build Coastguard Worker values.append(self.policy.header_fetch_parse(k, v)) 538*cda5da8dSAndroid Build Coastguard Worker if not values: 539*cda5da8dSAndroid Build Coastguard Worker return failobj 540*cda5da8dSAndroid Build Coastguard Worker return values 541*cda5da8dSAndroid Build Coastguard Worker 542*cda5da8dSAndroid Build Coastguard Worker def add_header(self, _name, _value, **_params): 543*cda5da8dSAndroid Build Coastguard Worker """Extended header setting. 544*cda5da8dSAndroid Build Coastguard Worker 545*cda5da8dSAndroid Build Coastguard Worker name is the header field to add. keyword arguments can be used to set 546*cda5da8dSAndroid Build Coastguard Worker additional parameters for the header field, with underscores converted 547*cda5da8dSAndroid Build Coastguard Worker to dashes. Normally the parameter will be added as key="value" unless 548*cda5da8dSAndroid Build Coastguard Worker value is None, in which case only the key will be added. If a 549*cda5da8dSAndroid Build Coastguard Worker parameter value contains non-ASCII characters it can be specified as a 550*cda5da8dSAndroid Build Coastguard Worker three-tuple of (charset, language, value), in which case it will be 551*cda5da8dSAndroid Build Coastguard Worker encoded according to RFC2231 rules. Otherwise it will be encoded using 552*cda5da8dSAndroid Build Coastguard Worker the utf-8 charset and a language of ''. 553*cda5da8dSAndroid Build Coastguard Worker 554*cda5da8dSAndroid Build Coastguard Worker Examples: 555*cda5da8dSAndroid Build Coastguard Worker 556*cda5da8dSAndroid Build Coastguard Worker msg.add_header('content-disposition', 'attachment', filename='bud.gif') 557*cda5da8dSAndroid Build Coastguard Worker msg.add_header('content-disposition', 'attachment', 558*cda5da8dSAndroid Build Coastguard Worker filename=('utf-8', '', Fußballer.ppt')) 559*cda5da8dSAndroid Build Coastguard Worker msg.add_header('content-disposition', 'attachment', 560*cda5da8dSAndroid Build Coastguard Worker filename='Fußballer.ppt')) 561*cda5da8dSAndroid Build Coastguard Worker """ 562*cda5da8dSAndroid Build Coastguard Worker parts = [] 563*cda5da8dSAndroid Build Coastguard Worker for k, v in _params.items(): 564*cda5da8dSAndroid Build Coastguard Worker if v is None: 565*cda5da8dSAndroid Build Coastguard Worker parts.append(k.replace('_', '-')) 566*cda5da8dSAndroid Build Coastguard Worker else: 567*cda5da8dSAndroid Build Coastguard Worker parts.append(_formatparam(k.replace('_', '-'), v)) 568*cda5da8dSAndroid Build Coastguard Worker if _value is not None: 569*cda5da8dSAndroid Build Coastguard Worker parts.insert(0, _value) 570*cda5da8dSAndroid Build Coastguard Worker self[_name] = SEMISPACE.join(parts) 571*cda5da8dSAndroid Build Coastguard Worker 572*cda5da8dSAndroid Build Coastguard Worker def replace_header(self, _name, _value): 573*cda5da8dSAndroid Build Coastguard Worker """Replace a header. 574*cda5da8dSAndroid Build Coastguard Worker 575*cda5da8dSAndroid Build Coastguard Worker Replace the first matching header found in the message, retaining 576*cda5da8dSAndroid Build Coastguard Worker header order and case. If no matching header was found, a KeyError is 577*cda5da8dSAndroid Build Coastguard Worker raised. 578*cda5da8dSAndroid Build Coastguard Worker """ 579*cda5da8dSAndroid Build Coastguard Worker _name = _name.lower() 580*cda5da8dSAndroid Build Coastguard Worker for i, (k, v) in zip(range(len(self._headers)), self._headers): 581*cda5da8dSAndroid Build Coastguard Worker if k.lower() == _name: 582*cda5da8dSAndroid Build Coastguard Worker self._headers[i] = self.policy.header_store_parse(k, _value) 583*cda5da8dSAndroid Build Coastguard Worker break 584*cda5da8dSAndroid Build Coastguard Worker else: 585*cda5da8dSAndroid Build Coastguard Worker raise KeyError(_name) 586*cda5da8dSAndroid Build Coastguard Worker 587*cda5da8dSAndroid Build Coastguard Worker # 588*cda5da8dSAndroid Build Coastguard Worker # Use these three methods instead of the three above. 589*cda5da8dSAndroid Build Coastguard Worker # 590*cda5da8dSAndroid Build Coastguard Worker 591*cda5da8dSAndroid Build Coastguard Worker def get_content_type(self): 592*cda5da8dSAndroid Build Coastguard Worker """Return the message's content type. 593*cda5da8dSAndroid Build Coastguard Worker 594*cda5da8dSAndroid Build Coastguard Worker The returned string is coerced to lower case of the form 595*cda5da8dSAndroid Build Coastguard Worker `maintype/subtype'. If there was no Content-Type header in the 596*cda5da8dSAndroid Build Coastguard Worker message, the default type as given by get_default_type() will be 597*cda5da8dSAndroid Build Coastguard Worker returned. Since according to RFC 2045, messages always have a default 598*cda5da8dSAndroid Build Coastguard Worker type this will always return a value. 599*cda5da8dSAndroid Build Coastguard Worker 600*cda5da8dSAndroid Build Coastguard Worker RFC 2045 defines a message's default type to be text/plain unless it 601*cda5da8dSAndroid Build Coastguard Worker appears inside a multipart/digest container, in which case it would be 602*cda5da8dSAndroid Build Coastguard Worker message/rfc822. 603*cda5da8dSAndroid Build Coastguard Worker """ 604*cda5da8dSAndroid Build Coastguard Worker missing = object() 605*cda5da8dSAndroid Build Coastguard Worker value = self.get('content-type', missing) 606*cda5da8dSAndroid Build Coastguard Worker if value is missing: 607*cda5da8dSAndroid Build Coastguard Worker # This should have no parameters 608*cda5da8dSAndroid Build Coastguard Worker return self.get_default_type() 609*cda5da8dSAndroid Build Coastguard Worker ctype = _splitparam(value)[0].lower() 610*cda5da8dSAndroid Build Coastguard Worker # RFC 2045, section 5.2 says if its invalid, use text/plain 611*cda5da8dSAndroid Build Coastguard Worker if ctype.count('/') != 1: 612*cda5da8dSAndroid Build Coastguard Worker return 'text/plain' 613*cda5da8dSAndroid Build Coastguard Worker return ctype 614*cda5da8dSAndroid Build Coastguard Worker 615*cda5da8dSAndroid Build Coastguard Worker def get_content_maintype(self): 616*cda5da8dSAndroid Build Coastguard Worker """Return the message's main content type. 617*cda5da8dSAndroid Build Coastguard Worker 618*cda5da8dSAndroid Build Coastguard Worker This is the `maintype' part of the string returned by 619*cda5da8dSAndroid Build Coastguard Worker get_content_type(). 620*cda5da8dSAndroid Build Coastguard Worker """ 621*cda5da8dSAndroid Build Coastguard Worker ctype = self.get_content_type() 622*cda5da8dSAndroid Build Coastguard Worker return ctype.split('/')[0] 623*cda5da8dSAndroid Build Coastguard Worker 624*cda5da8dSAndroid Build Coastguard Worker def get_content_subtype(self): 625*cda5da8dSAndroid Build Coastguard Worker """Returns the message's sub-content type. 626*cda5da8dSAndroid Build Coastguard Worker 627*cda5da8dSAndroid Build Coastguard Worker This is the `subtype' part of the string returned by 628*cda5da8dSAndroid Build Coastguard Worker get_content_type(). 629*cda5da8dSAndroid Build Coastguard Worker """ 630*cda5da8dSAndroid Build Coastguard Worker ctype = self.get_content_type() 631*cda5da8dSAndroid Build Coastguard Worker return ctype.split('/')[1] 632*cda5da8dSAndroid Build Coastguard Worker 633*cda5da8dSAndroid Build Coastguard Worker def get_default_type(self): 634*cda5da8dSAndroid Build Coastguard Worker """Return the `default' content type. 635*cda5da8dSAndroid Build Coastguard Worker 636*cda5da8dSAndroid Build Coastguard Worker Most messages have a default content type of text/plain, except for 637*cda5da8dSAndroid Build Coastguard Worker messages that are subparts of multipart/digest containers. Such 638*cda5da8dSAndroid Build Coastguard Worker subparts have a default content type of message/rfc822. 639*cda5da8dSAndroid Build Coastguard Worker """ 640*cda5da8dSAndroid Build Coastguard Worker return self._default_type 641*cda5da8dSAndroid Build Coastguard Worker 642*cda5da8dSAndroid Build Coastguard Worker def set_default_type(self, ctype): 643*cda5da8dSAndroid Build Coastguard Worker """Set the `default' content type. 644*cda5da8dSAndroid Build Coastguard Worker 645*cda5da8dSAndroid Build Coastguard Worker ctype should be either "text/plain" or "message/rfc822", although this 646*cda5da8dSAndroid Build Coastguard Worker is not enforced. The default content type is not stored in the 647*cda5da8dSAndroid Build Coastguard Worker Content-Type header. 648*cda5da8dSAndroid Build Coastguard Worker """ 649*cda5da8dSAndroid Build Coastguard Worker self._default_type = ctype 650*cda5da8dSAndroid Build Coastguard Worker 651*cda5da8dSAndroid Build Coastguard Worker def _get_params_preserve(self, failobj, header): 652*cda5da8dSAndroid Build Coastguard Worker # Like get_params() but preserves the quoting of values. BAW: 653*cda5da8dSAndroid Build Coastguard Worker # should this be part of the public interface? 654*cda5da8dSAndroid Build Coastguard Worker missing = object() 655*cda5da8dSAndroid Build Coastguard Worker value = self.get(header, missing) 656*cda5da8dSAndroid Build Coastguard Worker if value is missing: 657*cda5da8dSAndroid Build Coastguard Worker return failobj 658*cda5da8dSAndroid Build Coastguard Worker params = [] 659*cda5da8dSAndroid Build Coastguard Worker for p in _parseparam(value): 660*cda5da8dSAndroid Build Coastguard Worker try: 661*cda5da8dSAndroid Build Coastguard Worker name, val = p.split('=', 1) 662*cda5da8dSAndroid Build Coastguard Worker name = name.strip() 663*cda5da8dSAndroid Build Coastguard Worker val = val.strip() 664*cda5da8dSAndroid Build Coastguard Worker except ValueError: 665*cda5da8dSAndroid Build Coastguard Worker # Must have been a bare attribute 666*cda5da8dSAndroid Build Coastguard Worker name = p.strip() 667*cda5da8dSAndroid Build Coastguard Worker val = '' 668*cda5da8dSAndroid Build Coastguard Worker params.append((name, val)) 669*cda5da8dSAndroid Build Coastguard Worker params = utils.decode_params(params) 670*cda5da8dSAndroid Build Coastguard Worker return params 671*cda5da8dSAndroid Build Coastguard Worker 672*cda5da8dSAndroid Build Coastguard Worker def get_params(self, failobj=None, header='content-type', unquote=True): 673*cda5da8dSAndroid Build Coastguard Worker """Return the message's Content-Type parameters, as a list. 674*cda5da8dSAndroid Build Coastguard Worker 675*cda5da8dSAndroid Build Coastguard Worker The elements of the returned list are 2-tuples of key/value pairs, as 676*cda5da8dSAndroid Build Coastguard Worker split on the `=' sign. The left hand side of the `=' is the key, 677*cda5da8dSAndroid Build Coastguard Worker while the right hand side is the value. If there is no `=' sign in 678*cda5da8dSAndroid Build Coastguard Worker the parameter the value is the empty string. The value is as 679*cda5da8dSAndroid Build Coastguard Worker described in the get_param() method. 680*cda5da8dSAndroid Build Coastguard Worker 681*cda5da8dSAndroid Build Coastguard Worker Optional failobj is the object to return if there is no Content-Type 682*cda5da8dSAndroid Build Coastguard Worker header. Optional header is the header to search instead of 683*cda5da8dSAndroid Build Coastguard Worker Content-Type. If unquote is True, the value is unquoted. 684*cda5da8dSAndroid Build Coastguard Worker """ 685*cda5da8dSAndroid Build Coastguard Worker missing = object() 686*cda5da8dSAndroid Build Coastguard Worker params = self._get_params_preserve(missing, header) 687*cda5da8dSAndroid Build Coastguard Worker if params is missing: 688*cda5da8dSAndroid Build Coastguard Worker return failobj 689*cda5da8dSAndroid Build Coastguard Worker if unquote: 690*cda5da8dSAndroid Build Coastguard Worker return [(k, _unquotevalue(v)) for k, v in params] 691*cda5da8dSAndroid Build Coastguard Worker else: 692*cda5da8dSAndroid Build Coastguard Worker return params 693*cda5da8dSAndroid Build Coastguard Worker 694*cda5da8dSAndroid Build Coastguard Worker def get_param(self, param, failobj=None, header='content-type', 695*cda5da8dSAndroid Build Coastguard Worker unquote=True): 696*cda5da8dSAndroid Build Coastguard Worker """Return the parameter value if found in the Content-Type header. 697*cda5da8dSAndroid Build Coastguard Worker 698*cda5da8dSAndroid Build Coastguard Worker Optional failobj is the object to return if there is no Content-Type 699*cda5da8dSAndroid Build Coastguard Worker header, or the Content-Type header has no such parameter. Optional 700*cda5da8dSAndroid Build Coastguard Worker header is the header to search instead of Content-Type. 701*cda5da8dSAndroid Build Coastguard Worker 702*cda5da8dSAndroid Build Coastguard Worker Parameter keys are always compared case insensitively. The return 703*cda5da8dSAndroid Build Coastguard Worker value can either be a string, or a 3-tuple if the parameter was RFC 704*cda5da8dSAndroid Build Coastguard Worker 2231 encoded. When it's a 3-tuple, the elements of the value are of 705*cda5da8dSAndroid Build Coastguard Worker the form (CHARSET, LANGUAGE, VALUE). Note that both CHARSET and 706*cda5da8dSAndroid Build Coastguard Worker LANGUAGE can be None, in which case you should consider VALUE to be 707*cda5da8dSAndroid Build Coastguard Worker encoded in the us-ascii charset. You can usually ignore LANGUAGE. 708*cda5da8dSAndroid Build Coastguard Worker The parameter value (either the returned string, or the VALUE item in 709*cda5da8dSAndroid Build Coastguard Worker the 3-tuple) is always unquoted, unless unquote is set to False. 710*cda5da8dSAndroid Build Coastguard Worker 711*cda5da8dSAndroid Build Coastguard Worker If your application doesn't care whether the parameter was RFC 2231 712*cda5da8dSAndroid Build Coastguard Worker encoded, it can turn the return value into a string as follows: 713*cda5da8dSAndroid Build Coastguard Worker 714*cda5da8dSAndroid Build Coastguard Worker rawparam = msg.get_param('foo') 715*cda5da8dSAndroid Build Coastguard Worker param = email.utils.collapse_rfc2231_value(rawparam) 716*cda5da8dSAndroid Build Coastguard Worker 717*cda5da8dSAndroid Build Coastguard Worker """ 718*cda5da8dSAndroid Build Coastguard Worker if header not in self: 719*cda5da8dSAndroid Build Coastguard Worker return failobj 720*cda5da8dSAndroid Build Coastguard Worker for k, v in self._get_params_preserve(failobj, header): 721*cda5da8dSAndroid Build Coastguard Worker if k.lower() == param.lower(): 722*cda5da8dSAndroid Build Coastguard Worker if unquote: 723*cda5da8dSAndroid Build Coastguard Worker return _unquotevalue(v) 724*cda5da8dSAndroid Build Coastguard Worker else: 725*cda5da8dSAndroid Build Coastguard Worker return v 726*cda5da8dSAndroid Build Coastguard Worker return failobj 727*cda5da8dSAndroid Build Coastguard Worker 728*cda5da8dSAndroid Build Coastguard Worker def set_param(self, param, value, header='Content-Type', requote=True, 729*cda5da8dSAndroid Build Coastguard Worker charset=None, language='', replace=False): 730*cda5da8dSAndroid Build Coastguard Worker """Set a parameter in the Content-Type header. 731*cda5da8dSAndroid Build Coastguard Worker 732*cda5da8dSAndroid Build Coastguard Worker If the parameter already exists in the header, its value will be 733*cda5da8dSAndroid Build Coastguard Worker replaced with the new value. 734*cda5da8dSAndroid Build Coastguard Worker 735*cda5da8dSAndroid Build Coastguard Worker If header is Content-Type and has not yet been defined for this 736*cda5da8dSAndroid Build Coastguard Worker message, it will be set to "text/plain" and the new parameter and 737*cda5da8dSAndroid Build Coastguard Worker value will be appended as per RFC 2045. 738*cda5da8dSAndroid Build Coastguard Worker 739*cda5da8dSAndroid Build Coastguard Worker An alternate header can be specified in the header argument, and all 740*cda5da8dSAndroid Build Coastguard Worker parameters will be quoted as necessary unless requote is False. 741*cda5da8dSAndroid Build Coastguard Worker 742*cda5da8dSAndroid Build Coastguard Worker If charset is specified, the parameter will be encoded according to RFC 743*cda5da8dSAndroid Build Coastguard Worker 2231. Optional language specifies the RFC 2231 language, defaulting 744*cda5da8dSAndroid Build Coastguard Worker to the empty string. Both charset and language should be strings. 745*cda5da8dSAndroid Build Coastguard Worker """ 746*cda5da8dSAndroid Build Coastguard Worker if not isinstance(value, tuple) and charset: 747*cda5da8dSAndroid Build Coastguard Worker value = (charset, language, value) 748*cda5da8dSAndroid Build Coastguard Worker 749*cda5da8dSAndroid Build Coastguard Worker if header not in self and header.lower() == 'content-type': 750*cda5da8dSAndroid Build Coastguard Worker ctype = 'text/plain' 751*cda5da8dSAndroid Build Coastguard Worker else: 752*cda5da8dSAndroid Build Coastguard Worker ctype = self.get(header) 753*cda5da8dSAndroid Build Coastguard Worker if not self.get_param(param, header=header): 754*cda5da8dSAndroid Build Coastguard Worker if not ctype: 755*cda5da8dSAndroid Build Coastguard Worker ctype = _formatparam(param, value, requote) 756*cda5da8dSAndroid Build Coastguard Worker else: 757*cda5da8dSAndroid Build Coastguard Worker ctype = SEMISPACE.join( 758*cda5da8dSAndroid Build Coastguard Worker [ctype, _formatparam(param, value, requote)]) 759*cda5da8dSAndroid Build Coastguard Worker else: 760*cda5da8dSAndroid Build Coastguard Worker ctype = '' 761*cda5da8dSAndroid Build Coastguard Worker for old_param, old_value in self.get_params(header=header, 762*cda5da8dSAndroid Build Coastguard Worker unquote=requote): 763*cda5da8dSAndroid Build Coastguard Worker append_param = '' 764*cda5da8dSAndroid Build Coastguard Worker if old_param.lower() == param.lower(): 765*cda5da8dSAndroid Build Coastguard Worker append_param = _formatparam(param, value, requote) 766*cda5da8dSAndroid Build Coastguard Worker else: 767*cda5da8dSAndroid Build Coastguard Worker append_param = _formatparam(old_param, old_value, requote) 768*cda5da8dSAndroid Build Coastguard Worker if not ctype: 769*cda5da8dSAndroid Build Coastguard Worker ctype = append_param 770*cda5da8dSAndroid Build Coastguard Worker else: 771*cda5da8dSAndroid Build Coastguard Worker ctype = SEMISPACE.join([ctype, append_param]) 772*cda5da8dSAndroid Build Coastguard Worker if ctype != self.get(header): 773*cda5da8dSAndroid Build Coastguard Worker if replace: 774*cda5da8dSAndroid Build Coastguard Worker self.replace_header(header, ctype) 775*cda5da8dSAndroid Build Coastguard Worker else: 776*cda5da8dSAndroid Build Coastguard Worker del self[header] 777*cda5da8dSAndroid Build Coastguard Worker self[header] = ctype 778*cda5da8dSAndroid Build Coastguard Worker 779*cda5da8dSAndroid Build Coastguard Worker def del_param(self, param, header='content-type', requote=True): 780*cda5da8dSAndroid Build Coastguard Worker """Remove the given parameter completely from the Content-Type header. 781*cda5da8dSAndroid Build Coastguard Worker 782*cda5da8dSAndroid Build Coastguard Worker The header will be re-written in place without the parameter or its 783*cda5da8dSAndroid Build Coastguard Worker value. All values will be quoted as necessary unless requote is 784*cda5da8dSAndroid Build Coastguard Worker False. Optional header specifies an alternative to the Content-Type 785*cda5da8dSAndroid Build Coastguard Worker header. 786*cda5da8dSAndroid Build Coastguard Worker """ 787*cda5da8dSAndroid Build Coastguard Worker if header not in self: 788*cda5da8dSAndroid Build Coastguard Worker return 789*cda5da8dSAndroid Build Coastguard Worker new_ctype = '' 790*cda5da8dSAndroid Build Coastguard Worker for p, v in self.get_params(header=header, unquote=requote): 791*cda5da8dSAndroid Build Coastguard Worker if p.lower() != param.lower(): 792*cda5da8dSAndroid Build Coastguard Worker if not new_ctype: 793*cda5da8dSAndroid Build Coastguard Worker new_ctype = _formatparam(p, v, requote) 794*cda5da8dSAndroid Build Coastguard Worker else: 795*cda5da8dSAndroid Build Coastguard Worker new_ctype = SEMISPACE.join([new_ctype, 796*cda5da8dSAndroid Build Coastguard Worker _formatparam(p, v, requote)]) 797*cda5da8dSAndroid Build Coastguard Worker if new_ctype != self.get(header): 798*cda5da8dSAndroid Build Coastguard Worker del self[header] 799*cda5da8dSAndroid Build Coastguard Worker self[header] = new_ctype 800*cda5da8dSAndroid Build Coastguard Worker 801*cda5da8dSAndroid Build Coastguard Worker def set_type(self, type, header='Content-Type', requote=True): 802*cda5da8dSAndroid Build Coastguard Worker """Set the main type and subtype for the Content-Type header. 803*cda5da8dSAndroid Build Coastguard Worker 804*cda5da8dSAndroid Build Coastguard Worker type must be a string in the form "maintype/subtype", otherwise a 805*cda5da8dSAndroid Build Coastguard Worker ValueError is raised. 806*cda5da8dSAndroid Build Coastguard Worker 807*cda5da8dSAndroid Build Coastguard Worker This method replaces the Content-Type header, keeping all the 808*cda5da8dSAndroid Build Coastguard Worker parameters in place. If requote is False, this leaves the existing 809*cda5da8dSAndroid Build Coastguard Worker header's quoting as is. Otherwise, the parameters will be quoted (the 810*cda5da8dSAndroid Build Coastguard Worker default). 811*cda5da8dSAndroid Build Coastguard Worker 812*cda5da8dSAndroid Build Coastguard Worker An alternative header can be specified in the header argument. When 813*cda5da8dSAndroid Build Coastguard Worker the Content-Type header is set, we'll always also add a MIME-Version 814*cda5da8dSAndroid Build Coastguard Worker header. 815*cda5da8dSAndroid Build Coastguard Worker """ 816*cda5da8dSAndroid Build Coastguard Worker # BAW: should we be strict? 817*cda5da8dSAndroid Build Coastguard Worker if not type.count('/') == 1: 818*cda5da8dSAndroid Build Coastguard Worker raise ValueError 819*cda5da8dSAndroid Build Coastguard Worker # Set the Content-Type, you get a MIME-Version 820*cda5da8dSAndroid Build Coastguard Worker if header.lower() == 'content-type': 821*cda5da8dSAndroid Build Coastguard Worker del self['mime-version'] 822*cda5da8dSAndroid Build Coastguard Worker self['MIME-Version'] = '1.0' 823*cda5da8dSAndroid Build Coastguard Worker if header not in self: 824*cda5da8dSAndroid Build Coastguard Worker self[header] = type 825*cda5da8dSAndroid Build Coastguard Worker return 826*cda5da8dSAndroid Build Coastguard Worker params = self.get_params(header=header, unquote=requote) 827*cda5da8dSAndroid Build Coastguard Worker del self[header] 828*cda5da8dSAndroid Build Coastguard Worker self[header] = type 829*cda5da8dSAndroid Build Coastguard Worker # Skip the first param; it's the old type. 830*cda5da8dSAndroid Build Coastguard Worker for p, v in params[1:]: 831*cda5da8dSAndroid Build Coastguard Worker self.set_param(p, v, header, requote) 832*cda5da8dSAndroid Build Coastguard Worker 833*cda5da8dSAndroid Build Coastguard Worker def get_filename(self, failobj=None): 834*cda5da8dSAndroid Build Coastguard Worker """Return the filename associated with the payload if present. 835*cda5da8dSAndroid Build Coastguard Worker 836*cda5da8dSAndroid Build Coastguard Worker The filename is extracted from the Content-Disposition header's 837*cda5da8dSAndroid Build Coastguard Worker `filename' parameter, and it is unquoted. If that header is missing 838*cda5da8dSAndroid Build Coastguard Worker the `filename' parameter, this method falls back to looking for the 839*cda5da8dSAndroid Build Coastguard Worker `name' parameter. 840*cda5da8dSAndroid Build Coastguard Worker """ 841*cda5da8dSAndroid Build Coastguard Worker missing = object() 842*cda5da8dSAndroid Build Coastguard Worker filename = self.get_param('filename', missing, 'content-disposition') 843*cda5da8dSAndroid Build Coastguard Worker if filename is missing: 844*cda5da8dSAndroid Build Coastguard Worker filename = self.get_param('name', missing, 'content-type') 845*cda5da8dSAndroid Build Coastguard Worker if filename is missing: 846*cda5da8dSAndroid Build Coastguard Worker return failobj 847*cda5da8dSAndroid Build Coastguard Worker return utils.collapse_rfc2231_value(filename).strip() 848*cda5da8dSAndroid Build Coastguard Worker 849*cda5da8dSAndroid Build Coastguard Worker def get_boundary(self, failobj=None): 850*cda5da8dSAndroid Build Coastguard Worker """Return the boundary associated with the payload if present. 851*cda5da8dSAndroid Build Coastguard Worker 852*cda5da8dSAndroid Build Coastguard Worker The boundary is extracted from the Content-Type header's `boundary' 853*cda5da8dSAndroid Build Coastguard Worker parameter, and it is unquoted. 854*cda5da8dSAndroid Build Coastguard Worker """ 855*cda5da8dSAndroid Build Coastguard Worker missing = object() 856*cda5da8dSAndroid Build Coastguard Worker boundary = self.get_param('boundary', missing) 857*cda5da8dSAndroid Build Coastguard Worker if boundary is missing: 858*cda5da8dSAndroid Build Coastguard Worker return failobj 859*cda5da8dSAndroid Build Coastguard Worker # RFC 2046 says that boundaries may begin but not end in w/s 860*cda5da8dSAndroid Build Coastguard Worker return utils.collapse_rfc2231_value(boundary).rstrip() 861*cda5da8dSAndroid Build Coastguard Worker 862*cda5da8dSAndroid Build Coastguard Worker def set_boundary(self, boundary): 863*cda5da8dSAndroid Build Coastguard Worker """Set the boundary parameter in Content-Type to 'boundary'. 864*cda5da8dSAndroid Build Coastguard Worker 865*cda5da8dSAndroid Build Coastguard Worker This is subtly different than deleting the Content-Type header and 866*cda5da8dSAndroid Build Coastguard Worker adding a new one with a new boundary parameter via add_header(). The 867*cda5da8dSAndroid Build Coastguard Worker main difference is that using the set_boundary() method preserves the 868*cda5da8dSAndroid Build Coastguard Worker order of the Content-Type header in the original message. 869*cda5da8dSAndroid Build Coastguard Worker 870*cda5da8dSAndroid Build Coastguard Worker HeaderParseError is raised if the message has no Content-Type header. 871*cda5da8dSAndroid Build Coastguard Worker """ 872*cda5da8dSAndroid Build Coastguard Worker missing = object() 873*cda5da8dSAndroid Build Coastguard Worker params = self._get_params_preserve(missing, 'content-type') 874*cda5da8dSAndroid Build Coastguard Worker if params is missing: 875*cda5da8dSAndroid Build Coastguard Worker # There was no Content-Type header, and we don't know what type 876*cda5da8dSAndroid Build Coastguard Worker # to set it to, so raise an exception. 877*cda5da8dSAndroid Build Coastguard Worker raise errors.HeaderParseError('No Content-Type header found') 878*cda5da8dSAndroid Build Coastguard Worker newparams = [] 879*cda5da8dSAndroid Build Coastguard Worker foundp = False 880*cda5da8dSAndroid Build Coastguard Worker for pk, pv in params: 881*cda5da8dSAndroid Build Coastguard Worker if pk.lower() == 'boundary': 882*cda5da8dSAndroid Build Coastguard Worker newparams.append(('boundary', '"%s"' % boundary)) 883*cda5da8dSAndroid Build Coastguard Worker foundp = True 884*cda5da8dSAndroid Build Coastguard Worker else: 885*cda5da8dSAndroid Build Coastguard Worker newparams.append((pk, pv)) 886*cda5da8dSAndroid Build Coastguard Worker if not foundp: 887*cda5da8dSAndroid Build Coastguard Worker # The original Content-Type header had no boundary attribute. 888*cda5da8dSAndroid Build Coastguard Worker # Tack one on the end. BAW: should we raise an exception 889*cda5da8dSAndroid Build Coastguard Worker # instead??? 890*cda5da8dSAndroid Build Coastguard Worker newparams.append(('boundary', '"%s"' % boundary)) 891*cda5da8dSAndroid Build Coastguard Worker # Replace the existing Content-Type header with the new value 892*cda5da8dSAndroid Build Coastguard Worker newheaders = [] 893*cda5da8dSAndroid Build Coastguard Worker for h, v in self._headers: 894*cda5da8dSAndroid Build Coastguard Worker if h.lower() == 'content-type': 895*cda5da8dSAndroid Build Coastguard Worker parts = [] 896*cda5da8dSAndroid Build Coastguard Worker for k, v in newparams: 897*cda5da8dSAndroid Build Coastguard Worker if v == '': 898*cda5da8dSAndroid Build Coastguard Worker parts.append(k) 899*cda5da8dSAndroid Build Coastguard Worker else: 900*cda5da8dSAndroid Build Coastguard Worker parts.append('%s=%s' % (k, v)) 901*cda5da8dSAndroid Build Coastguard Worker val = SEMISPACE.join(parts) 902*cda5da8dSAndroid Build Coastguard Worker newheaders.append(self.policy.header_store_parse(h, val)) 903*cda5da8dSAndroid Build Coastguard Worker 904*cda5da8dSAndroid Build Coastguard Worker else: 905*cda5da8dSAndroid Build Coastguard Worker newheaders.append((h, v)) 906*cda5da8dSAndroid Build Coastguard Worker self._headers = newheaders 907*cda5da8dSAndroid Build Coastguard Worker 908*cda5da8dSAndroid Build Coastguard Worker def get_content_charset(self, failobj=None): 909*cda5da8dSAndroid Build Coastguard Worker """Return the charset parameter of the Content-Type header. 910*cda5da8dSAndroid Build Coastguard Worker 911*cda5da8dSAndroid Build Coastguard Worker The returned string is always coerced to lower case. If there is no 912*cda5da8dSAndroid Build Coastguard Worker Content-Type header, or if that header has no charset parameter, 913*cda5da8dSAndroid Build Coastguard Worker failobj is returned. 914*cda5da8dSAndroid Build Coastguard Worker """ 915*cda5da8dSAndroid Build Coastguard Worker missing = object() 916*cda5da8dSAndroid Build Coastguard Worker charset = self.get_param('charset', missing) 917*cda5da8dSAndroid Build Coastguard Worker if charset is missing: 918*cda5da8dSAndroid Build Coastguard Worker return failobj 919*cda5da8dSAndroid Build Coastguard Worker if isinstance(charset, tuple): 920*cda5da8dSAndroid Build Coastguard Worker # RFC 2231 encoded, so decode it, and it better end up as ascii. 921*cda5da8dSAndroid Build Coastguard Worker pcharset = charset[0] or 'us-ascii' 922*cda5da8dSAndroid Build Coastguard Worker try: 923*cda5da8dSAndroid Build Coastguard Worker # LookupError will be raised if the charset isn't known to 924*cda5da8dSAndroid Build Coastguard Worker # Python. UnicodeError will be raised if the encoded text 925*cda5da8dSAndroid Build Coastguard Worker # contains a character not in the charset. 926*cda5da8dSAndroid Build Coastguard Worker as_bytes = charset[2].encode('raw-unicode-escape') 927*cda5da8dSAndroid Build Coastguard Worker charset = str(as_bytes, pcharset) 928*cda5da8dSAndroid Build Coastguard Worker except (LookupError, UnicodeError): 929*cda5da8dSAndroid Build Coastguard Worker charset = charset[2] 930*cda5da8dSAndroid Build Coastguard Worker # charset characters must be in us-ascii range 931*cda5da8dSAndroid Build Coastguard Worker try: 932*cda5da8dSAndroid Build Coastguard Worker charset.encode('us-ascii') 933*cda5da8dSAndroid Build Coastguard Worker except UnicodeError: 934*cda5da8dSAndroid Build Coastguard Worker return failobj 935*cda5da8dSAndroid Build Coastguard Worker # RFC 2046, $4.1.2 says charsets are not case sensitive 936*cda5da8dSAndroid Build Coastguard Worker return charset.lower() 937*cda5da8dSAndroid Build Coastguard Worker 938*cda5da8dSAndroid Build Coastguard Worker def get_charsets(self, failobj=None): 939*cda5da8dSAndroid Build Coastguard Worker """Return a list containing the charset(s) used in this message. 940*cda5da8dSAndroid Build Coastguard Worker 941*cda5da8dSAndroid Build Coastguard Worker The returned list of items describes the Content-Type headers' 942*cda5da8dSAndroid Build Coastguard Worker charset parameter for this message and all the subparts in its 943*cda5da8dSAndroid Build Coastguard Worker payload. 944*cda5da8dSAndroid Build Coastguard Worker 945*cda5da8dSAndroid Build Coastguard Worker Each item will either be a string (the value of the charset parameter 946*cda5da8dSAndroid Build Coastguard Worker in the Content-Type header of that part) or the value of the 947*cda5da8dSAndroid Build Coastguard Worker 'failobj' parameter (defaults to None), if the part does not have a 948*cda5da8dSAndroid Build Coastguard Worker main MIME type of "text", or the charset is not defined. 949*cda5da8dSAndroid Build Coastguard Worker 950*cda5da8dSAndroid Build Coastguard Worker The list will contain one string for each part of the message, plus 951*cda5da8dSAndroid Build Coastguard Worker one for the container message (i.e. self), so that a non-multipart 952*cda5da8dSAndroid Build Coastguard Worker message will still return a list of length 1. 953*cda5da8dSAndroid Build Coastguard Worker """ 954*cda5da8dSAndroid Build Coastguard Worker return [part.get_content_charset(failobj) for part in self.walk()] 955*cda5da8dSAndroid Build Coastguard Worker 956*cda5da8dSAndroid Build Coastguard Worker def get_content_disposition(self): 957*cda5da8dSAndroid Build Coastguard Worker """Return the message's content-disposition if it exists, or None. 958*cda5da8dSAndroid Build Coastguard Worker 959*cda5da8dSAndroid Build Coastguard Worker The return values can be either 'inline', 'attachment' or None 960*cda5da8dSAndroid Build Coastguard Worker according to the rfc2183. 961*cda5da8dSAndroid Build Coastguard Worker """ 962*cda5da8dSAndroid Build Coastguard Worker value = self.get('content-disposition') 963*cda5da8dSAndroid Build Coastguard Worker if value is None: 964*cda5da8dSAndroid Build Coastguard Worker return None 965*cda5da8dSAndroid Build Coastguard Worker c_d = _splitparam(value)[0].lower() 966*cda5da8dSAndroid Build Coastguard Worker return c_d 967*cda5da8dSAndroid Build Coastguard Worker 968*cda5da8dSAndroid Build Coastguard Worker # I.e. def walk(self): ... 969*cda5da8dSAndroid Build Coastguard Worker from email.iterators import walk 970*cda5da8dSAndroid Build Coastguard Worker 971*cda5da8dSAndroid Build Coastguard Worker 972*cda5da8dSAndroid Build Coastguard Workerclass MIMEPart(Message): 973*cda5da8dSAndroid Build Coastguard Worker 974*cda5da8dSAndroid Build Coastguard Worker def __init__(self, policy=None): 975*cda5da8dSAndroid Build Coastguard Worker if policy is None: 976*cda5da8dSAndroid Build Coastguard Worker from email.policy import default 977*cda5da8dSAndroid Build Coastguard Worker policy = default 978*cda5da8dSAndroid Build Coastguard Worker super().__init__(policy) 979*cda5da8dSAndroid Build Coastguard Worker 980*cda5da8dSAndroid Build Coastguard Worker 981*cda5da8dSAndroid Build Coastguard Worker def as_string(self, unixfrom=False, maxheaderlen=None, policy=None): 982*cda5da8dSAndroid Build Coastguard Worker """Return the entire formatted message as a string. 983*cda5da8dSAndroid Build Coastguard Worker 984*cda5da8dSAndroid Build Coastguard Worker Optional 'unixfrom', when true, means include the Unix From_ envelope 985*cda5da8dSAndroid Build Coastguard Worker header. maxheaderlen is retained for backward compatibility with the 986*cda5da8dSAndroid Build Coastguard Worker base Message class, but defaults to None, meaning that the policy value 987*cda5da8dSAndroid Build Coastguard Worker for max_line_length controls the header maximum length. 'policy' is 988*cda5da8dSAndroid Build Coastguard Worker passed to the Generator instance used to serialize the message; if it 989*cda5da8dSAndroid Build Coastguard Worker is not specified the policy associated with the message instance is 990*cda5da8dSAndroid Build Coastguard Worker used. 991*cda5da8dSAndroid Build Coastguard Worker """ 992*cda5da8dSAndroid Build Coastguard Worker policy = self.policy if policy is None else policy 993*cda5da8dSAndroid Build Coastguard Worker if maxheaderlen is None: 994*cda5da8dSAndroid Build Coastguard Worker maxheaderlen = policy.max_line_length 995*cda5da8dSAndroid Build Coastguard Worker return super().as_string(unixfrom, maxheaderlen, policy) 996*cda5da8dSAndroid Build Coastguard Worker 997*cda5da8dSAndroid Build Coastguard Worker def __str__(self): 998*cda5da8dSAndroid Build Coastguard Worker return self.as_string(policy=self.policy.clone(utf8=True)) 999*cda5da8dSAndroid Build Coastguard Worker 1000*cda5da8dSAndroid Build Coastguard Worker def is_attachment(self): 1001*cda5da8dSAndroid Build Coastguard Worker c_d = self.get('content-disposition') 1002*cda5da8dSAndroid Build Coastguard Worker return False if c_d is None else c_d.content_disposition == 'attachment' 1003*cda5da8dSAndroid Build Coastguard Worker 1004*cda5da8dSAndroid Build Coastguard Worker def _find_body(self, part, preferencelist): 1005*cda5da8dSAndroid Build Coastguard Worker if part.is_attachment(): 1006*cda5da8dSAndroid Build Coastguard Worker return 1007*cda5da8dSAndroid Build Coastguard Worker maintype, subtype = part.get_content_type().split('/') 1008*cda5da8dSAndroid Build Coastguard Worker if maintype == 'text': 1009*cda5da8dSAndroid Build Coastguard Worker if subtype in preferencelist: 1010*cda5da8dSAndroid Build Coastguard Worker yield (preferencelist.index(subtype), part) 1011*cda5da8dSAndroid Build Coastguard Worker return 1012*cda5da8dSAndroid Build Coastguard Worker if maintype != 'multipart' or not self.is_multipart(): 1013*cda5da8dSAndroid Build Coastguard Worker return 1014*cda5da8dSAndroid Build Coastguard Worker if subtype != 'related': 1015*cda5da8dSAndroid Build Coastguard Worker for subpart in part.iter_parts(): 1016*cda5da8dSAndroid Build Coastguard Worker yield from self._find_body(subpart, preferencelist) 1017*cda5da8dSAndroid Build Coastguard Worker return 1018*cda5da8dSAndroid Build Coastguard Worker if 'related' in preferencelist: 1019*cda5da8dSAndroid Build Coastguard Worker yield (preferencelist.index('related'), part) 1020*cda5da8dSAndroid Build Coastguard Worker candidate = None 1021*cda5da8dSAndroid Build Coastguard Worker start = part.get_param('start') 1022*cda5da8dSAndroid Build Coastguard Worker if start: 1023*cda5da8dSAndroid Build Coastguard Worker for subpart in part.iter_parts(): 1024*cda5da8dSAndroid Build Coastguard Worker if subpart['content-id'] == start: 1025*cda5da8dSAndroid Build Coastguard Worker candidate = subpart 1026*cda5da8dSAndroid Build Coastguard Worker break 1027*cda5da8dSAndroid Build Coastguard Worker if candidate is None: 1028*cda5da8dSAndroid Build Coastguard Worker subparts = part.get_payload() 1029*cda5da8dSAndroid Build Coastguard Worker candidate = subparts[0] if subparts else None 1030*cda5da8dSAndroid Build Coastguard Worker if candidate is not None: 1031*cda5da8dSAndroid Build Coastguard Worker yield from self._find_body(candidate, preferencelist) 1032*cda5da8dSAndroid Build Coastguard Worker 1033*cda5da8dSAndroid Build Coastguard Worker def get_body(self, preferencelist=('related', 'html', 'plain')): 1034*cda5da8dSAndroid Build Coastguard Worker """Return best candidate mime part for display as 'body' of message. 1035*cda5da8dSAndroid Build Coastguard Worker 1036*cda5da8dSAndroid Build Coastguard Worker Do a depth first search, starting with self, looking for the first part 1037*cda5da8dSAndroid Build Coastguard Worker matching each of the items in preferencelist, and return the part 1038*cda5da8dSAndroid Build Coastguard Worker corresponding to the first item that has a match, or None if no items 1039*cda5da8dSAndroid Build Coastguard Worker have a match. If 'related' is not included in preferencelist, consider 1040*cda5da8dSAndroid Build Coastguard Worker the root part of any multipart/related encountered as a candidate 1041*cda5da8dSAndroid Build Coastguard Worker match. Ignore parts with 'Content-Disposition: attachment'. 1042*cda5da8dSAndroid Build Coastguard Worker """ 1043*cda5da8dSAndroid Build Coastguard Worker best_prio = len(preferencelist) 1044*cda5da8dSAndroid Build Coastguard Worker body = None 1045*cda5da8dSAndroid Build Coastguard Worker for prio, part in self._find_body(self, preferencelist): 1046*cda5da8dSAndroid Build Coastguard Worker if prio < best_prio: 1047*cda5da8dSAndroid Build Coastguard Worker best_prio = prio 1048*cda5da8dSAndroid Build Coastguard Worker body = part 1049*cda5da8dSAndroid Build Coastguard Worker if prio == 0: 1050*cda5da8dSAndroid Build Coastguard Worker break 1051*cda5da8dSAndroid Build Coastguard Worker return body 1052*cda5da8dSAndroid Build Coastguard Worker 1053*cda5da8dSAndroid Build Coastguard Worker _body_types = {('text', 'plain'), 1054*cda5da8dSAndroid Build Coastguard Worker ('text', 'html'), 1055*cda5da8dSAndroid Build Coastguard Worker ('multipart', 'related'), 1056*cda5da8dSAndroid Build Coastguard Worker ('multipart', 'alternative')} 1057*cda5da8dSAndroid Build Coastguard Worker def iter_attachments(self): 1058*cda5da8dSAndroid Build Coastguard Worker """Return an iterator over the non-main parts of a multipart. 1059*cda5da8dSAndroid Build Coastguard Worker 1060*cda5da8dSAndroid Build Coastguard Worker Skip the first of each occurrence of text/plain, text/html, 1061*cda5da8dSAndroid Build Coastguard Worker multipart/related, or multipart/alternative in the multipart (unless 1062*cda5da8dSAndroid Build Coastguard Worker they have a 'Content-Disposition: attachment' header) and include all 1063*cda5da8dSAndroid Build Coastguard Worker remaining subparts in the returned iterator. When applied to a 1064*cda5da8dSAndroid Build Coastguard Worker multipart/related, return all parts except the root part. Return an 1065*cda5da8dSAndroid Build Coastguard Worker empty iterator when applied to a multipart/alternative or a 1066*cda5da8dSAndroid Build Coastguard Worker non-multipart. 1067*cda5da8dSAndroid Build Coastguard Worker """ 1068*cda5da8dSAndroid Build Coastguard Worker maintype, subtype = self.get_content_type().split('/') 1069*cda5da8dSAndroid Build Coastguard Worker if maintype != 'multipart' or subtype == 'alternative': 1070*cda5da8dSAndroid Build Coastguard Worker return 1071*cda5da8dSAndroid Build Coastguard Worker payload = self.get_payload() 1072*cda5da8dSAndroid Build Coastguard Worker # Certain malformed messages can have content type set to `multipart/*` 1073*cda5da8dSAndroid Build Coastguard Worker # but still have single part body, in which case payload.copy() can 1074*cda5da8dSAndroid Build Coastguard Worker # fail with AttributeError. 1075*cda5da8dSAndroid Build Coastguard Worker try: 1076*cda5da8dSAndroid Build Coastguard Worker parts = payload.copy() 1077*cda5da8dSAndroid Build Coastguard Worker except AttributeError: 1078*cda5da8dSAndroid Build Coastguard Worker # payload is not a list, it is most probably a string. 1079*cda5da8dSAndroid Build Coastguard Worker return 1080*cda5da8dSAndroid Build Coastguard Worker 1081*cda5da8dSAndroid Build Coastguard Worker if maintype == 'multipart' and subtype == 'related': 1082*cda5da8dSAndroid Build Coastguard Worker # For related, we treat everything but the root as an attachment. 1083*cda5da8dSAndroid Build Coastguard Worker # The root may be indicated by 'start'; if there's no start or we 1084*cda5da8dSAndroid Build Coastguard Worker # can't find the named start, treat the first subpart as the root. 1085*cda5da8dSAndroid Build Coastguard Worker start = self.get_param('start') 1086*cda5da8dSAndroid Build Coastguard Worker if start: 1087*cda5da8dSAndroid Build Coastguard Worker found = False 1088*cda5da8dSAndroid Build Coastguard Worker attachments = [] 1089*cda5da8dSAndroid Build Coastguard Worker for part in parts: 1090*cda5da8dSAndroid Build Coastguard Worker if part.get('content-id') == start: 1091*cda5da8dSAndroid Build Coastguard Worker found = True 1092*cda5da8dSAndroid Build Coastguard Worker else: 1093*cda5da8dSAndroid Build Coastguard Worker attachments.append(part) 1094*cda5da8dSAndroid Build Coastguard Worker if found: 1095*cda5da8dSAndroid Build Coastguard Worker yield from attachments 1096*cda5da8dSAndroid Build Coastguard Worker return 1097*cda5da8dSAndroid Build Coastguard Worker parts.pop(0) 1098*cda5da8dSAndroid Build Coastguard Worker yield from parts 1099*cda5da8dSAndroid Build Coastguard Worker return 1100*cda5da8dSAndroid Build Coastguard Worker # Otherwise we more or less invert the remaining logic in get_body. 1101*cda5da8dSAndroid Build Coastguard Worker # This only really works in edge cases (ex: non-text related or 1102*cda5da8dSAndroid Build Coastguard Worker # alternatives) if the sending agent sets content-disposition. 1103*cda5da8dSAndroid Build Coastguard Worker seen = [] # Only skip the first example of each candidate type. 1104*cda5da8dSAndroid Build Coastguard Worker for part in parts: 1105*cda5da8dSAndroid Build Coastguard Worker maintype, subtype = part.get_content_type().split('/') 1106*cda5da8dSAndroid Build Coastguard Worker if ((maintype, subtype) in self._body_types and 1107*cda5da8dSAndroid Build Coastguard Worker not part.is_attachment() and subtype not in seen): 1108*cda5da8dSAndroid Build Coastguard Worker seen.append(subtype) 1109*cda5da8dSAndroid Build Coastguard Worker continue 1110*cda5da8dSAndroid Build Coastguard Worker yield part 1111*cda5da8dSAndroid Build Coastguard Worker 1112*cda5da8dSAndroid Build Coastguard Worker def iter_parts(self): 1113*cda5da8dSAndroid Build Coastguard Worker """Return an iterator over all immediate subparts of a multipart. 1114*cda5da8dSAndroid Build Coastguard Worker 1115*cda5da8dSAndroid Build Coastguard Worker Return an empty iterator for a non-multipart. 1116*cda5da8dSAndroid Build Coastguard Worker """ 1117*cda5da8dSAndroid Build Coastguard Worker if self.is_multipart(): 1118*cda5da8dSAndroid Build Coastguard Worker yield from self.get_payload() 1119*cda5da8dSAndroid Build Coastguard Worker 1120*cda5da8dSAndroid Build Coastguard Worker def get_content(self, *args, content_manager=None, **kw): 1121*cda5da8dSAndroid Build Coastguard Worker if content_manager is None: 1122*cda5da8dSAndroid Build Coastguard Worker content_manager = self.policy.content_manager 1123*cda5da8dSAndroid Build Coastguard Worker return content_manager.get_content(self, *args, **kw) 1124*cda5da8dSAndroid Build Coastguard Worker 1125*cda5da8dSAndroid Build Coastguard Worker def set_content(self, *args, content_manager=None, **kw): 1126*cda5da8dSAndroid Build Coastguard Worker if content_manager is None: 1127*cda5da8dSAndroid Build Coastguard Worker content_manager = self.policy.content_manager 1128*cda5da8dSAndroid Build Coastguard Worker content_manager.set_content(self, *args, **kw) 1129*cda5da8dSAndroid Build Coastguard Worker 1130*cda5da8dSAndroid Build Coastguard Worker def _make_multipart(self, subtype, disallowed_subtypes, boundary): 1131*cda5da8dSAndroid Build Coastguard Worker if self.get_content_maintype() == 'multipart': 1132*cda5da8dSAndroid Build Coastguard Worker existing_subtype = self.get_content_subtype() 1133*cda5da8dSAndroid Build Coastguard Worker disallowed_subtypes = disallowed_subtypes + (subtype,) 1134*cda5da8dSAndroid Build Coastguard Worker if existing_subtype in disallowed_subtypes: 1135*cda5da8dSAndroid Build Coastguard Worker raise ValueError("Cannot convert {} to {}".format( 1136*cda5da8dSAndroid Build Coastguard Worker existing_subtype, subtype)) 1137*cda5da8dSAndroid Build Coastguard Worker keep_headers = [] 1138*cda5da8dSAndroid Build Coastguard Worker part_headers = [] 1139*cda5da8dSAndroid Build Coastguard Worker for name, value in self._headers: 1140*cda5da8dSAndroid Build Coastguard Worker if name.lower().startswith('content-'): 1141*cda5da8dSAndroid Build Coastguard Worker part_headers.append((name, value)) 1142*cda5da8dSAndroid Build Coastguard Worker else: 1143*cda5da8dSAndroid Build Coastguard Worker keep_headers.append((name, value)) 1144*cda5da8dSAndroid Build Coastguard Worker if part_headers: 1145*cda5da8dSAndroid Build Coastguard Worker # There is existing content, move it to the first subpart. 1146*cda5da8dSAndroid Build Coastguard Worker part = type(self)(policy=self.policy) 1147*cda5da8dSAndroid Build Coastguard Worker part._headers = part_headers 1148*cda5da8dSAndroid Build Coastguard Worker part._payload = self._payload 1149*cda5da8dSAndroid Build Coastguard Worker self._payload = [part] 1150*cda5da8dSAndroid Build Coastguard Worker else: 1151*cda5da8dSAndroid Build Coastguard Worker self._payload = [] 1152*cda5da8dSAndroid Build Coastguard Worker self._headers = keep_headers 1153*cda5da8dSAndroid Build Coastguard Worker self['Content-Type'] = 'multipart/' + subtype 1154*cda5da8dSAndroid Build Coastguard Worker if boundary is not None: 1155*cda5da8dSAndroid Build Coastguard Worker self.set_param('boundary', boundary) 1156*cda5da8dSAndroid Build Coastguard Worker 1157*cda5da8dSAndroid Build Coastguard Worker def make_related(self, boundary=None): 1158*cda5da8dSAndroid Build Coastguard Worker self._make_multipart('related', ('alternative', 'mixed'), boundary) 1159*cda5da8dSAndroid Build Coastguard Worker 1160*cda5da8dSAndroid Build Coastguard Worker def make_alternative(self, boundary=None): 1161*cda5da8dSAndroid Build Coastguard Worker self._make_multipart('alternative', ('mixed',), boundary) 1162*cda5da8dSAndroid Build Coastguard Worker 1163*cda5da8dSAndroid Build Coastguard Worker def make_mixed(self, boundary=None): 1164*cda5da8dSAndroid Build Coastguard Worker self._make_multipart('mixed', (), boundary) 1165*cda5da8dSAndroid Build Coastguard Worker 1166*cda5da8dSAndroid Build Coastguard Worker def _add_multipart(self, _subtype, *args, _disp=None, **kw): 1167*cda5da8dSAndroid Build Coastguard Worker if (self.get_content_maintype() != 'multipart' or 1168*cda5da8dSAndroid Build Coastguard Worker self.get_content_subtype() != _subtype): 1169*cda5da8dSAndroid Build Coastguard Worker getattr(self, 'make_' + _subtype)() 1170*cda5da8dSAndroid Build Coastguard Worker part = type(self)(policy=self.policy) 1171*cda5da8dSAndroid Build Coastguard Worker part.set_content(*args, **kw) 1172*cda5da8dSAndroid Build Coastguard Worker if _disp and 'content-disposition' not in part: 1173*cda5da8dSAndroid Build Coastguard Worker part['Content-Disposition'] = _disp 1174*cda5da8dSAndroid Build Coastguard Worker self.attach(part) 1175*cda5da8dSAndroid Build Coastguard Worker 1176*cda5da8dSAndroid Build Coastguard Worker def add_related(self, *args, **kw): 1177*cda5da8dSAndroid Build Coastguard Worker self._add_multipart('related', *args, _disp='inline', **kw) 1178*cda5da8dSAndroid Build Coastguard Worker 1179*cda5da8dSAndroid Build Coastguard Worker def add_alternative(self, *args, **kw): 1180*cda5da8dSAndroid Build Coastguard Worker self._add_multipart('alternative', *args, **kw) 1181*cda5da8dSAndroid Build Coastguard Worker 1182*cda5da8dSAndroid Build Coastguard Worker def add_attachment(self, *args, **kw): 1183*cda5da8dSAndroid Build Coastguard Worker self._add_multipart('mixed', *args, _disp='attachment', **kw) 1184*cda5da8dSAndroid Build Coastguard Worker 1185*cda5da8dSAndroid Build Coastguard Worker def clear(self): 1186*cda5da8dSAndroid Build Coastguard Worker self._headers = [] 1187*cda5da8dSAndroid Build Coastguard Worker self._payload = None 1188*cda5da8dSAndroid Build Coastguard Worker 1189*cda5da8dSAndroid Build Coastguard Worker def clear_content(self): 1190*cda5da8dSAndroid Build Coastguard Worker self._headers = [(n, v) for n, v in self._headers 1191*cda5da8dSAndroid Build Coastguard Worker if not n.lower().startswith('content-')] 1192*cda5da8dSAndroid Build Coastguard Worker self._payload = None 1193*cda5da8dSAndroid Build Coastguard Worker 1194*cda5da8dSAndroid Build Coastguard Worker 1195*cda5da8dSAndroid Build Coastguard Workerclass EmailMessage(MIMEPart): 1196*cda5da8dSAndroid Build Coastguard Worker 1197*cda5da8dSAndroid Build Coastguard Worker def set_content(self, *args, **kw): 1198*cda5da8dSAndroid Build Coastguard Worker super().set_content(*args, **kw) 1199*cda5da8dSAndroid Build Coastguard Worker if 'MIME-Version' not in self: 1200*cda5da8dSAndroid Build Coastguard Worker self['MIME-Version'] = '1.0' 1201