+++ /dev/null
-# Copyright (C) 2001-2006 Python Software Foundation\r
-# Author: Barry Warsaw\r
-# Contact: email-sig@python.org\r
-\r
-"""Basic message object for the email package object model."""\r
-\r
-__all__ = ['Message']\r
-\r
-import re\r
-import uu\r
-import binascii\r
-import warnings\r
-from cStringIO import StringIO\r
-\r
-# Intrapackage imports\r
-import email.charset\r
-from email import utils\r
-from email import errors\r
-\r
-SEMISPACE = '; '\r
-\r
-# Regular expression that matches `special' characters in parameters, the\r
-# existence of which force quoting of the parameter value.\r
-tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')\r
-\r
-\r
-# Helper functions\r
-def _splitparam(param):\r
- # Split header parameters. BAW: this may be too simple. It isn't\r
- # strictly RFC 2045 (section 5.1) compliant, but it catches most headers\r
- # found in the wild. We may eventually need a full fledged parser\r
- # eventually.\r
- a, sep, b = param.partition(';')\r
- if not sep:\r
- return a.strip(), None\r
- return a.strip(), b.strip()\r
-\f\r
-def _formatparam(param, value=None, quote=True):\r
- """Convenience function to format and return a key=value pair.\r
-\r
- This will quote the value if needed or if quote is true. If value is a\r
- three tuple (charset, language, value), it will be encoded according\r
- to RFC2231 rules.\r
- """\r
- if value is not None and len(value) > 0:\r
- # A tuple is used for RFC 2231 encoded parameter values where items\r
- # are (charset, language, value). charset is a string, not a Charset\r
- # instance.\r
- if isinstance(value, tuple):\r
- # Encode as per RFC 2231\r
- param += '*'\r
- value = utils.encode_rfc2231(value[2], value[0], value[1])\r
- # BAW: Please check this. I think that if quote is set it should\r
- # force quoting even if not necessary.\r
- if quote or tspecials.search(value):\r
- return '%s="%s"' % (param, utils.quote(value))\r
- else:\r
- return '%s=%s' % (param, value)\r
- else:\r
- return param\r
-\r
-def _parseparam(s):\r
- plist = []\r
- while s[:1] == ';':\r
- s = s[1:]\r
- end = s.find(';')\r
- while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:\r
- end = s.find(';', end + 1)\r
- if end < 0:\r
- end = len(s)\r
- f = s[:end]\r
- if '=' in f:\r
- i = f.index('=')\r
- f = f[:i].strip().lower() + '=' + f[i+1:].strip()\r
- plist.append(f.strip())\r
- s = s[end:]\r
- return plist\r
-\r
-\r
-def _unquotevalue(value):\r
- # This is different than utils.collapse_rfc2231_value() because it doesn't\r
- # try to convert the value to a unicode. Message.get_param() and\r
- # Message.get_params() are both currently defined to return the tuple in\r
- # the face of RFC 2231 parameters.\r
- if isinstance(value, tuple):\r
- return value[0], value[1], utils.unquote(value[2])\r
- else:\r
- return utils.unquote(value)\r
-\r
-\r
-\f\r
-class Message:\r
- """Basic message object.\r
-\r
- A message object is defined as something that has a bunch of RFC 2822\r
- headers and a payload. It may optionally have an envelope header\r
- (a.k.a. Unix-From or From_ header). If the message is a container (i.e. a\r
- multipart or a message/rfc822), then the payload is a list of Message\r
- objects, otherwise it is a string.\r
-\r
- Message objects implement part of the `mapping' interface, which assumes\r
- there is exactly one occurrence of the header per message. Some headers\r
- do in fact appear multiple times (e.g. Received) and for those headers,\r
- you must use the explicit API to set or get all the headers. Not all of\r
- the mapping methods are implemented.\r
- """\r
- def __init__(self):\r
- self._headers = []\r
- self._unixfrom = None\r
- self._payload = None\r
- self._charset = None\r
- # Defaults for multipart messages\r
- self.preamble = self.epilogue = None\r
- self.defects = []\r
- # Default content type\r
- self._default_type = 'text/plain'\r
-\r
- def __str__(self):\r
- """Return the entire formatted message as a string.\r
- This includes the headers, body, and envelope header.\r
- """\r
- return self.as_string(unixfrom=True)\r
-\r
- def as_string(self, unixfrom=False):\r
- """Return the entire formatted message as a string.\r
- Optional `unixfrom' when True, means include the Unix From_ envelope\r
- header.\r
-\r
- This is a convenience method and may not generate the message exactly\r
- as you intend because by default it mangles lines that begin with\r
- "From ". For more flexibility, use the flatten() method of a\r
- Generator instance.\r
- """\r
- from email.generator import Generator\r
- fp = StringIO()\r
- g = Generator(fp)\r
- g.flatten(self, unixfrom=unixfrom)\r
- return fp.getvalue()\r
-\r
- def is_multipart(self):\r
- """Return True if the message consists of multiple parts."""\r
- return isinstance(self._payload, list)\r
-\r
- #\r
- # Unix From_ line\r
- #\r
- def set_unixfrom(self, unixfrom):\r
- self._unixfrom = unixfrom\r
-\r
- def get_unixfrom(self):\r
- return self._unixfrom\r
-\r
- #\r
- # Payload manipulation.\r
- #\r
- def attach(self, payload):\r
- """Add the given payload to the current payload.\r
-\r
- The current payload will always be a list of objects after this method\r
- is called. If you want to set the payload to a scalar object, use\r
- set_payload() instead.\r
- """\r
- if self._payload is None:\r
- self._payload = [payload]\r
- else:\r
- self._payload.append(payload)\r
-\r
- def get_payload(self, i=None, decode=False):\r
- """Return a reference to the payload.\r
-\r
- The payload will either be a list object or a string. If you mutate\r
- the list object, you modify the message's payload in place. Optional\r
- i returns that index into the payload.\r
-\r
- Optional decode is a flag indicating whether the payload should be\r
- decoded or not, according to the Content-Transfer-Encoding header\r
- (default is False).\r
-\r
- When True and the message is not a multipart, the payload will be\r
- decoded if this header's value is `quoted-printable' or `base64'. If\r
- some other encoding is used, or the header is missing, or if the\r
- payload has bogus data (i.e. bogus base64 or uuencoded data), the\r
- payload is returned as-is.\r
-\r
- If the message is a multipart and the decode flag is True, then None\r
- is returned.\r
- """\r
- if i is None:\r
- payload = self._payload\r
- elif not isinstance(self._payload, list):\r
- raise TypeError('Expected list, got %s' % type(self._payload))\r
- else:\r
- payload = self._payload[i]\r
- if decode:\r
- if self.is_multipart():\r
- return None\r
- cte = self.get('content-transfer-encoding', '').lower()\r
- if cte == 'quoted-printable':\r
- return utils._qdecode(payload)\r
- elif cte == 'base64':\r
- try:\r
- return utils._bdecode(payload)\r
- except binascii.Error:\r
- # Incorrect padding\r
- return payload\r
- elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):\r
- sfp = StringIO()\r
- try:\r
- uu.decode(StringIO(payload+'\n'), sfp, quiet=True)\r
- payload = sfp.getvalue()\r
- except uu.Error:\r
- # Some decoding problem\r
- return payload\r
- # Everything else, including encodings with 8bit or 7bit are returned\r
- # unchanged.\r
- return payload\r
-\r
- def set_payload(self, payload, charset=None):\r
- """Set the payload to the given value.\r
-\r
- Optional charset sets the message's default character set. See\r
- set_charset() for details.\r
- """\r
- self._payload = payload\r
- if charset is not None:\r
- self.set_charset(charset)\r
-\r
- def set_charset(self, charset):\r
- """Set the charset of the payload to a given character set.\r
-\r
- charset can be a Charset instance, a string naming a character set, or\r
- None. If it is a string it will be converted to a Charset instance.\r
- If charset is None, the charset parameter will be removed from the\r
- Content-Type field. Anything else will generate a TypeError.\r
-\r
- The message will be assumed to be of type text/* encoded with\r
- charset.input_charset. It will be converted to charset.output_charset\r
- and encoded properly, if needed, when generating the plain text\r
- representation of the message. MIME headers (MIME-Version,\r
- Content-Type, Content-Transfer-Encoding) will be added as needed.\r
-\r
- """\r
- if charset is None:\r
- self.del_param('charset')\r
- self._charset = None\r
- return\r
- if isinstance(charset, basestring):\r
- charset = email.charset.Charset(charset)\r
- if not isinstance(charset, email.charset.Charset):\r
- raise TypeError(charset)\r
- # BAW: should we accept strings that can serve as arguments to the\r
- # Charset constructor?\r
- self._charset = charset\r
- if 'MIME-Version' not in self:\r
- self.add_header('MIME-Version', '1.0')\r
- if 'Content-Type' not in self:\r
- self.add_header('Content-Type', 'text/plain',\r
- charset=charset.get_output_charset())\r
- else:\r
- self.set_param('charset', charset.get_output_charset())\r
- if isinstance(self._payload, unicode):\r
- self._payload = self._payload.encode(charset.output_charset)\r
- if str(charset) != charset.get_output_charset():\r
- self._payload = charset.body_encode(self._payload)\r
- if 'Content-Transfer-Encoding' not in self:\r
- cte = charset.get_body_encoding()\r
- try:\r
- cte(self)\r
- except TypeError:\r
- self._payload = charset.body_encode(self._payload)\r
- self.add_header('Content-Transfer-Encoding', cte)\r
-\r
- def get_charset(self):\r
- """Return the Charset instance associated with the message's payload.\r
- """\r
- return self._charset\r
-\r
- #\r
- # MAPPING INTERFACE (partial)\r
- #\r
- def __len__(self):\r
- """Return the total number of headers, including duplicates."""\r
- return len(self._headers)\r
-\r
- def __getitem__(self, name):\r
- """Get a header value.\r
-\r
- Return None if the header is missing instead of raising an exception.\r
-\r
- Note that if the header appeared multiple times, exactly which\r
- occurrence gets returned is undefined. Use get_all() to get all\r
- the values matching a header field name.\r
- """\r
- return self.get(name)\r
-\r
- def __setitem__(self, name, val):\r
- """Set the value of a header.\r
-\r
- Note: this does not overwrite an existing header with the same field\r
- name. Use __delitem__() first to delete any existing headers.\r
- """\r
- self._headers.append((name, val))\r
-\r
- def __delitem__(self, name):\r
- """Delete all occurrences of a header, if present.\r
-\r
- Does not raise an exception if the header is missing.\r
- """\r
- name = name.lower()\r
- newheaders = []\r
- for k, v in self._headers:\r
- if k.lower() != name:\r
- newheaders.append((k, v))\r
- self._headers = newheaders\r
-\r
- def __contains__(self, name):\r
- return name.lower() in [k.lower() for k, v in self._headers]\r
-\r
- def has_key(self, name):\r
- """Return true if the message contains the header."""\r
- missing = object()\r
- return self.get(name, missing) is not missing\r
-\r
- def keys(self):\r
- """Return a list of all the message's header field names.\r
-\r
- These will be sorted in the order they appeared in the original\r
- message, or were added to the message, and may contain duplicates.\r
- Any fields deleted and re-inserted are always appended to the header\r
- list.\r
- """\r
- return [k for k, v in self._headers]\r
-\r
- def values(self):\r
- """Return a list of all the message's header values.\r
-\r
- These will be sorted in the order they appeared in the original\r
- message, or were added to the message, and may contain duplicates.\r
- Any fields deleted and re-inserted are always appended to the header\r
- list.\r
- """\r
- return [v for k, v in self._headers]\r
-\r
- def items(self):\r
- """Get all the message's header fields and values.\r
-\r
- These will be sorted in the order they appeared in the original\r
- message, or were added to the message, and may contain duplicates.\r
- Any fields deleted and re-inserted are always appended to the header\r
- list.\r
- """\r
- return self._headers[:]\r
-\r
- def get(self, name, failobj=None):\r
- """Get a header value.\r
-\r
- Like __getitem__() but return failobj instead of None when the field\r
- is missing.\r
- """\r
- name = name.lower()\r
- for k, v in self._headers:\r
- if k.lower() == name:\r
- return v\r
- return failobj\r
-\r
- #\r
- # Additional useful stuff\r
- #\r
-\r
- def get_all(self, name, failobj=None):\r
- """Return a list of all the values for the named field.\r
-\r
- These will be sorted in the order they appeared in the original\r
- message, and may contain duplicates. Any fields deleted and\r
- re-inserted are always appended to the header list.\r
-\r
- If no such fields exist, failobj is returned (defaults to None).\r
- """\r
- values = []\r
- name = name.lower()\r
- for k, v in self._headers:\r
- if k.lower() == name:\r
- values.append(v)\r
- if not values:\r
- return failobj\r
- return values\r
-\r
- def add_header(self, _name, _value, **_params):\r
- """Extended header setting.\r
-\r
- name is the header field to add. keyword arguments can be used to set\r
- additional parameters for the header field, with underscores converted\r
- to dashes. Normally the parameter will be added as key="value" unless\r
- value is None, in which case only the key will be added. If a\r
- parameter value contains non-ASCII characters it must be specified as a\r
- three-tuple of (charset, language, value), in which case it will be\r
- encoded according to RFC2231 rules.\r
-\r
- Example:\r
-\r
- msg.add_header('content-disposition', 'attachment', filename='bud.gif')\r
- """\r
- parts = []\r
- for k, v in _params.items():\r
- if v is None:\r
- parts.append(k.replace('_', '-'))\r
- else:\r
- parts.append(_formatparam(k.replace('_', '-'), v))\r
- if _value is not None:\r
- parts.insert(0, _value)\r
- self._headers.append((_name, SEMISPACE.join(parts)))\r
-\r
- def replace_header(self, _name, _value):\r
- """Replace a header.\r
-\r
- Replace the first matching header found in the message, retaining\r
- header order and case. If no matching header was found, a KeyError is\r
- raised.\r
- """\r
- _name = _name.lower()\r
- for i, (k, v) in zip(range(len(self._headers)), self._headers):\r
- if k.lower() == _name:\r
- self._headers[i] = (k, _value)\r
- break\r
- else:\r
- raise KeyError(_name)\r
-\r
- #\r
- # Use these three methods instead of the three above.\r
- #\r
-\r
- def get_content_type(self):\r
- """Return the message's content type.\r
-\r
- The returned string is coerced to lower case of the form\r
- `maintype/subtype'. If there was no Content-Type header in the\r
- message, the default type as given by get_default_type() will be\r
- returned. Since according to RFC 2045, messages always have a default\r
- type this will always return a value.\r
-\r
- RFC 2045 defines a message's default type to be text/plain unless it\r
- appears inside a multipart/digest container, in which case it would be\r
- message/rfc822.\r
- """\r
- missing = object()\r
- value = self.get('content-type', missing)\r
- if value is missing:\r
- # This should have no parameters\r
- return self.get_default_type()\r
- ctype = _splitparam(value)[0].lower()\r
- # RFC 2045, section 5.2 says if its invalid, use text/plain\r
- if ctype.count('/') != 1:\r
- return 'text/plain'\r
- return ctype\r
-\r
- def get_content_maintype(self):\r
- """Return the message's main content type.\r
-\r
- This is the `maintype' part of the string returned by\r
- get_content_type().\r
- """\r
- ctype = self.get_content_type()\r
- return ctype.split('/')[0]\r
-\r
- def get_content_subtype(self):\r
- """Returns the message's sub-content type.\r
-\r
- This is the `subtype' part of the string returned by\r
- get_content_type().\r
- """\r
- ctype = self.get_content_type()\r
- return ctype.split('/')[1]\r
-\r
- def get_default_type(self):\r
- """Return the `default' content type.\r
-\r
- Most messages have a default content type of text/plain, except for\r
- messages that are subparts of multipart/digest containers. Such\r
- subparts have a default content type of message/rfc822.\r
- """\r
- return self._default_type\r
-\r
- def set_default_type(self, ctype):\r
- """Set the `default' content type.\r
-\r
- ctype should be either "text/plain" or "message/rfc822", although this\r
- is not enforced. The default content type is not stored in the\r
- Content-Type header.\r
- """\r
- self._default_type = ctype\r
-\r
- def _get_params_preserve(self, failobj, header):\r
- # Like get_params() but preserves the quoting of values. BAW:\r
- # should this be part of the public interface?\r
- missing = object()\r
- value = self.get(header, missing)\r
- if value is missing:\r
- return failobj\r
- params = []\r
- for p in _parseparam(';' + value):\r
- try:\r
- name, val = p.split('=', 1)\r
- name = name.strip()\r
- val = val.strip()\r
- except ValueError:\r
- # Must have been a bare attribute\r
- name = p.strip()\r
- val = ''\r
- params.append((name, val))\r
- params = utils.decode_params(params)\r
- return params\r
-\r
- def get_params(self, failobj=None, header='content-type', unquote=True):\r
- """Return the message's Content-Type parameters, as a list.\r
-\r
- The elements of the returned list are 2-tuples of key/value pairs, as\r
- split on the `=' sign. The left hand side of the `=' is the key,\r
- while the right hand side is the value. If there is no `=' sign in\r
- the parameter the value is the empty string. The value is as\r
- described in the get_param() method.\r
-\r
- Optional failobj is the object to return if there is no Content-Type\r
- header. Optional header is the header to search instead of\r
- Content-Type. If unquote is True, the value is unquoted.\r
- """\r
- missing = object()\r
- params = self._get_params_preserve(missing, header)\r
- if params is missing:\r
- return failobj\r
- if unquote:\r
- return [(k, _unquotevalue(v)) for k, v in params]\r
- else:\r
- return params\r
-\r
- def get_param(self, param, failobj=None, header='content-type',\r
- unquote=True):\r
- """Return the parameter value if found in the Content-Type header.\r
-\r
- Optional failobj is the object to return if there is no Content-Type\r
- header, or the Content-Type header has no such parameter. Optional\r
- header is the header to search instead of Content-Type.\r
-\r
- Parameter keys are always compared case insensitively. The return\r
- value can either be a string, or a 3-tuple if the parameter was RFC\r
- 2231 encoded. When it's a 3-tuple, the elements of the value are of\r
- the form (CHARSET, LANGUAGE, VALUE). Note that both CHARSET and\r
- LANGUAGE can be None, in which case you should consider VALUE to be\r
- encoded in the us-ascii charset. You can usually ignore LANGUAGE.\r
-\r
- Your application should be prepared to deal with 3-tuple return\r
- values, and can convert the parameter to a Unicode string like so:\r
-\r
- param = msg.get_param('foo')\r
- if isinstance(param, tuple):\r
- param = unicode(param[2], param[0] or 'us-ascii')\r
-\r
- In any case, the parameter value (either the returned string, or the\r
- VALUE item in the 3-tuple) is always unquoted, unless unquote is set\r
- to False.\r
- """\r
- if header not in self:\r
- return failobj\r
- for k, v in self._get_params_preserve(failobj, header):\r
- if k.lower() == param.lower():\r
- if unquote:\r
- return _unquotevalue(v)\r
- else:\r
- return v\r
- return failobj\r
-\r
- def set_param(self, param, value, header='Content-Type', requote=True,\r
- charset=None, language=''):\r
- """Set a parameter in the Content-Type header.\r
-\r
- If the parameter already exists in the header, its value will be\r
- replaced with the new value.\r
-\r
- If header is Content-Type and has not yet been defined for this\r
- message, it will be set to "text/plain" and the new parameter and\r
- value will be appended as per RFC 2045.\r
-\r
- An alternate header can specified in the header argument, and all\r
- parameters will be quoted as necessary unless requote is False.\r
-\r
- If charset is specified, the parameter will be encoded according to RFC\r
- 2231. Optional language specifies the RFC 2231 language, defaulting\r
- to the empty string. Both charset and language should be strings.\r
- """\r
- if not isinstance(value, tuple) and charset:\r
- value = (charset, language, value)\r
-\r
- if header not in self and header.lower() == 'content-type':\r
- ctype = 'text/plain'\r
- else:\r
- ctype = self.get(header)\r
- if not self.get_param(param, header=header):\r
- if not ctype:\r
- ctype = _formatparam(param, value, requote)\r
- else:\r
- ctype = SEMISPACE.join(\r
- [ctype, _formatparam(param, value, requote)])\r
- else:\r
- ctype = ''\r
- for old_param, old_value in self.get_params(header=header,\r
- unquote=requote):\r
- append_param = ''\r
- if old_param.lower() == param.lower():\r
- append_param = _formatparam(param, value, requote)\r
- else:\r
- append_param = _formatparam(old_param, old_value, requote)\r
- if not ctype:\r
- ctype = append_param\r
- else:\r
- ctype = SEMISPACE.join([ctype, append_param])\r
- if ctype != self.get(header):\r
- del self[header]\r
- self[header] = ctype\r
-\r
- def del_param(self, param, header='content-type', requote=True):\r
- """Remove the given parameter completely from the Content-Type header.\r
-\r
- The header will be re-written in place without the parameter or its\r
- value. All values will be quoted as necessary unless requote is\r
- False. Optional header specifies an alternative to the Content-Type\r
- header.\r
- """\r
- if header not in self:\r
- return\r
- new_ctype = ''\r
- for p, v in self.get_params(header=header, unquote=requote):\r
- if p.lower() != param.lower():\r
- if not new_ctype:\r
- new_ctype = _formatparam(p, v, requote)\r
- else:\r
- new_ctype = SEMISPACE.join([new_ctype,\r
- _formatparam(p, v, requote)])\r
- if new_ctype != self.get(header):\r
- del self[header]\r
- self[header] = new_ctype\r
-\r
- def set_type(self, type, header='Content-Type', requote=True):\r
- """Set the main type and subtype for the Content-Type header.\r
-\r
- type must be a string in the form "maintype/subtype", otherwise a\r
- ValueError is raised.\r
-\r
- This method replaces the Content-Type header, keeping all the\r
- parameters in place. If requote is False, this leaves the existing\r
- header's quoting as is. Otherwise, the parameters will be quoted (the\r
- default).\r
-\r
- An alternative header can be specified in the header argument. When\r
- the Content-Type header is set, we'll always also add a MIME-Version\r
- header.\r
- """\r
- # BAW: should we be strict?\r
- if not type.count('/') == 1:\r
- raise ValueError\r
- # Set the Content-Type, you get a MIME-Version\r
- if header.lower() == 'content-type':\r
- del self['mime-version']\r
- self['MIME-Version'] = '1.0'\r
- if header not in self:\r
- self[header] = type\r
- return\r
- params = self.get_params(header=header, unquote=requote)\r
- del self[header]\r
- self[header] = type\r
- # Skip the first param; it's the old type.\r
- for p, v in params[1:]:\r
- self.set_param(p, v, header, requote)\r
-\r
- def get_filename(self, failobj=None):\r
- """Return the filename associated with the payload if present.\r
-\r
- The filename is extracted from the Content-Disposition header's\r
- `filename' parameter, and it is unquoted. If that header is missing\r
- the `filename' parameter, this method falls back to looking for the\r
- `name' parameter.\r
- """\r
- missing = object()\r
- filename = self.get_param('filename', missing, 'content-disposition')\r
- if filename is missing:\r
- filename = self.get_param('name', missing, 'content-type')\r
- if filename is missing:\r
- return failobj\r
- return utils.collapse_rfc2231_value(filename).strip()\r
-\r
- def get_boundary(self, failobj=None):\r
- """Return the boundary associated with the payload if present.\r
-\r
- The boundary is extracted from the Content-Type header's `boundary'\r
- parameter, and it is unquoted.\r
- """\r
- missing = object()\r
- boundary = self.get_param('boundary', missing)\r
- if boundary is missing:\r
- return failobj\r
- # RFC 2046 says that boundaries may begin but not end in w/s\r
- return utils.collapse_rfc2231_value(boundary).rstrip()\r
-\r
- def set_boundary(self, boundary):\r
- """Set the boundary parameter in Content-Type to 'boundary'.\r
-\r
- This is subtly different than deleting the Content-Type header and\r
- adding a new one with a new boundary parameter via add_header(). The\r
- main difference is that using the set_boundary() method preserves the\r
- order of the Content-Type header in the original message.\r
-\r
- HeaderParseError is raised if the message has no Content-Type header.\r
- """\r
- missing = object()\r
- params = self._get_params_preserve(missing, 'content-type')\r
- if params is missing:\r
- # There was no Content-Type header, and we don't know what type\r
- # to set it to, so raise an exception.\r
- raise errors.HeaderParseError('No Content-Type header found')\r
- newparams = []\r
- foundp = False\r
- for pk, pv in params:\r
- if pk.lower() == 'boundary':\r
- newparams.append(('boundary', '"%s"' % boundary))\r
- foundp = True\r
- else:\r
- newparams.append((pk, pv))\r
- if not foundp:\r
- # The original Content-Type header had no boundary attribute.\r
- # Tack one on the end. BAW: should we raise an exception\r
- # instead???\r
- newparams.append(('boundary', '"%s"' % boundary))\r
- # Replace the existing Content-Type header with the new value\r
- newheaders = []\r
- for h, v in self._headers:\r
- if h.lower() == 'content-type':\r
- parts = []\r
- for k, v in newparams:\r
- if v == '':\r
- parts.append(k)\r
- else:\r
- parts.append('%s=%s' % (k, v))\r
- newheaders.append((h, SEMISPACE.join(parts)))\r
-\r
- else:\r
- newheaders.append((h, v))\r
- self._headers = newheaders\r
-\r
- def get_content_charset(self, failobj=None):\r
- """Return the charset parameter of the Content-Type header.\r
-\r
- The returned string is always coerced to lower case. If there is no\r
- Content-Type header, or if that header has no charset parameter,\r
- failobj is returned.\r
- """\r
- missing = object()\r
- charset = self.get_param('charset', missing)\r
- if charset is missing:\r
- return failobj\r
- if isinstance(charset, tuple):\r
- # RFC 2231 encoded, so decode it, and it better end up as ascii.\r
- pcharset = charset[0] or 'us-ascii'\r
- try:\r
- # LookupError will be raised if the charset isn't known to\r
- # Python. UnicodeError will be raised if the encoded text\r
- # contains a character not in the charset.\r
- charset = unicode(charset[2], pcharset).encode('us-ascii')\r
- except (LookupError, UnicodeError):\r
- charset = charset[2]\r
- # charset character must be in us-ascii range\r
- try:\r
- if isinstance(charset, str):\r
- charset = unicode(charset, 'us-ascii')\r
- charset = charset.encode('us-ascii')\r
- except UnicodeError:\r
- return failobj\r
- # RFC 2046, $4.1.2 says charsets are not case sensitive\r
- return charset.lower()\r
-\r
- def get_charsets(self, failobj=None):\r
- """Return a list containing the charset(s) used in this message.\r
-\r
- The returned list of items describes the Content-Type headers'\r
- charset parameter for this message and all the subparts in its\r
- payload.\r
-\r
- Each item will either be a string (the value of the charset parameter\r
- in the Content-Type header of that part) or the value of the\r
- 'failobj' parameter (defaults to None), if the part does not have a\r
- main MIME type of "text", or the charset is not defined.\r
-\r
- The list will contain one string for each part of the message, plus\r
- one for the container message (i.e. self), so that a non-multipart\r
- message will still return a list of length 1.\r
- """\r
- return [part.get_content_charset(failobj) for part in self.walk()]\r
-\r
- # I.e. def walk(self): ...\r
- from email.iterators import walk\r