1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
import six
from webob.multidict import MultiDict
from flanker.mime.message.headers import encodedword
from flanker.mime.message.headers.parsing import normalize, parse_stream
from flanker.mime.message.headers.encoding import to_mime
from flanker.mime.message.errors import EncodingError
class MimeHeaders(object):
"""Dictionary-like object that preserves the order and
supports multiple values for the same key, knows
whether it has been changed after the creation
"""
def __init__(self, items=()):
self._v = MultiDict([(normalize(key), remove_newlines(val))
for (key, val) in items])
self.changed = False
self.num_prepends = 0
def __getitem__(self, key):
v = self._v.get(normalize(key), None)
if v is not None:
return encodedword.decode(v)
return None
def __len__(self):
return len(self._v)
def __iter__(self):
return iter(self._v)
def __contains__(self, key):
return normalize(key) in self._v
def __setitem__(self, key, value):
key = normalize(key)
if key in self._v:
self._v[key] = remove_newlines(value)
self.changed = True
else:
self.prepend(key, remove_newlines(value))
def __delitem__(self, key):
del self._v[normalize(key)]
self.changed = True
def __nonzero__(self):
return len(self._v) > 0
def prepend(self, key, value):
self._v._items.insert(0, (normalize(key), remove_newlines(value)))
self.num_prepends += 1
def add(self, key, value):
"""Adds header without changing the
existing headers with same name"""
self.prepend(key, value)
def keys(self):
"""
Returns the keys. (message header names)
It remembers the order in which they were added, what
is really important
"""
return self._v.keys()
def transform(self, fn, decode=False):
"""Accepts a function, getting a key, val and returning
a new pair of key, val and applies the function to all
header, value pairs in the message.
"""
changed = [False]
def wrapper(key, val):
new_key, new_val = fn(key, val)
if new_val != val or new_key != key:
changed[0] = True
return new_key, new_val
v = MultiDict(wrapper(k, v) for k, v in self.iteritems(raw=not decode))
if changed[0]:
self._v = v
self.changed = True
def items(self):
"""
Returns header,val pairs in the preserved order.
"""
return list(self.iteritems())
def iteritems(self, raw=False):
"""
Returns iterator header,val pairs in the preserved order.
"""
if raw:
return self._v.iteritems()
return iter([(x[0], encodedword.decode(x[1]))
for x in self._v.iteritems()])
def get(self, key, default=None):
"""
Returns header value (case-insensitive).
"""
v = self._v.get(normalize(key), default)
if v is not None:
return encodedword.decode(v)
return None
def getraw(self, key, default=None):
"""
Returns raw header value (case-insensitive, non-decoded.
"""
return self._v.get(normalize(key), default)
def getall(self, key):
"""
Returns all header values by the given header name (case-insensitive).
"""
v = self._v.getall(normalize(key))
return [encodedword.decode(x) for x in v]
def have_changed(self, ignore_prepends=False):
"""
Tells whether someone has altered the headers after creation.
"""
return self.changed or (self.num_prepends > 0 and not ignore_prepends)
def __str__(self):
return str(self._v)
@classmethod
def from_stream(cls, stream):
"""
Takes a stream and reads the headers, decodes headers to unicode dict
like object.
"""
return cls(parse_stream(stream))
def to_stream(self, stream, prepends_only=False):
"""
Takes a stream and serializes headers in a mime format.
"""
i = 0
for h, v in self.iteritems(raw=True):
if prepends_only and i == self.num_prepends:
break
i += 1
try:
h.encode('ascii')
except UnicodeDecodeError:
raise EncodingError("Non-ascii header name")
stream.write("{0}: {1}\r\n".format(h, to_mime(h, v)))
def remove_newlines(value):
if not value:
return ''
elif isinstance(value, six.string_types):
return value.replace('\r', '').replace('\n', '')
else:
return value
|