Commit beb5945c authored by Barry Warsaw's avatar Barry Warsaw

has_key(): Implement in terms of get().

get_type(): Use a compiled regular expression, which can be shared.

_get_params_preserve(): A helper method which extracts the header's
    parameter list preserving value quoting.  I'm not sure that this
    needs to be a public method.  It's necessary because we want
    get_param() and friends to return the unquoted parameter value,
    however we want the quote-preserved form for set_boundary().

get_params(), get_param(), set_boundary(): Implement in terms of
    _get_params_preserve().

walk(): Yield ourself first, then recurse over our subparts (if any).
parent 76fac8ed
...@@ -12,12 +12,13 @@ import quopri ...@@ -12,12 +12,13 @@ import quopri
from cStringIO import StringIO from cStringIO import StringIO
from types import ListType from types import ListType
SEMISPACE = '; '
# Intrapackage imports # Intrapackage imports
import Errors import Errors
import Utils import Utils
SEMISPACE = '; '
paramre = re.compile(r';\s*')
class Message: class Message:
...@@ -135,7 +136,7 @@ class Message: ...@@ -135,7 +136,7 @@ class Message:
# MAPPING INTERFACE (partial) # MAPPING INTERFACE (partial)
# #
def __len__(self): def __len__(self):
"""Get the total number of headers, including duplicates.""" """Return the total number of headers, including duplicates."""
return len(self._headers) return len(self._headers)
def __getitem__(self, name): def __getitem__(self, name):
...@@ -174,7 +175,8 @@ class Message: ...@@ -174,7 +175,8 @@ class Message:
def has_key(self, name): def has_key(self, name):
"""Return true if the message contains the header.""" """Return true if the message contains the header."""
return self[name] <> None missing = []
return self.get(name, missing) is not missing
def keys(self): def keys(self):
"""Return a list of all the message's header field names. """Return a list of all the message's header field names.
...@@ -267,7 +269,7 @@ class Message: ...@@ -267,7 +269,7 @@ class Message:
value = self.get('content-type', missing) value = self.get('content-type', missing)
if value is missing: if value is missing:
return failobj return failobj
return re.split(r';\s+', value)[0].lower() return paramre.split(value)[0].lower()
def get_main_type(self, failobj=None): def get_main_type(self, failobj=None):
"""Return the message's main content type if present.""" """Return the message's main content type if present."""
...@@ -291,18 +293,42 @@ class Message: ...@@ -291,18 +293,42 @@ class Message:
return ctype.split('/')[1] return ctype.split('/')[1]
return failobj return failobj
def _get_params_preserve(self, failobj, header):
# Like get_params() but preserves the quoting of values. BAW:
# should this be part of the public interface?
missing = []
value = self.get(header, missing)
if value is missing:
return failobj
params = []
for p in paramre.split(value):
try:
name, val = p.split('=', 1)
except ValueError:
# Must have been a bare attribute
name = p
val = ''
params.append((name, val))
return params
def get_params(self, failobj=None, header='content-type'): def get_params(self, failobj=None, header='content-type'):
"""Return the message's Content-Type: parameters, as a list. """Return the message's Content-Type: parameters, as a list.
The elements of the returned list are 2-tuples of key/value pairs, as
split on the `=' sign. The left hand side of the `=' is the key,
while the right hand side is the value. If there is no `=' sign in
the parameter the value is the empty string. The value is always
unquoted.
Optional failobj is the object to return if there is no Content-Type: Optional failobj is the object to return if there is no Content-Type:
header. Optional header is the header to search instead of header. Optional header is the header to search instead of
Content-Type: Content-Type:
""" """
missing = [] missing = []
value = self.get(header, missing) params = self._get_params_preserve(missing, header)
if value is missing: if params is missing:
return failobj return failobj
return re.split(r';\s+', value)[1:] return [(k, Utils.unquote(v)) for k, v in params]
def get_param(self, param, failobj=None, header='content-type'): def get_param(self, param, failobj=None, header='content-type'):
"""Return the parameter value if found in the Content-Type: header. """Return the parameter value if found in the Content-Type: header.
...@@ -310,21 +336,15 @@ class Message: ...@@ -310,21 +336,15 @@ class Message:
Optional failobj is the object to return if there is no Content-Type: Optional failobj is the object to return if there is no Content-Type:
header. Optional header is the header to search instead of header. Optional header is the header to search instead of
Content-Type: Content-Type:
Parameter keys are always compared case insensitively. Values are
always unquoted.
""" """
param = param.lower() if not self.has_key(header):
missing = []
params = self.get_params(missing, header=header)
if params is missing:
return failobj return failobj
for p in params: for k, v in self._get_params_preserve(failobj, header):
try: if k.lower() == param.lower():
name, val = p.split('=', 1) return Utils.unquote(v)
except ValueError:
# Must have been a bare attribute
name = p
val = ''
if name.lower() == param:
return Utils.unquote(val)
return failobj return failobj
def get_filename(self, failobj=None): def get_filename(self, failobj=None):
...@@ -361,31 +381,37 @@ class Message: ...@@ -361,31 +381,37 @@ class Message:
HeaderParseError is raised if the message has no Content-Type: header. HeaderParseError is raised if the message has no Content-Type: header.
""" """
params = self.get_params() missing = []
if not params: params = self._get_params_preserve(missing, 'content-type')
if params is missing:
# There was no Content-Type: header, and we don't know what type # There was no Content-Type: header, and we don't know what type
# to set it to, so raise an exception. # to set it to, so raise an exception.
raise Errors.HeaderParseError, 'No Content-Type: header found' raise Errors.HeaderParseError, 'No Content-Type: header found'
newparams = [] newparams = []
foundp = 0 foundp = 0
for p in params: for pk, pv in params:
if p.lower().startswith('boundary='): if pk.lower() == 'boundary':
newparams.append('boundary="%s"' % boundary) newparams.append(('boundary', '"%s"' % boundary))
foundp = 1 foundp = 1
else: else:
newparams.append(p) newparams.append((pk, pv))
if not foundp: if not foundp:
# The original Content-Type: header had no boundary attribute. # The original Content-Type: header had no boundary attribute.
# Tack one one the end. BAW: should we raise an exception # Tack one one the end. BAW: should we raise an exception
# instead??? # instead???
newparams.append('boundary="%s"' % boundary) newparams.append(('boundary', '"%s"' % boundary))
# Replace the existing Content-Type: header with the new value # Replace the existing Content-Type: header with the new value
newheaders = [] newheaders = []
for h, v in self._headers: for h, v in self._headers:
if h.lower() == 'content-type': if h.lower() == 'content-type':
value = v.split(';', 1)[0] parts = []
newparams.insert(0, value) for k, v in newparams:
newheaders.append((h, SEMISPACE.join(newparams))) if v == '':
parts.append(k)
else:
parts.append('%s=%s' % (k, v))
newheaders.append((h, SEMISPACE.join(parts)))
else: else:
newheaders.append((h, v)) newheaders.append((h, v))
self._headers = newheaders self._headers = newheaders
...@@ -396,12 +422,11 @@ class Message: ...@@ -396,12 +422,11 @@ class Message:
The walk is performed in breadth-first order. This method is a The walk is performed in breadth-first order. This method is a
generator. generator.
""" """
yield self
if self.is_multipart(): if self.is_multipart():
for subpart in self.get_payload(): for subpart in self.get_payload():
for subsubpart in subpart.walk(): for subsubpart in subpart.walk():
yield subsubpart yield subsubpart
else:
yield self
def get_charsets(self, failobj=None): def get_charsets(self, failobj=None):
"""Return a list containing the charset(s) used in this message. """Return a list containing the charset(s) used in this message.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment