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