File: headers.py

package info (click to toggle)
python-flanker 0.9.15-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 17,976 kB
  • sloc: python: 9,308; makefile: 4
file content (164 lines) | stat: -rw-r--r-- 4,867 bytes parent folder | download
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