PyXR

c:\python24\lib \ email \ Message.py



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
SourceForge.net Logo