0001 # Copyright (C) 2001-2004 Python Software Foundation 0002 # Author: Barry Warsaw 0003 # Contact: email-sig@python.org 0004 0005 """Basic message object for the email package object model.""" 0006 0007 import re 0008 import uu 0009 import binascii 0010 import warnings 0011 from cStringIO import StringIO 0012 0013 # Intrapackage imports 0014 from email import Utils 0015 from email import Errors 0016 from email import Charset 0017 0018 SEMISPACE = '; ' 0019 0020 # Regular expression used to split header parameters. BAW: this may be too 0021 # simple. It isn't strictly RFC 2045 (section 5.1) compliant, but it catches 0022 # most headers found in the wild. We may eventually need a full fledged 0023 # parser eventually. 0024 paramre = re.compile(r'\s*;\s*') 0025 # Regular expression that matches `special' characters in parameters, the 0026 # existance of which force quoting of the parameter value. 0027 tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') 0028 0029 0030 0031 # Helper functions 0032 def _formatparam(param, value=None, quote=True): 0033 """Convenience function to format and return a key=value pair. 0034 0035 This will quote the value if needed or if quote is true. 0036 """ 0037 if value is not None and len(value) > 0: 0038 # A tuple is used for RFC 2231 encoded parameter values where items 0039 # are (charset, language, value). charset is a string, not a Charset 0040 # instance. 0041 if isinstance(value, tuple): 0042 # Encode as per RFC 2231 0043 param += '*' 0044 value = Utils.encode_rfc2231(value[2], value[0], value[1]) 0045 # BAW: Please check this. I think that if quote is set it should 0046 # force quoting even if not necessary. 0047 if quote or tspecials.search(value): 0048 return '%s="%s"' % (param, Utils.quote(value)) 0049 else: 0050 return '%s=%s' % (param, value) 0051 else: 0052 return param 0053 0054 def _parseparam(s): 0055 plist = [] 0056 while s[:1] == ';': 0057 s = s[1:] 0058 end = s.find(';') 0059 while end > 0 and s.count('"', 0, end) % 2: 0060 end = s.find(';', end + 1) 0061 if end < 0: 0062 end = len(s) 0063 f = s[:end] 0064 if '=' in f: 0065 i = f.index('=') 0066 f = f[:i].strip().lower() + '=' + f[i+1:].strip() 0067 plist.append(f.strip()) 0068 s = s[end:] 0069 return plist 0070 0071 0072 def _unquotevalue(value): 0073 # This is different than Utils.collapse_rfc2231_value() because it doesn't 0074 # try to convert the value to a unicode. Message.get_param() and 0075 # Message.get_params() are both currently defined to return the tuple in 0076 # the face of RFC 2231 parameters. 0077 if isinstance(value, tuple): 0078 return value[0], value[1], Utils.unquote(value[2]) 0079 else: 0080 return Utils.unquote(value) 0081 0082 0083 0084 class Message: 0085 """Basic message object. 0086 0087 A message object is defined as something that has a bunch of RFC 2822 0088 headers and a payload. It may optionally have an envelope header 0089 (a.k.a. Unix-From or From_ header). If the message is a container (i.e. a 0090 multipart or a message/rfc822), then the payload is a list of Message 0091 objects, otherwise it is a string. 0092 0093 Message objects implement part of the `mapping' interface, which assumes 0094 there is exactly one occurrance of the header per message. Some headers 0095 do in fact appear multiple times (e.g. Received) and for those headers, 0096 you must use the explicit API to set or get all the headers. Not all of 0097 the mapping methods are implemented. 0098 """ 0099 def __init__(self): 0100 self._headers = [] 0101 self._unixfrom = None 0102 self._payload = None 0103 self._charset = None 0104 # Defaults for multipart messages 0105 self.preamble = self.epilogue = None 0106 self.defects = [] 0107 # Default content type 0108 self._default_type = 'text/plain' 0109 0110 def __str__(self): 0111 """Return the entire formatted message as a string. 0112 This includes the headers, body, and envelope header. 0113 """ 0114 return self.as_string(unixfrom=True) 0115 0116 def as_string(self, unixfrom=False): 0117 """Return the entire formatted message as a string. 0118 Optional `unixfrom' when True, means include the Unix From_ envelope 0119 header. 0120 0121 This is a convenience method and may not generate the message exactly 0122 as you intend because by default it mangles lines that begin with 0123 "From ". For more flexibility, use the flatten() method of a 0124 Generator instance. 0125 """ 0126 from email.Generator import Generator 0127 fp = StringIO() 0128 g = Generator(fp) 0129 g.flatten(self, unixfrom=unixfrom) 0130 return fp.getvalue() 0131 0132 def is_multipart(self): 0133 """Return True if the message consists of multiple parts.""" 0134 return isinstance(self._payload, list) 0135 0136 # 0137 # Unix From_ line 0138 # 0139 def set_unixfrom(self, unixfrom): 0140 self._unixfrom = unixfrom 0141 0142 def get_unixfrom(self): 0143 return self._unixfrom 0144 0145 # 0146 # Payload manipulation. 0147 # 0148 def attach(self, payload): 0149 """Add the given payload to the current payload. 0150 0151 The current payload will always be a list of objects after this method 0152 is called. If you want to set the payload to a scalar object, use 0153 set_payload() instead. 0154 """ 0155 if self._payload is None: 0156 self._payload = [payload] 0157 else: 0158 self._payload.append(payload) 0159 0160 def get_payload(self, i=None, decode=False): 0161 """Return a reference to the payload. 0162 0163 The payload will either be a list object or a string. If you mutate 0164 the list object, you modify the message's payload in place. Optional 0165 i returns that index into the payload. 0166 0167 Optional decode is a flag indicating whether the payload should be 0168 decoded or not, according to the Content-Transfer-Encoding header 0169 (default is False). 0170 0171 When True and the message is not a multipart, the payload will be 0172 decoded if this header's value is `quoted-printable' or `base64'. If 0173 some other encoding is used, or the header is missing, or if the 0174 payload has bogus data (i.e. bogus base64 or uuencoded data), the 0175 payload is returned as-is. 0176 0177 If the message is a multipart and the decode flag is True, then None 0178 is returned. 0179 """ 0180 if i is None: 0181 payload = self._payload 0182 elif not isinstance(self._payload, list): 0183 raise TypeError('Expected list, got %s' % type(self._payload)) 0184 else: 0185 payload = self._payload[i] 0186 if decode: 0187 if self.is_multipart(): 0188 return None 0189 cte = self.get('content-transfer-encoding', '').lower() 0190 if cte == 'quoted-printable': 0191 return Utils._qdecode(payload) 0192 elif cte == 'base64': 0193 try: 0194 return Utils._bdecode(payload) 0195 except binascii.Error: 0196 # Incorrect padding 0197 return payload 0198 elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'): 0199 sfp = StringIO() 0200 try: 0201 uu.decode(StringIO(payload+'\n'), sfp) 0202 payload = sfp.getvalue() 0203 except uu.Error: 0204 # Some decoding problem 0205 return payload 0206 # Everything else, including encodings with 8bit or 7bit are returned 0207 # unchanged. 0208 return payload 0209 0210 def set_payload(self, payload, charset=None): 0211 """Set the payload to the given value. 0212 0213 Optional charset sets the message's default character set. See 0214 set_charset() for details. 0215 """ 0216 self._payload = payload 0217 if charset is not None: 0218 self.set_charset(charset) 0219 0220 def set_charset(self, charset): 0221 """Set the charset of the payload to a given character set. 0222 0223 charset can be a Charset instance, a string naming a character set, or 0224 None. If it is a string it will be converted to a Charset instance. 0225 If charset is None, the charset parameter will be removed from the 0226 Content-Type field. Anything else will generate a TypeError. 0227 0228 The message will be assumed to be of type text/* encoded with 0229 charset.input_charset. It will be converted to charset.output_charset 0230 and encoded properly, if needed, when generating the plain text 0231 representation of the message. MIME headers (MIME-Version, 0232 Content-Type, Content-Transfer-Encoding) will be added as needed. 0233 0234 """ 0235 if charset is None: 0236 self.del_param('charset') 0237 self._charset = None 0238 return 0239 if isinstance(charset, str): 0240 charset = Charset.Charset(charset) 0241 if not isinstance(charset, Charset.Charset): 0242 raise TypeError(charset) 0243 # BAW: should we accept strings that can serve as arguments to the 0244 # Charset constructor? 0245 self._charset = charset 0246 if not self.has_key('MIME-Version'): 0247 self.add_header('MIME-Version', '1.0') 0248 if not self.has_key('Content-Type'): 0249 self.add_header('Content-Type', 'text/plain', 0250 charset=charset.get_output_charset()) 0251 else: 0252 self.set_param('charset', charset.get_output_charset()) 0253 if not self.has_key('Content-Transfer-Encoding'): 0254 cte = charset.get_body_encoding() 0255 try: 0256 cte(self) 0257 except TypeError: 0258 self.add_header('Content-Transfer-Encoding', cte) 0259 0260 def get_charset(self): 0261 """Return the Charset instance associated with the message's payload. 0262 """ 0263 return self._charset 0264 0265 # 0266 # MAPPING INTERFACE (partial) 0267 # 0268 def __len__(self): 0269 """Return the total number of headers, including duplicates.""" 0270 return len(self._headers) 0271 0272 def __getitem__(self, name): 0273 """Get a header value. 0274 0275 Return None if the header is missing instead of raising an exception. 0276 0277 Note that if the header appeared multiple times, exactly which 0278 occurrance gets returned is undefined. Use get_all() to get all 0279 the values matching a header field name. 0280 """ 0281 return self.get(name) 0282 0283 def __setitem__(self, name, val): 0284 """Set the value of a header. 0285 0286 Note: this does not overwrite an existing header with the same field 0287 name. Use __delitem__() first to delete any existing headers. 0288 """ 0289 self._headers.append((name, val)) 0290 0291 def __delitem__(self, name): 0292 """Delete all occurrences of a header, if present. 0293 0294 Does not raise an exception if the header is missing. 0295 """ 0296 name = name.lower() 0297 newheaders = [] 0298 for k, v in self._headers: 0299 if k.lower() <> name: 0300 newheaders.append((k, v)) 0301 self._headers = newheaders 0302 0303 def __contains__(self, name): 0304 return name.lower() in [k.lower() for k, v in self._headers] 0305 0306 def has_key(self, name): 0307 """Return true if the message contains the header.""" 0308 missing = object() 0309 return self.get(name, missing) is not missing 0310 0311 def keys(self): 0312 """Return a list of all the message's header field names. 0313 0314 These will be sorted in the order they appeared in the original 0315 message, or were added to the message, and may contain duplicates. 0316 Any fields deleted and re-inserted are always appended to the header 0317 list. 0318 """ 0319 return [k for k, v in self._headers] 0320 0321 def values(self): 0322 """Return a list of all the message's header values. 0323 0324 These will be sorted in the order they appeared in the original 0325 message, or were added to the message, and may contain duplicates. 0326 Any fields deleted and re-inserted are always appended to the header 0327 list. 0328 """ 0329 return [v for k, v in self._headers] 0330 0331 def items(self): 0332 """Get all the message's header fields and values. 0333 0334 These will be sorted in the order they appeared in the original 0335 message, or were added to the message, and may contain duplicates. 0336 Any fields deleted and re-inserted are always appended to the header 0337 list. 0338 """ 0339 return self._headers[:] 0340 0341 def get(self, name, failobj=None): 0342 """Get a header value. 0343 0344 Like __getitem__() but return failobj instead of None when the field 0345 is missing. 0346 """ 0347 name = name.lower() 0348 for k, v in self._headers: 0349 if k.lower() == name: 0350 return v 0351 return failobj 0352 0353 # 0354 # Additional useful stuff 0355 # 0356 0357 def get_all(self, name, failobj=None): 0358 """Return a list of all the values for the named field. 0359 0360 These will be sorted in the order they appeared in the original 0361 message, and may contain duplicates. Any fields deleted and 0362 re-inserted are always appended to the header list. 0363 0364 If no such fields exist, failobj is returned (defaults to None). 0365 """ 0366 values = [] 0367 name = name.lower() 0368 for k, v in self._headers: 0369 if k.lower() == name: 0370 values.append(v) 0371 if not values: 0372 return failobj 0373 return values 0374 0375 def add_header(self, _name, _value, **_params): 0376 """Extended header setting. 0377 0378 name is the header field to add. keyword arguments can be used to set 0379 additional parameters for the header field, with underscores converted 0380 to dashes. Normally the parameter will be added as key="value" unless 0381 value is None, in which case only the key will be added. 0382 0383 Example: 0384 0385 msg.add_header('content-disposition', 'attachment', filename='bud.gif') 0386 """ 0387 parts = [] 0388 for k, v in _params.items(): 0389 if v is None: 0390 parts.append(k.replace('_', '-')) 0391 else: 0392 parts.append(_formatparam(k.replace('_', '-'), v)) 0393 if _value is not None: 0394 parts.insert(0, _value) 0395 self._headers.append((_name, SEMISPACE.join(parts))) 0396 0397 def replace_header(self, _name, _value): 0398 """Replace a header. 0399 0400 Replace the first matching header found in the message, retaining 0401 header order and case. If no matching header was found, a KeyError is 0402 raised. 0403 """ 0404 _name = _name.lower() 0405 for i, (k, v) in zip(range(len(self._headers)), self._headers): 0406 if k.lower() == _name: 0407 self._headers[i] = (k, _value) 0408 break 0409 else: 0410 raise KeyError(_name) 0411 0412 # 0413 # Deprecated methods. These will be removed in email 3.1. 0414 # 0415 0416 def get_type(self, failobj=None): 0417 """Returns the message's content type. 0418 0419 The returned string is coerced to lowercase and returned as a single 0420 string of the form `maintype/subtype'. If there was no Content-Type 0421 header in the message, failobj is returned (defaults to None). 0422 """ 0423 warnings.warn('get_type() deprecated; use get_content_type()', 0424 DeprecationWarning, 2) 0425 missing = object() 0426 value = self.get('content-type', missing) 0427 if value is missing: 0428 return failobj 0429 return paramre.split(value)[0].lower().strip() 0430 0431 def get_main_type(self, failobj=None): 0432 """Return the message's main content type if present.""" 0433 warnings.warn('get_main_type() deprecated; use get_content_maintype()', 0434 DeprecationWarning, 2) 0435 missing = object() 0436 ctype = self.get_type(missing) 0437 if ctype is missing: 0438 return failobj 0439 if ctype.count('/') <> 1: 0440 return failobj 0441 return ctype.split('/')[0] 0442 0443 def get_subtype(self, failobj=None): 0444 """Return the message's content subtype if present.""" 0445 warnings.warn('get_subtype() deprecated; use get_content_subtype()', 0446 DeprecationWarning, 2) 0447 missing = object() 0448 ctype = self.get_type(missing) 0449 if ctype is missing: 0450 return failobj 0451 if ctype.count('/') <> 1: 0452 return failobj 0453 return ctype.split('/')[1] 0454 0455 # 0456 # Use these three methods instead of the three above. 0457 # 0458 0459 def get_content_type(self): 0460 """Return the message's content type. 0461 0462 The returned string is coerced to lower case of the form 0463 `maintype/subtype'. If there was no Content-Type header in the 0464 message, the default type as given by get_default_type() will be 0465 returned. Since according to RFC 2045, messages always have a default 0466 type this will always return a value. 0467 0468 RFC 2045 defines a message's default type to be text/plain unless it 0469 appears inside a multipart/digest container, in which case it would be 0470 message/rfc822. 0471 """ 0472 missing = object() 0473 value = self.get('content-type', missing) 0474 if value is missing: 0475 # This should have no parameters 0476 return self.get_default_type() 0477 ctype = paramre.split(value)[0].lower().strip() 0478 # RFC 2045, section 5.2 says if its invalid, use text/plain 0479 if ctype.count('/') <> 1: 0480 return 'text/plain' 0481 return ctype 0482 0483 def get_content_maintype(self): 0484 """Return the message's main content type. 0485 0486 This is the `maintype' part of the string returned by 0487 get_content_type(). 0488 """ 0489 ctype = self.get_content_type() 0490 return ctype.split('/')[0] 0491 0492 def get_content_subtype(self): 0493 """Returns the message's sub-content type. 0494 0495 This is the `subtype' part of the string returned by 0496 get_content_type(). 0497 """ 0498 ctype = self.get_content_type() 0499 return ctype.split('/')[1] 0500 0501 def get_default_type(self): 0502 """Return the `default' content type. 0503 0504 Most messages have a default content type of text/plain, except for 0505 messages that are subparts of multipart/digest containers. Such 0506 subparts have a default content type of message/rfc822. 0507 """ 0508 return self._default_type 0509 0510 def set_default_type(self, ctype): 0511 """Set the `default' content type. 0512 0513 ctype should be either "text/plain" or "message/rfc822", although this 0514 is not enforced. The default content type is not stored in the 0515 Content-Type header. 0516 """ 0517 self._default_type = ctype 0518 0519 def _get_params_preserve(self, failobj, header): 0520 # Like get_params() but preserves the quoting of values. BAW: 0521 # should this be part of the public interface? 0522 missing = object() 0523 value = self.get(header, missing) 0524 if value is missing: 0525 return failobj 0526 params = [] 0527 for p in _parseparam(';' + value): 0528 try: 0529 name, val = p.split('=', 1) 0530 name = name.strip() 0531 val = val.strip() 0532 except ValueError: 0533 # Must have been a bare attribute 0534 name = p.strip() 0535 val = '' 0536 params.append((name, val)) 0537 params = Utils.decode_params(params) 0538 return params 0539 0540 def get_params(self, failobj=None, header='content-type', unquote=True): 0541 """Return the message's Content-Type parameters, as a list. 0542 0543 The elements of the returned list are 2-tuples of key/value pairs, as 0544 split on the `=' sign. The left hand side of the `=' is the key, 0545 while the right hand side is the value. If there is no `=' sign in 0546 the parameter the value is the empty string. The value is as 0547 described in the get_param() method. 0548 0549 Optional failobj is the object to return if there is no Content-Type 0550 header. Optional header is the header to search instead of 0551 Content-Type. If unquote is True, the value is unquoted. 0552 """ 0553 missing = object() 0554 params = self._get_params_preserve(missing, header) 0555 if params is missing: 0556 return failobj 0557 if unquote: 0558 return [(k, _unquotevalue(v)) for k, v in params] 0559 else: 0560 return params 0561 0562 def get_param(self, param, failobj=None, header='content-type', 0563 unquote=True): 0564 """Return the parameter value if found in the Content-Type header. 0565 0566 Optional failobj is the object to return if there is no Content-Type 0567 header, or the Content-Type header has no such parameter. Optional 0568 header is the header to search instead of Content-Type. 0569 0570 Parameter keys are always compared case insensitively. The return 0571 value can either be a string, or a 3-tuple if the parameter was RFC 0572 2231 encoded. When it's a 3-tuple, the elements of the value are of 0573 the form (CHARSET, LANGUAGE, VALUE). Note that both CHARSET and 0574 LANGUAGE can be None, in which case you should consider VALUE to be 0575 encoded in the us-ascii charset. You can usually ignore LANGUAGE. 0576 0577 Your application should be prepared to deal with 3-tuple return 0578 values, and can convert the parameter to a Unicode string like so: 0579 0580 param = msg.get_param('foo') 0581 if isinstance(param, tuple): 0582 param = unicode(param[2], param[0] or 'us-ascii') 0583 0584 In any case, the parameter value (either the returned string, or the 0585 VALUE item in the 3-tuple) is always unquoted, unless unquote is set 0586 to False. 0587 """ 0588 if not self.has_key(header): 0589 return failobj 0590 for k, v in self._get_params_preserve(failobj, header): 0591 if k.lower() == param.lower(): 0592 if unquote: 0593 return _unquotevalue(v) 0594 else: 0595 return v 0596 return failobj 0597 0598 def set_param(self, param, value, header='Content-Type', requote=True, 0599 charset=None, language=''): 0600 """Set a parameter in the Content-Type header. 0601 0602 If the parameter already exists in the header, its value will be 0603 replaced with the new value. 0604 0605 If header is Content-Type and has not yet been defined for this 0606 message, it will be set to "text/plain" and the new parameter and 0607 value will be appended as per RFC 2045. 0608 0609 An alternate header can specified in the header argument, and all 0610 parameters will be quoted as necessary unless requote is False. 0611 0612 If charset is specified, the parameter will be encoded according to RFC 0613 2231. Optional language specifies the RFC 2231 language, defaulting 0614 to the empty string. Both charset and language should be strings. 0615 """ 0616 if not isinstance(value, tuple) and charset: 0617 value = (charset, language, value) 0618 0619 if not self.has_key(header) and header.lower() == 'content-type': 0620 ctype = 'text/plain' 0621 else: 0622 ctype = self.get(header) 0623 if not self.get_param(param, header=header): 0624 if not ctype: 0625 ctype = _formatparam(param, value, requote) 0626 else: 0627 ctype = SEMISPACE.join( 0628 [ctype, _formatparam(param, value, requote)]) 0629 else: 0630 ctype = '' 0631 for old_param, old_value in self.get_params(header=header, 0632 unquote=requote): 0633 append_param = '' 0634 if old_param.lower() == param.lower(): 0635 append_param = _formatparam(param, value, requote) 0636 else: 0637 append_param = _formatparam(old_param, old_value, requote) 0638 if not ctype: 0639 ctype = append_param 0640 else: 0641 ctype = SEMISPACE.join([ctype, append_param]) 0642 if ctype <> self.get(header): 0643 del self[header] 0644 self[header] = ctype 0645 0646 def del_param(self, param, header='content-type', requote=True): 0647 """Remove the given parameter completely from the Content-Type header. 0648 0649 The header will be re-written in place without the parameter or its 0650 value. All values will be quoted as necessary unless requote is 0651 False. Optional header specifies an alternative to the Content-Type 0652 header. 0653 """ 0654 if not self.has_key(header): 0655 return 0656 new_ctype = '' 0657 for p, v in self.get_params(header=header, unquote=requote): 0658 if p.lower() <> param.lower(): 0659 if not new_ctype: 0660 new_ctype = _formatparam(p, v, requote) 0661 else: 0662 new_ctype = SEMISPACE.join([new_ctype, 0663 _formatparam(p, v, requote)]) 0664 if new_ctype <> self.get(header): 0665 del self[header] 0666 self[header] = new_ctype 0667 0668 def set_type(self, type, header='Content-Type', requote=True): 0669 """Set the main type and subtype for the Content-Type header. 0670 0671 type must be a string in the form "maintype/subtype", otherwise a 0672 ValueError is raised. 0673 0674 This method replaces the Content-Type header, keeping all the 0675 parameters in place. If requote is False, this leaves the existing 0676 header's quoting as is. Otherwise, the parameters will be quoted (the 0677 default). 0678 0679 An alternative header can be specified in the header argument. When 0680 the Content-Type header is set, we'll always also add a MIME-Version 0681 header. 0682 """ 0683 # BAW: should we be strict? 0684 if not type.count('/') == 1: 0685 raise ValueError 0686 # Set the Content-Type, you get a MIME-Version 0687 if header.lower() == 'content-type': 0688 del self['mime-version'] 0689 self['MIME-Version'] = '1.0' 0690 if not self.has_key(header): 0691 self[header] = type 0692 return 0693 params = self.get_params(header=header, unquote=requote) 0694 del self[header] 0695 self[header] = type 0696 # Skip the first param; it's the old type. 0697 for p, v in params[1:]: 0698 self.set_param(p, v, header, requote) 0699 0700 def get_filename(self, failobj=None): 0701 """Return the filename associated with the payload if present. 0702 0703 The filename is extracted from the Content-Disposition header's 0704 `filename' parameter, and it is unquoted. 0705 """ 0706 missing = object() 0707 filename = self.get_param('filename', missing, 'content-disposition') 0708 if filename is missing: 0709 return failobj 0710 return Utils.collapse_rfc2231_value(filename).strip() 0711 0712 def get_boundary(self, failobj=None): 0713 """Return the boundary associated with the payload if present. 0714 0715 The boundary is extracted from the Content-Type header's `boundary' 0716 parameter, and it is unquoted. 0717 """ 0718 missing = object() 0719 boundary = self.get_param('boundary', missing) 0720 if boundary is missing: 0721 return failobj 0722 return Utils.collapse_rfc2231_value(boundary).strip() 0723 0724 def set_boundary(self, boundary): 0725 """Set the boundary parameter in Content-Type to 'boundary'. 0726 0727 This is subtly different than deleting the Content-Type header and 0728 adding a new one with a new boundary parameter via add_header(). The 0729 main difference is that using the set_boundary() method preserves the 0730 order of the Content-Type header in the original message. 0731 0732 HeaderParseError is raised if the message has no Content-Type header. 0733 """ 0734 missing = object() 0735 params = self._get_params_preserve(missing, 'content-type') 0736 if params is missing: 0737 # There was no Content-Type header, and we don't know what type 0738 # to set it to, so raise an exception. 0739 raise Errors.HeaderParseError, 'No Content-Type header found' 0740 newparams = [] 0741 foundp = False 0742 for pk, pv in params: 0743 if pk.lower() == 'boundary': 0744 newparams.append(('boundary', '"%s"' % boundary)) 0745 foundp = True 0746 else: 0747 newparams.append((pk, pv)) 0748 if not foundp: 0749 # The original Content-Type header had no boundary attribute. 0750 # Tack one on the end. BAW: should we raise an exception 0751 # instead??? 0752 newparams.append(('boundary', '"%s"' % boundary)) 0753 # Replace the existing Content-Type header with the new value 0754 newheaders = [] 0755 for h, v in self._headers: 0756 if h.lower() == 'content-type': 0757 parts = [] 0758 for k, v in newparams: 0759 if v == '': 0760 parts.append(k) 0761 else: 0762 parts.append('%s=%s' % (k, v)) 0763 newheaders.append((h, SEMISPACE.join(parts))) 0764 0765 else: 0766 newheaders.append((h, v)) 0767 self._headers = newheaders 0768 0769 def get_content_charset(self, failobj=None): 0770 """Return the charset parameter of the Content-Type header. 0771 0772 The returned string is always coerced to lower case. If there is no 0773 Content-Type header, or if that header has no charset parameter, 0774 failobj is returned. 0775 """ 0776 missing = object() 0777 charset = self.get_param('charset', missing) 0778 if charset is missing: 0779 return failobj 0780 if isinstance(charset, tuple): 0781 # RFC 2231 encoded, so decode it, and it better end up as ascii. 0782 pcharset = charset[0] or 'us-ascii' 0783 charset = unicode(charset[2], pcharset).encode('us-ascii') 0784 # RFC 2046, $4.1.2 says charsets are not case sensitive 0785 return charset.lower() 0786 0787 def get_charsets(self, failobj=None): 0788 """Return a list containing the charset(s) used in this message. 0789 0790 The returned list of items describes the Content-Type headers' 0791 charset parameter for this message and all the subparts in its 0792 payload. 0793 0794 Each item will either be a string (the value of the charset parameter 0795 in the Content-Type header of that part) or the value of the 0796 'failobj' parameter (defaults to None), if the part does not have a 0797 main MIME type of "text", or the charset is not defined. 0798 0799 The list will contain one string for each part of the message, plus 0800 one for the container message (i.e. self), so that a non-multipart 0801 message will still return a list of length 1. 0802 """ 0803 return [part.get_content_charset(failobj) for part in self.walk()] 0804 0805 # I.e. def walk(self): ... 0806 from email.Iterators import walk 0807
Generated by PyXR 0.9.4