1# -*- coding: utf-8 -*-
2# Copyright (C) 2001-2007 Python Software Foundation
3# Author: Barry Warsaw
4# Contact: email-sig@python.org
5
6"""Basic message object for the email package object model."""
7from __future__ import absolute_import, division, unicode_literals
8from future.builtins import list, range, str, zip
9
10__all__ = ['Message']
11
12import re
13import uu
14import base64
15import binascii
16from io import BytesIO, StringIO
17
18# Intrapackage imports
19from future.utils import as_native_str
20from future.backports.email import utils
21from future.backports.email import errors
22from future.backports.email._policybase import compat32
23from future.backports.email import charset as _charset
24from future.backports.email._encoded_words import decode_b
25Charset = _charset.Charset
26
27SEMISPACE = '; '
28
29# Regular expression that matches `special' characters in parameters, the
30# existence of which force quoting of the parameter value.
31tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
32
33
34def _splitparam(param):
35    # Split header parameters.  BAW: this may be too simple.  It isn't
36    # strictly RFC 2045 (section 5.1) compliant, but it catches most headers
37    # found in the wild.  We may eventually need a full fledged parser.
38    # RDM: we might have a Header here; for now just stringify it.
39    a, sep, b = str(param).partition(';')
40    if not sep:
41        return a.strip(), None
42    return a.strip(), b.strip()
43
44def _formatparam(param, value=None, quote=True):
45    """Convenience function to format and return a key=value pair.
46
47    This will quote the value if needed or if quote is true.  If value is a
48    three tuple (charset, language, value), it will be encoded according
49    to RFC2231 rules.  If it contains non-ascii characters it will likewise
50    be encoded according to RFC2231 rules, using the utf-8 charset and
51    a null language.
52    """
53    if value is not None and len(value) > 0:
54        # A tuple is used for RFC 2231 encoded parameter values where items
55        # are (charset, language, value).  charset is a string, not a Charset
56        # instance.  RFC 2231 encoded values are never quoted, per RFC.
57        if isinstance(value, tuple):
58            # Encode as per RFC 2231
59            param += '*'
60            value = utils.encode_rfc2231(value[2], value[0], value[1])
61            return '%s=%s' % (param, value)
62        else:
63            try:
64                value.encode('ascii')
65            except UnicodeEncodeError:
66                param += '*'
67                value = utils.encode_rfc2231(value, 'utf-8', '')
68                return '%s=%s' % (param, value)
69        # BAW: Please check this.  I think that if quote is set it should
70        # force quoting even if not necessary.
71        if quote or tspecials.search(value):
72            return '%s="%s"' % (param, utils.quote(value))
73        else:
74            return '%s=%s' % (param, value)
75    else:
76        return param
77
78def _parseparam(s):
79    # RDM This might be a Header, so for now stringify it.
80    s = ';' + str(s)
81    plist = []
82    while s[:1] == ';':
83        s = s[1:]
84        end = s.find(';')
85        while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
86            end = s.find(';', end + 1)
87        if end < 0:
88            end = len(s)
89        f = s[:end]
90        if '=' in f:
91            i = f.index('=')
92            f = f[:i].strip().lower() + '=' + f[i+1:].strip()
93        plist.append(f.strip())
94        s = s[end:]
95    return plist
96
97
98def _unquotevalue(value):
99    # This is different than utils.collapse_rfc2231_value() because it doesn't
100    # try to convert the value to a unicode.  Message.get_param() and
101    # Message.get_params() are both currently defined to return the tuple in
102    # the face of RFC 2231 parameters.
103    if isinstance(value, tuple):
104        return value[0], value[1], utils.unquote(value[2])
105    else:
106        return utils.unquote(value)
107
108
109class Message(object):
110    """Basic message object.
111
112    A message object is defined as something that has a bunch of RFC 2822
113    headers and a payload.  It may optionally have an envelope header
114    (a.k.a. Unix-From or From_ header).  If the message is a container (i.e. a
115    multipart or a message/rfc822), then the payload is a list of Message
116    objects, otherwise it is a string.
117
118    Message objects implement part of the `mapping' interface, which assumes
119    there is exactly one occurrence of the header per message.  Some headers
120    do in fact appear multiple times (e.g. Received) and for those headers,
121    you must use the explicit API to set or get all the headers.  Not all of
122    the mapping methods are implemented.
123    """
124    def __init__(self, policy=compat32):
125        self.policy = policy
126        self._headers = list()
127        self._unixfrom = None
128        self._payload = None
129        self._charset = None
130        # Defaults for multipart messages
131        self.preamble = self.epilogue = None
132        self.defects = []
133        # Default content type
134        self._default_type = 'text/plain'
135
136    @as_native_str(encoding='utf-8')
137    def __str__(self):
138        """Return the entire formatted message as a string.
139        This includes the headers, body, and envelope header.
140        """
141        return self.as_string()
142
143    def as_string(self, unixfrom=False, maxheaderlen=0):
144        """Return the entire formatted message as a (unicode) string.
145        Optional `unixfrom' when True, means include the Unix From_ envelope
146        header.
147
148        This is a convenience method and may not generate the message exactly
149        as you intend.  For more flexibility, use the flatten() method of a
150        Generator instance.
151        """
152        from future.backports.email.generator import Generator
153        fp = StringIO()
154        g = Generator(fp, mangle_from_=False, maxheaderlen=maxheaderlen)
155        g.flatten(self, unixfrom=unixfrom)
156        return fp.getvalue()
157
158    def is_multipart(self):
159        """Return True if the message consists of multiple parts."""
160        return isinstance(self._payload, list)
161
162    #
163    # Unix From_ line
164    #
165    def set_unixfrom(self, unixfrom):
166        self._unixfrom = unixfrom
167
168    def get_unixfrom(self):
169        return self._unixfrom
170
171    #
172    # Payload manipulation.
173    #
174    def attach(self, payload):
175        """Add the given payload to the current payload.
176
177        The current payload will always be a list of objects after this method
178        is called.  If you want to set the payload to a scalar object, use
179        set_payload() instead.
180        """
181        if self._payload is None:
182            self._payload = [payload]
183        else:
184            self._payload.append(payload)
185
186    def get_payload(self, i=None, decode=False):
187        """Return a reference to the payload.
188
189        The payload will either be a list object or a string.  If you mutate
190        the list object, you modify the message's payload in place.  Optional
191        i returns that index into the payload.
192
193        Optional decode is a flag indicating whether the payload should be
194        decoded or not, according to the Content-Transfer-Encoding header
195        (default is False).
196
197        When True and the message is not a multipart, the payload will be
198        decoded if this header's value is `quoted-printable' or `base64'.  If
199        some other encoding is used, or the header is missing, or if the
200        payload has bogus data (i.e. bogus base64 or uuencoded data), the
201        payload is returned as-is.
202
203        If the message is a multipart and the decode flag is True, then None
204        is returned.
205        """
206        # Here is the logic table for this code, based on the email5.0.0 code:
207        #   i     decode  is_multipart  result
208        # ------  ------  ------------  ------------------------------
209        #  None   True    True          None
210        #   i     True    True          None
211        #  None   False   True          _payload (a list)
212        #   i     False   True          _payload element i (a Message)
213        #   i     False   False         error (not a list)
214        #   i     True    False         error (not a list)
215        #  None   False   False         _payload
216        #  None   True    False         _payload decoded (bytes)
217        # Note that Barry planned to factor out the 'decode' case, but that
218        # isn't so easy now that we handle the 8 bit data, which needs to be
219        # converted in both the decode and non-decode path.
220        if self.is_multipart():
221            if decode:
222                return None
223            if i is None:
224                return self._payload
225            else:
226                return self._payload[i]
227        # For backward compatibility, Use isinstance and this error message
228        # instead of the more logical is_multipart test.
229        if i is not None and not isinstance(self._payload, list):
230            raise TypeError('Expected list, got %s' % type(self._payload))
231        payload = self._payload
232        # cte might be a Header, so for now stringify it.
233        cte = str(self.get('content-transfer-encoding', '')).lower()
234        # payload may be bytes here.
235        if isinstance(payload, str):
236            payload = str(payload)    # for Python-Future, so surrogateescape works
237            if utils._has_surrogates(payload):
238                bpayload = payload.encode('ascii', 'surrogateescape')
239                if not decode:
240                    try:
241                        payload = bpayload.decode(self.get_param('charset', 'ascii'), 'replace')
242                    except LookupError:
243                        payload = bpayload.decode('ascii', 'replace')
244            elif decode:
245                try:
246                    bpayload = payload.encode('ascii')
247                except UnicodeError:
248                    # This won't happen for RFC compliant messages (messages
249                    # containing only ASCII codepoints in the unicode input).
250                    # If it does happen, turn the string into bytes in a way
251                    # guaranteed not to fail.
252                    bpayload = payload.encode('raw-unicode-escape')
253        if not decode:
254            return payload
255        if cte == 'quoted-printable':
256            return utils._qdecode(bpayload)
257        elif cte == 'base64':
258            # XXX: this is a bit of a hack; decode_b should probably be factored
259            # out somewhere, but I haven't figured out where yet.
260            value, defects = decode_b(b''.join(bpayload.splitlines()))
261            for defect in defects:
262                self.policy.handle_defect(self, defect)
263            return value
264        elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
265            in_file = BytesIO(bpayload)
266            out_file = BytesIO()
267            try:
268                uu.decode(in_file, out_file, quiet=True)
269                return out_file.getvalue()
270            except uu.Error:
271                # Some decoding problem
272                return bpayload
273        if isinstance(payload, str):
274            return bpayload
275        return payload
276
277    def set_payload(self, payload, charset=None):
278        """Set the payload to the given value.
279
280        Optional charset sets the message's default character set.  See
281        set_charset() for details.
282        """
283        self._payload = payload
284        if charset is not None:
285            self.set_charset(charset)
286
287    def set_charset(self, charset):
288        """Set the charset of the payload to a given character set.
289
290        charset can be a Charset instance, a string naming a character set, or
291        None.  If it is a string it will be converted to a Charset instance.
292        If charset is None, the charset parameter will be removed from the
293        Content-Type field.  Anything else will generate a TypeError.
294
295        The message will be assumed to be of type text/* encoded with
296        charset.input_charset.  It will be converted to charset.output_charset
297        and encoded properly, if needed, when generating the plain text
298        representation of the message.  MIME headers (MIME-Version,
299        Content-Type, Content-Transfer-Encoding) will be added as needed.
300        """
301        if charset is None:
302            self.del_param('charset')
303            self._charset = None
304            return
305        if not isinstance(charset, Charset):
306            charset = Charset(charset)
307        self._charset = charset
308        if 'MIME-Version' not in self:
309            self.add_header('MIME-Version', '1.0')
310        if 'Content-Type' not in self:
311            self.add_header('Content-Type', 'text/plain',
312                            charset=charset.get_output_charset())
313        else:
314            self.set_param('charset', charset.get_output_charset())
315        if charset != charset.get_output_charset():
316            self._payload = charset.body_encode(self._payload)
317        if 'Content-Transfer-Encoding' not in self:
318            cte = charset.get_body_encoding()
319            try:
320                cte(self)
321            except TypeError:
322                self._payload = charset.body_encode(self._payload)
323                self.add_header('Content-Transfer-Encoding', cte)
324
325    def get_charset(self):
326        """Return the Charset instance associated with the message's payload.
327        """
328        return self._charset
329
330    #
331    # MAPPING INTERFACE (partial)
332    #
333    def __len__(self):
334        """Return the total number of headers, including duplicates."""
335        return len(self._headers)
336
337    def __getitem__(self, name):
338        """Get a header value.
339
340        Return None if the header is missing instead of raising an exception.
341
342        Note that if the header appeared multiple times, exactly which
343        occurrence gets returned is undefined.  Use get_all() to get all
344        the values matching a header field name.
345        """
346        return self.get(name)
347
348    def __setitem__(self, name, val):
349        """Set the value of a header.
350
351        Note: this does not overwrite an existing header with the same field
352        name.  Use __delitem__() first to delete any existing headers.
353        """
354        max_count = self.policy.header_max_count(name)
355        if max_count:
356            lname = name.lower()
357            found = 0
358            for k, v in self._headers:
359                if k.lower() == lname:
360                    found += 1
361                    if found >= max_count:
362                        raise ValueError("There may be at most {} {} headers "
363                                         "in a message".format(max_count, name))
364        self._headers.append(self.policy.header_store_parse(name, val))
365
366    def __delitem__(self, name):
367        """Delete all occurrences of a header, if present.
368
369        Does not raise an exception if the header is missing.
370        """
371        name = name.lower()
372        newheaders = list()
373        for k, v in self._headers:
374            if k.lower() != name:
375                newheaders.append((k, v))
376        self._headers = newheaders
377
378    def __contains__(self, name):
379        return name.lower() in [k.lower() for k, v in self._headers]
380
381    def __iter__(self):
382        for field, value in self._headers:
383            yield field
384
385    def keys(self):
386        """Return a list of all the message's header field names.
387
388        These will be sorted in the order they appeared in the original
389        message, or were added to the message, and may contain duplicates.
390        Any fields deleted and re-inserted are always appended to the header
391        list.
392        """
393        return [k for k, v in self._headers]
394
395    def values(self):
396        """Return a list of all the message's header values.
397
398        These will be sorted in the order they appeared in the original
399        message, or were added to the message, and may contain duplicates.
400        Any fields deleted and re-inserted are always appended to the header
401        list.
402        """
403        return [self.policy.header_fetch_parse(k, v)
404                for k, v in self._headers]
405
406    def items(self):
407        """Get all the message's header fields and values.
408
409        These will be sorted in the order they appeared in the original
410        message, or were added to the message, and may contain duplicates.
411        Any fields deleted and re-inserted are always appended to the header
412        list.
413        """
414        return [(k, self.policy.header_fetch_parse(k, v))
415                for k, v in self._headers]
416
417    def get(self, name, failobj=None):
418        """Get a header value.
419
420        Like __getitem__() but return failobj instead of None when the field
421        is missing.
422        """
423        name = name.lower()
424        for k, v in self._headers:
425            if k.lower() == name:
426                return self.policy.header_fetch_parse(k, v)
427        return failobj
428
429    #
430    # "Internal" methods (public API, but only intended for use by a parser
431    # or generator, not normal application code.
432    #
433
434    def set_raw(self, name, value):
435        """Store name and value in the model without modification.
436
437        This is an "internal" API, intended only for use by a parser.
438        """
439        self._headers.append((name, value))
440
441    def raw_items(self):
442        """Return the (name, value) header pairs without modification.
443
444        This is an "internal" API, intended only for use by a generator.
445        """
446        return iter(self._headers.copy())
447
448    #
449    # Additional useful stuff
450    #
451
452    def get_all(self, name, failobj=None):
453        """Return a list of all the values for the named field.
454
455        These will be sorted in the order they appeared in the original
456        message, and may contain duplicates.  Any fields deleted and
457        re-inserted are always appended to the header list.
458
459        If no such fields exist, failobj is returned (defaults to None).
460        """
461        values = []
462        name = name.lower()
463        for k, v in self._headers:
464            if k.lower() == name:
465                values.append(self.policy.header_fetch_parse(k, v))
466        if not values:
467            return failobj
468        return values
469
470    def add_header(self, _name, _value, **_params):
471        """Extended header setting.
472
473        name is the header field to add.  keyword arguments can be used to set
474        additional parameters for the header field, with underscores converted
475        to dashes.  Normally the parameter will be added as key="value" unless
476        value is None, in which case only the key will be added.  If a
477        parameter value contains non-ASCII characters it can be specified as a
478        three-tuple of (charset, language, value), in which case it will be
479        encoded according to RFC2231 rules.  Otherwise it will be encoded using
480        the utf-8 charset and a language of ''.
481
482        Examples:
483
484        msg.add_header('content-disposition', 'attachment', filename='bud.gif')
485        msg.add_header('content-disposition', 'attachment',
486                       filename=('utf-8', '', 'Fußballer.ppt'))
487        msg.add_header('content-disposition', 'attachment',
488                       filename='Fußballer.ppt'))
489        """
490        parts = []
491        for k, v in _params.items():
492            if v is None:
493                parts.append(k.replace('_', '-'))
494            else:
495                parts.append(_formatparam(k.replace('_', '-'), v))
496        if _value is not None:
497            parts.insert(0, _value)
498        self[_name] = SEMISPACE.join(parts)
499
500    def replace_header(self, _name, _value):
501        """Replace a header.
502
503        Replace the first matching header found in the message, retaining
504        header order and case.  If no matching header was found, a KeyError is
505        raised.
506        """
507        _name = _name.lower()
508        for i, (k, v) in zip(range(len(self._headers)), self._headers):
509            if k.lower() == _name:
510                self._headers[i] = self.policy.header_store_parse(k, _value)
511                break
512        else:
513            raise KeyError(_name)
514
515    #
516    # Use these three methods instead of the three above.
517    #
518
519    def get_content_type(self):
520        """Return the message's content type.
521
522        The returned string is coerced to lower case of the form
523        `maintype/subtype'.  If there was no Content-Type header in the
524        message, the default type as given by get_default_type() will be
525        returned.  Since according to RFC 2045, messages always have a default
526        type this will always return a value.
527
528        RFC 2045 defines a message's default type to be text/plain unless it
529        appears inside a multipart/digest container, in which case it would be
530        message/rfc822.
531        """
532        missing = object()
533        value = self.get('content-type', missing)
534        if value is missing:
535            # This should have no parameters
536            return self.get_default_type()
537        ctype = _splitparam(value)[0].lower()
538        # RFC 2045, section 5.2 says if its invalid, use text/plain
539        if ctype.count('/') != 1:
540            return 'text/plain'
541        return ctype
542
543    def get_content_maintype(self):
544        """Return the message's main content type.
545
546        This is the `maintype' part of the string returned by
547        get_content_type().
548        """
549        ctype = self.get_content_type()
550        return ctype.split('/')[0]
551
552    def get_content_subtype(self):
553        """Returns the message's sub-content type.
554
555        This is the `subtype' part of the string returned by
556        get_content_type().
557        """
558        ctype = self.get_content_type()
559        return ctype.split('/')[1]
560
561    def get_default_type(self):
562        """Return the `default' content type.
563
564        Most messages have a default content type of text/plain, except for
565        messages that are subparts of multipart/digest containers.  Such
566        subparts have a default content type of message/rfc822.
567        """
568        return self._default_type
569
570    def set_default_type(self, ctype):
571        """Set the `default' content type.
572
573        ctype should be either "text/plain" or "message/rfc822", although this
574        is not enforced.  The default content type is not stored in the
575        Content-Type header.
576        """
577        self._default_type = ctype
578
579    def _get_params_preserve(self, failobj, header):
580        # Like get_params() but preserves the quoting of values.  BAW:
581        # should this be part of the public interface?
582        missing = object()
583        value = self.get(header, missing)
584        if value is missing:
585            return failobj
586        params = []
587        for p in _parseparam(value):
588            try:
589                name, val = p.split('=', 1)
590                name = name.strip()
591                val = val.strip()
592            except ValueError:
593                # Must have been a bare attribute
594                name = p.strip()
595                val = ''
596            params.append((name, val))
597        params = utils.decode_params(params)
598        return params
599
600    def get_params(self, failobj=None, header='content-type', unquote=True):
601        """Return the message's Content-Type parameters, as a list.
602
603        The elements of the returned list are 2-tuples of key/value pairs, as
604        split on the `=' sign.  The left hand side of the `=' is the key,
605        while the right hand side is the value.  If there is no `=' sign in
606        the parameter the value is the empty string.  The value is as
607        described in the get_param() method.
608
609        Optional failobj is the object to return if there is no Content-Type
610        header.  Optional header is the header to search instead of
611        Content-Type.  If unquote is True, the value is unquoted.
612        """
613        missing = object()
614        params = self._get_params_preserve(missing, header)
615        if params is missing:
616            return failobj
617        if unquote:
618            return [(k, _unquotevalue(v)) for k, v in params]
619        else:
620            return params
621
622    def get_param(self, param, failobj=None, header='content-type',
623                  unquote=True):
624        """Return the parameter value if found in the Content-Type header.
625
626        Optional failobj is the object to return if there is no Content-Type
627        header, or the Content-Type header has no such parameter.  Optional
628        header is the header to search instead of Content-Type.
629
630        Parameter keys are always compared case insensitively.  The return
631        value can either be a string, or a 3-tuple if the parameter was RFC
632        2231 encoded.  When it's a 3-tuple, the elements of the value are of
633        the form (CHARSET, LANGUAGE, VALUE).  Note that both CHARSET and
634        LANGUAGE can be None, in which case you should consider VALUE to be
635        encoded in the us-ascii charset.  You can usually ignore LANGUAGE.
636        The parameter value (either the returned string, or the VALUE item in
637        the 3-tuple) is always unquoted, unless unquote is set to False.
638
639        If your application doesn't care whether the parameter was RFC 2231
640        encoded, it can turn the return value into a string as follows:
641
642            param = msg.get_param('foo')
643            param = email.utils.collapse_rfc2231_value(rawparam)
644
645        """
646        if header not in self:
647            return failobj
648        for k, v in self._get_params_preserve(failobj, header):
649            if k.lower() == param.lower():
650                if unquote:
651                    return _unquotevalue(v)
652                else:
653                    return v
654        return failobj
655
656    def set_param(self, param, value, header='Content-Type', requote=True,
657                  charset=None, language=''):
658        """Set a parameter in the Content-Type header.
659
660        If the parameter already exists in the header, its value will be
661        replaced with the new value.
662
663        If header is Content-Type and has not yet been defined for this
664        message, it will be set to "text/plain" and the new parameter and
665        value will be appended as per RFC 2045.
666
667        An alternate header can specified in the header argument, and all
668        parameters will be quoted as necessary unless requote is False.
669
670        If charset is specified, the parameter will be encoded according to RFC
671        2231.  Optional language specifies the RFC 2231 language, defaulting
672        to the empty string.  Both charset and language should be strings.
673        """
674        if not isinstance(value, tuple) and charset:
675            value = (charset, language, value)
676
677        if header not in self and header.lower() == 'content-type':
678            ctype = 'text/plain'
679        else:
680            ctype = self.get(header)
681        if not self.get_param(param, header=header):
682            if not ctype:
683                ctype = _formatparam(param, value, requote)
684            else:
685                ctype = SEMISPACE.join(
686                    [ctype, _formatparam(param, value, requote)])
687        else:
688            ctype = ''
689            for old_param, old_value in self.get_params(header=header,
690                                                        unquote=requote):
691                append_param = ''
692                if old_param.lower() == param.lower():
693                    append_param = _formatparam(param, value, requote)
694                else:
695                    append_param = _formatparam(old_param, old_value, requote)
696                if not ctype:
697                    ctype = append_param
698                else:
699                    ctype = SEMISPACE.join([ctype, append_param])
700        if ctype != self.get(header):
701            del self[header]
702            self[header] = ctype
703
704    def del_param(self, param, header='content-type', requote=True):
705        """Remove the given parameter completely from the Content-Type header.
706
707        The header will be re-written in place without the parameter or its
708        value. All values will be quoted as necessary unless requote is
709        False.  Optional header specifies an alternative to the Content-Type
710        header.
711        """
712        if header not in self:
713            return
714        new_ctype = ''
715        for p, v in self.get_params(header=header, unquote=requote):
716            if p.lower() != param.lower():
717                if not new_ctype:
718                    new_ctype = _formatparam(p, v, requote)
719                else:
720                    new_ctype = SEMISPACE.join([new_ctype,
721                                                _formatparam(p, v, requote)])
722        if new_ctype != self.get(header):
723            del self[header]
724            self[header] = new_ctype
725
726    def set_type(self, type, header='Content-Type', requote=True):
727        """Set the main type and subtype for the Content-Type header.
728
729        type must be a string in the form "maintype/subtype", otherwise a
730        ValueError is raised.
731
732        This method replaces the Content-Type header, keeping all the
733        parameters in place.  If requote is False, this leaves the existing
734        header's quoting as is.  Otherwise, the parameters will be quoted (the
735        default).
736
737        An alternative header can be specified in the header argument.  When
738        the Content-Type header is set, we'll always also add a MIME-Version
739        header.
740        """
741        # BAW: should we be strict?
742        if not type.count('/') == 1:
743            raise ValueError
744        # Set the Content-Type, you get a MIME-Version
745        if header.lower() == 'content-type':
746            del self['mime-version']
747            self['MIME-Version'] = '1.0'
748        if header not in self:
749            self[header] = type
750            return
751        params = self.get_params(header=header, unquote=requote)
752        del self[header]
753        self[header] = type
754        # Skip the first param; it's the old type.
755        for p, v in params[1:]:
756            self.set_param(p, v, header, requote)
757
758    def get_filename(self, failobj=None):
759        """Return the filename associated with the payload if present.
760
761        The filename is extracted from the Content-Disposition header's
762        `filename' parameter, and it is unquoted.  If that header is missing
763        the `filename' parameter, this method falls back to looking for the
764        `name' parameter.
765        """
766        missing = object()
767        filename = self.get_param('filename', missing, 'content-disposition')
768        if filename is missing:
769            filename = self.get_param('name', missing, 'content-type')
770        if filename is missing:
771            return failobj
772        return utils.collapse_rfc2231_value(filename).strip()
773
774    def get_boundary(self, failobj=None):
775        """Return the boundary associated with the payload if present.
776
777        The boundary is extracted from the Content-Type header's `boundary'
778        parameter, and it is unquoted.
779        """
780        missing = object()
781        boundary = self.get_param('boundary', missing)
782        if boundary is missing:
783            return failobj
784        # RFC 2046 says that boundaries may begin but not end in w/s
785        return utils.collapse_rfc2231_value(boundary).rstrip()
786
787    def set_boundary(self, boundary):
788        """Set the boundary parameter in Content-Type to 'boundary'.
789
790        This is subtly different than deleting the Content-Type header and
791        adding a new one with a new boundary parameter via add_header().  The
792        main difference is that using the set_boundary() method preserves the
793        order of the Content-Type header in the original message.
794
795        HeaderParseError is raised if the message has no Content-Type header.
796        """
797        missing = object()
798        params = self._get_params_preserve(missing, 'content-type')
799        if params is missing:
800            # There was no Content-Type header, and we don't know what type
801            # to set it to, so raise an exception.
802            raise errors.HeaderParseError('No Content-Type header found')
803        newparams = list()
804        foundp = False
805        for pk, pv in params:
806            if pk.lower() == 'boundary':
807                newparams.append(('boundary', '"%s"' % boundary))
808                foundp = True
809            else:
810                newparams.append((pk, pv))
811        if not foundp:
812            # The original Content-Type header had no boundary attribute.
813            # Tack one on the end.  BAW: should we raise an exception
814            # instead???
815            newparams.append(('boundary', '"%s"' % boundary))
816        # Replace the existing Content-Type header with the new value
817        newheaders = list()
818        for h, v in self._headers:
819            if h.lower() == 'content-type':
820                parts = list()
821                for k, v in newparams:
822                    if v == '':
823                        parts.append(k)
824                    else:
825                        parts.append('%s=%s' % (k, v))
826                val = SEMISPACE.join(parts)
827                newheaders.append(self.policy.header_store_parse(h, val))
828
829            else:
830                newheaders.append((h, v))
831        self._headers = newheaders
832
833    def get_content_charset(self, failobj=None):
834        """Return the charset parameter of the Content-Type header.
835
836        The returned string is always coerced to lower case.  If there is no
837        Content-Type header, or if that header has no charset parameter,
838        failobj is returned.
839        """
840        missing = object()
841        charset = self.get_param('charset', missing)
842        if charset is missing:
843            return failobj
844        if isinstance(charset, tuple):
845            # RFC 2231 encoded, so decode it, and it better end up as ascii.
846            pcharset = charset[0] or 'us-ascii'
847            try:
848                # LookupError will be raised if the charset isn't known to
849                # Python.  UnicodeError will be raised if the encoded text
850                # contains a character not in the charset.
851                as_bytes = charset[2].encode('raw-unicode-escape')
852                charset = str(as_bytes, pcharset)
853            except (LookupError, UnicodeError):
854                charset = charset[2]
855        # charset characters must be in us-ascii range
856        try:
857            charset.encode('us-ascii')
858        except UnicodeError:
859            return failobj
860        # RFC 2046, $4.1.2 says charsets are not case sensitive
861        return charset.lower()
862
863    def get_charsets(self, failobj=None):
864        """Return a list containing the charset(s) used in this message.
865
866        The returned list of items describes the Content-Type headers'
867        charset parameter for this message and all the subparts in its
868        payload.
869
870        Each item will either be a string (the value of the charset parameter
871        in the Content-Type header of that part) or the value of the
872        'failobj' parameter (defaults to None), if the part does not have a
873        main MIME type of "text", or the charset is not defined.
874
875        The list will contain one string for each part of the message, plus
876        one for the container message (i.e. self), so that a non-multipart
877        message will still return a list of length 1.
878        """
879        return [part.get_content_charset(failobj) for part in self.walk()]
880
881    # I.e. def walk(self): ...
882    from future.backports.email.iterators import walk
883