PageRenderTime 60ms CodeModel.GetById 21ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 0ms

/circuits/web/headers.py

https://bitbucket.org/prologic/circuits/
Python | 254 lines | 223 code | 13 blank | 18 comment | 12 complexity | b02731d7d99e7358eafc2649d9bc9cbd MD5 | raw file
  1# Module:   headers
  2# Date:     1st February 2009 November 2008
  3# Author:   James Mills, prologic at shortcircuit dot net dot au
  4
  5"""Headers Support
  6
  7This module implements support for parsing and handling headers.
  8"""
  9
 10import re
 11from circuits.six import iteritems, u, b
 12
 13# Regular expression that matches `special' characters in parameters, the
 14# existance of which force quoting of the parameter value.
 15
 16tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
 17q_separator = re.compile(r'; *q *=')
 18
 19
 20def _formatparam(param, value=None, quote=1):
 21    """Convenience function to format and return a key=value pair.
 22
 23    This will quote the value if needed or if quote is true.
 24    """
 25    if value is not None and len(value) > 0:
 26        if quote or tspecials.search(value):
 27            value = value.replace('\\', '\\\\').replace('"', r'\"')
 28            return '%s="%s"' % (param, value)
 29        else:
 30            return '%s=%s' % (param, value)
 31    else:
 32        return param
 33
 34
 35def header_elements(fieldname, fieldvalue):
 36    """Return a sorted HeaderElement list.
 37
 38    Returns a sorted HeaderElement list
 39    from a comma-separated header string.
 40    """
 41
 42    if not fieldvalue:
 43        return []
 44
 45    result = []
 46    for element in fieldvalue.split(","):
 47        if fieldname.startswith("Accept") or fieldname == 'TE':
 48            hv = AcceptElement.from_str(element)
 49        else:
 50            hv = HeaderElement.from_str(element)
 51        result.append(hv)
 52
 53    return list(reversed(sorted(result)))
 54
 55
 56class HeaderElement(object):
 57    """An element (with parameters) from an HTTP header's element list."""
 58
 59    def __init__(self, value, params=None):
 60        self.value = value
 61        if params is None:
 62            params = {}
 63        self.params = params
 64
 65    def __eq__(self, other):
 66        return self.value == other.value
 67
 68    def __lt__(self, other):
 69        return self.value < other.value
 70
 71    def __str__(self):
 72        p = [";%s=%s" % (k, v) for k, v in iteritems(self.params)]
 73        return "%s%s" % (self.value, "".join(p))
 74
 75    def __bytes__(self):
 76        return b(self.__str__())
 77
 78    def __unicode__(self):
 79        return u(self.__str__())
 80
 81    def parse(elementstr):
 82        """Transform 'token;key=val' to ('token', {'key': 'val'})."""
 83        # Split the element into a value and parameters. The 'value' may
 84        # be of the form, "token=token", but we don't split that here.
 85        atoms = [x.strip() for x in elementstr.split(";") if x.strip()]
 86        if not atoms:
 87            initial_value = ''
 88        else:
 89            initial_value = atoms.pop(0).strip()
 90        params = {}
 91        for atom in atoms:
 92            atom = [x.strip() for x in atom.split("=", 1) if x.strip()]
 93            key = atom.pop(0)
 94            if atom:
 95                val = atom[0]
 96            else:
 97                val = ""
 98            params[key] = val
 99        return initial_value, params
