comparison planemo/lib/python3.7/site-packages/future/backports/email/message.py @ 0:d30785e31577 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:18:57 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d30785e31577
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."""
7 from __future__ import absolute_import, division, unicode_literals
8 from future.builtins import list, range, str, zip
9
10 __all__ = ['Message']
11
12 import re
13 import uu
14 import base64
15 import binascii
16 from io import BytesIO, StringIO
17
18 # Intrapackage imports
19 from future.utils import as_native_str
20 from future.backports.email import utils
21 from future.backports.email import errors
22 from future.backports.email._policybase import compat32
23 from future.backports.email import charset as _charset
24 from future.backports.email._encoded_words import decode_b
25 Charset = _charset.Charset
26
27 SEMISPACE = '; '
28
29 # Regular expression that matches `special' characters in parameters, the
30 # existence of which force quoting of the parameter value.
31 tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
32
33
34 def _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
44 def _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
78 def _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
98 def _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
109 class 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