100    parse = staticmethod(parse)
101
102    @classmethod
103    def from_str(cls, elementstr):
104        """Construct an instance from a string of the form 'token;key=val'."""
105        ival, params = cls.parse(elementstr)
106        return cls(ival, params)
107
108
109class AcceptElement(HeaderElement):
110    """An element (with parameters) from an Accept* header's element list.
111
112    AcceptElement objects are comparable; the more-preferred object will be
113    "less than" the less-preferred object. They are also therefore sortable;
114    if you sort a list of AcceptElement objects, they will be listed in
115    priority order; the most preferred value will be first. Yes, it should
116    have been the other way around, but it's too late to fix now.
117    """
118
119    @classmethod
120    def from_str(cls, elementstr):
121        qvalue = None
122        # The first "q" parameter (if any) separates the initial
123        # media-range parameter(s) (if any) from the accept-params.
124        atoms = q_separator.split(elementstr, 1)
125        media_range = atoms.pop(0).strip()
126        if atoms:
127            # The qvalue for an Accept header can have extensions. The other
128            # headers cannot, but it's easier to parse them as if they did.
129            qvalue = HeaderElement.from_str(atoms[0].strip())
130
131        media_type, params = cls.parse(media_range)
132        if qvalue is not None:
133            params["q"] = qvalue
134        return cls(media_type, params)
135
136    def qvalue(self):
137        val = self.params.get("q", "1")
138        if isinstance(val, HeaderElement):
139            val = val.value
140        return float(val)
141    qvalue = property(qvalue, doc="The qvalue, or priority, of this value.")
142
143    def __eq__(self, other):
144        return self.qvalue == other.qvalue
145
146    def __lt__(self, other):
147        if self.qvalue == other.qvalue:
148            return str(self) < str(other)
149        else:
150            return self.qvalue < other.qvalue
151
152
153class CaseInsensitiveDict(dict):
154    """A case-insensitive dict subclass.
155
156    Each key is changed on entry to str(key).title().
157    """
158
159    def __init__(self, *args, **kwargs):
160        d = dict(*args, **kwargs)
161        for key, value in iteritems(d):
162            dict.__setitem__(self, str(key).title(), value)
163        dict.__init__(self)
164
165    def __getitem__(self, key):
166        return dict.__getitem__(self, str(key).title())
167
168    def __setitem__(self, key, value):
169        dict.__setitem__(self, str(key).title(), value)
170
171    def __delitem__(self, key):
172        dict.__delitem__(self, str(key).title())
173
174    def __contains__(self, key):
175        return dict.__contains__(self, str(key).title())
176
177    def get(self, key, default=None):
178        return dict.get(self, str(key).title(), default)
179
180    def update(self, E):
181        for k in E.keys():
182            self[str(k).title()] = E[k]
183
184    @classmethod
185    def fromkeys(cls, seq, value=None):
186        newdict = cls()
187        for k in seq:
188            newdict[k] = value
189        return newdict
190
191    def setdefault(self, key, x=None):
192        key = str(key).title()
193        try:
194            return dict.__getitem__(self, key)
195        except KeyError:
196            self[key] = x
197            return x
198
199    def pop(self, key, default=None):
200        return dict.pop(self, str(key).title(), default)
201
202
203class Headers(CaseInsensitiveDict):
204
205    def elements(self, key):
206        """Return a sorted list of HeaderElements for the given header."""
207        return header_elements(key, self.get(key))
208
209    def get_all(self, name):
210        """Return a list of all the values for the named field."""
211        return [val.strip() for val in self.get(name, '').split(',')]
212
213    def __repr__(self):
214        return "Headers(%s)" % repr(list(self.items()))
215
216    def __str__(self):
217        headers = ["%s: %s\r\n" % (k, v) for k, v in self.items()]
218        return "".join(headers) + '\r\n'
219
220    def __bytes__(self):
221        return str(self).encode("latin1")
222
223    def append(self, key, value):
224        if not key in self:
225            self[key] = value
226        else:
227            self[key] = ", ".join([self[key], value])
228
229    def add_header(self, _name, _value, **_params):
230        """Extended header setting.
231
232        _name is the header field to add. keyword arguments can be used to set
233        additional parameters for the header field, with underscores converted
234        to dashes. Normally the parameter will be added as key="value" unless
235        value is None, in which case only the key will be added.
236
237        Example:
238
239        h.add_header('content-disposition', 'attachment', filename='bud.gif')
240
241        Note that unlike the corresponding 'email.Message' method, this does
242        *not* handle '(charset, language, value)' tuples: all values must be
243        strings or None.
244        """
245        parts = []
246        if _value is not None:
247            parts.append(_value)
248        for k, v in list(_params.items()):
249            k = k.replace('_', '-')
250            if v is None:
251                parts.append(k)
252            else:
253                parts.append(_formatparam(k, v))
254        self.append(_name, "; ".join(parts))