/circuits/web/utils.py

https://bitbucket.org/prologic/circuits/ · Python · 386 lines · 318 code · 28 blank · 40 comment · 32 complexity · f1e4ee7c8b207992626e90768971b16e MD5 · raw file

  1. # Module: utils
  2. # Date: 13th September 2007
  3. # Author: James Mills, prologic at shortcircuit dot net dot au
  4. """Utilities
  5. This module implements utility functions.
  6. """
  7. import re
  8. import zlib
  9. import time
  10. import struct
  11. from math import sqrt
  12. from io import TextIOWrapper
  13. from cgi import FieldStorage
  14. from collections import MutableMapping
  15. try:
  16. from urllib.parse import urljoin as _urljoin
  17. except ImportError:
  18. from urlparse import urljoin as _urljoin # NOQA
  19. try:
  20. from urllib.parse import parse_qs as _parse_qs
  21. except ImportError:
  22. from cgi import parse_qs as _parse_qs # NOQA
  23. from circuits.six import iterbytes
  24. from .exceptions import RangeUnsatisfiable, RequestEntityTooLarge
  25. quoted_slash = re.compile("(?i)%2F")
  26. image_map_pattern = re.compile("^[0-9]+,[0-9]+$")
  27. def average(xs):
  28. return sum(xs) * 1.0 / len(xs)
  29. def variance(xs):
  30. avg = average(xs)
  31. return list(map(lambda x: (x - avg)**2, xs))
  32. def stddev(xs):
  33. return sqrt(average(variance(xs)))
  34. def parse_body(request, response, params):
  35. if "Content-Type" not in request.headers:
  36. request.headers["Content-Type"] = ""
  37. try:
  38. form = FieldStorage(
  39. environ={"REQUEST_METHOD": "POST"},
  40. fp=TextIOWrapper(request.body),
  41. headers=request.headers,
  42. keep_blank_values=True
  43. )
  44. except Exception as e:
  45. if e.__class__.__name__ == 'MaxSizeExceeded':
  46. # Post data is too big
  47. raise RequestEntityTooLarge()
  48. raise
  49. if form.file:
  50. request.body = form.file
  51. else:
  52. params.update(dictform(form))
  53. def parse_qs(query_string, keep_blank_values=True):
  54. """parse_qs(query_string) -> dict
  55. Build a params dictionary from a query_string.
  56. If keep_blank_values is True (the default), keep
  57. values that are blank.
  58. """
  59. if image_map_pattern.match(query_string):
  60. # Server-side image map. Map the coords to "x" and "y"
  61. # (like CGI::Request does).
  62. pm = query_string.split(",")
  63. return {"x": int(pm[0]), "y": int(pm[1])}
  64. else:
  65. pm = _parse_qs(query_string, keep_blank_values)
  66. return dict((k, v[0]) for k, v in pm.items() if v)
  67. def dictform(form):
  68. d = {}
  69. for key in list(form.keys()):
  70. values = form[key]
  71. if isinstance(values, list):
  72. d[key] = []
  73. for item in values:
  74. if item.filename is not None:
  75. value = item # It's a file upload
  76. else:
  77. value = item.value # It's a regular field
  78. d[key].append(value)
  79. else:
  80. if values.filename is not None:
  81. value = values # It's a file upload
  82. else:
  83. value = values.value # It's a regular field
  84. d[key] = value
  85. return d
  86. def compress(body, compress_level):
  87. """Compress 'body' at the given compress_level."""
  88. # Header
  89. yield b"\037\213\010\0" \
  90. + struct.pack("<L", int(time.time())) \
  91. + b"\002\377"
  92. size = 0
  93. crc = zlib.crc32(b"")
  94. zobj = zlib.compressobj(
  95. compress_level,
  96. zlib.DEFLATED,
  97. -zlib.MAX_WBITS,
  98. zlib.DEF_MEM_LEVEL,
  99. 0,
  100. )
  101. for chunk in body:
  102. if not isinstance(chunk, bytes):
  103. chunk = chunk.encode("utf-8")
  104. size += len(chunk)
  105. crc = zlib.crc32(chunk, crc)
  106. yield zobj.compress(chunk)
  107. yield zobj.flush() \
  108. + struct.pack("<l", crc) \
  109. + struct.pack("<L", size & 0xFFFFFFFF)
  110. def get_ranges(headervalue, content_length):
  111. """Return a list of (start, stop) indices from a Range header, or None.
  112. Each (start, stop) tuple will be composed of two ints, which are suitable
  113. for use in a slicing operation. That is, the header "Range: bytes=3-6",
  114. if applied against a Python string, is requesting resource[3:7]. This
  115. function will return the list [(3, 7)].
  116. If this function returns an empty list, you should return HTTP 416.
  117. """
  118. if not headervalue:
  119. return None
  120. result = []
  121. bytesunit, byteranges = headervalue.split("=", 1)
  122. for brange in byteranges.split(","):
  123. start, stop = [x.strip() for x in brange.split("-", 1)]
  124. if start:
  125. if not stop:
  126. stop = content_length - 1
  127. start, stop = list(map(int, (start, stop)))
  128. if start >= content_length:
  129. # From rfc 2616 sec 14.16:
  130. # "If the server receives a request (other than one
  131. # including an If-Range request-header field) with an
  132. # unsatisfiable Range request-header field (that is,
  133. # all of whose byte-range-spec values have a first-byte-pos
  134. # value greater than the current length of the selected
  135. # resource), it SHOULD return a response code of 416
  136. # (Requested range not satisfiable)."
  137. continue
  138. if stop < start:
  139. # From rfc 2616 sec 14.16:
  140. # "If the server ignores a byte-range-spec because it
  141. # is syntactically invalid, the server SHOULD treat
  142. # the request as if the invalid Range header field
  143. # did not exist. (Normally, this means return a 200
  144. # response containing the full entity)."
  145. return None
  146. # Prevent duplicate ranges. See Issue #59
  147. if (start, stop + 1) not in result:
  148. result.append((start, stop + 1))
  149. else:
  150. if not stop:
  151. # See rfc quote above.
  152. return None
  153. # Negative subscript (last N bytes)
  154. # Prevent duplicate ranges. See Issue #59
  155. if (content_length - int(stop), content_length) not in result:
  156. result.append((content_length - int(stop), content_length))
  157. # Can we satisfy the requested Range?
  158. # If we have an exceedingly high standard deviation
  159. # of Range(s) we reject the request.
  160. # See Issue #59
  161. if len(result) > 1 and stddev([x[1] - x[0] for x in result]) > 2.0:
  162. raise RangeUnsatisfiable()
  163. return result
  164. class IOrderedDict(dict, MutableMapping):
  165. 'Dictionary that remembers insertion order with insensitive key'
  166. # An inherited dict maps keys to values.
  167. # The inherited dict provides __getitem__, __len__, __contains__, and get.
  168. # The remaining methods are order-aware.
  169. # Big-O running times for all methods are the same as for regular dictionaries.
  170. # The internal self.__map dictionary maps keys to links in a doubly linked list.
  171. # The circular doubly linked list starts and ends with a sentinel element.
  172. # The sentinel element never gets deleted (this simplifies the algorithm).
  173. # Each link is stored as a list of length three: [PREV, NEXT, KEY].
  174. def __init__(self, *args, **kwds):
  175. '''Initialize an ordered dictionary. Signature is the same as for
  176. regular dictionaries, but keyword arguments are not recommended
  177. because their insertion order is arbitrary.
  178. '''
  179. if len(args) > 1:
  180. raise TypeError('expected at most 1 arguments, got %d' % len(args))
  181. try:
  182. self.__root
  183. except AttributeError:
  184. self.__root = root = [None, None, None] # sentinel node
  185. PREV = 0
  186. NEXT = 1
  187. root[PREV] = root[NEXT] = root
  188. self.__map = {}
  189. self.__lower = {}
  190. self.update(*args, **kwds)
  191. def __setitem__(self, key, value, PREV=0, NEXT=1, dict_setitem=dict.__setitem__):
  192. 'od.__setitem__(i, y) <==> od[i]=y'
  193. # Setting a new item creates a new link which goes at the end of the linked
  194. # list, and the inherited dictionary is updated with the new key/value pair.
  195. if key not in self:
  196. root = self.__root
  197. last = root[PREV]
  198. last[NEXT] = root[PREV] = self.__map[key] = [last, root, key]
  199. self.__lower[key.lower()] = key
  200. key = self.__lower[key.lower()]
  201. dict_setitem(self, key, value)
  202. def __delitem__(self, key, PREV=0, NEXT=1, dict_delitem=dict.__delitem__):
  203. 'od.__delitem__(y) <==> del od[y]'
  204. # Deleting an existing item uses self.__map to find the link which is
  205. # then removed by updating the links in the predecessor and successor nodes.
  206. if key in self:
  207. key = self.__lower.pop(key.lower())
  208. dict_delitem(self, key)
  209. link = self.__map.pop(key)
  210. link_prev = link[PREV]
  211. link_next = link[NEXT]
  212. link_prev[NEXT] = link_next
  213. link_next[PREV] = link_prev
  214. def __getitem__(self, key, dict_getitem=dict.__getitem__):
  215. if key in self:
  216. key = self.__lower.get(key.lower())
  217. return dict_getitem(self, key)
  218. def __contains__(self, key):
  219. return key.lower() in self.__lower
  220. def __iter__(self, NEXT=1, KEY=2):
  221. 'od.__iter__() <==> iter(od)'
  222. # Traverse the linked list in order.
  223. root = self.__root
  224. curr = root[NEXT]
  225. while curr is not root:
  226. yield curr[KEY]
  227. curr = curr[NEXT]
  228. def __reversed__(self, PREV=0, KEY=2):
  229. 'od.__reversed__() <==> reversed(od)'
  230. # Traverse the linked list in reverse order.
  231. root = self.__root
  232. curr = root[PREV]
  233. while curr is not root:
  234. yield curr[KEY]
  235. curr = curr[PREV]
  236. def __reduce__(self):
  237. 'Return state information for pickling'
  238. items = [[k, self[k]] for k in self]
  239. tmp = self.__map, self.__root
  240. del self.__map, self.__root
  241. inst_dict = vars(self).copy()
  242. self.__map, self.__root = tmp
  243. if inst_dict:
  244. return (self.__class__, (items,), inst_dict)
  245. return self.__class__, (items,)
  246. def clear(self):
  247. 'od.clear() -> None. Remove all items from od.'
  248. try:
  249. for node in self.__map.values():
  250. del node[:]
  251. self.__root[:] = [self.__root, self.__root, None]
  252. self.__map.clear()
  253. except AttributeError:
  254. pass
  255. dict.clear(self)
  256. def get(self, key, default=None):
  257. if key in self:
  258. return self[key]
  259. return default
  260. setdefault = MutableMapping.setdefault
  261. update = MutableMapping.update
  262. pop = MutableMapping.pop
  263. keys = MutableMapping.keys
  264. values = MutableMapping.values
  265. items = MutableMapping.items
  266. __ne__ = MutableMapping.__ne__
  267. def popitem(self, last=True):
  268. '''od.popitem() -> (k, v), return and remove a (key, value) pair.
  269. Pairs are returned in LIFO order if last is true or FIFO order if false.
  270. '''
  271. if not self:
  272. raise KeyError('dictionary is empty')
  273. key = next(reversed(self) if last else iter(self))
  274. value = self.pop(key)
  275. return key, value
  276. def __repr__(self):
  277. 'od.__repr__() <==> repr(od)'
  278. if not self:
  279. return '%s()' % (self.__class__.__name__,)
  280. return '%s(%r)' % (self.__class__.__name__, list(self.items()))
  281. def copy(self):
  282. 'od.copy() -> a shallow copy of od'
  283. return self.__class__(self)
  284. @classmethod
  285. def fromkeys(cls, iterable, value=None):
  286. '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
  287. and values equal to v (which defaults to None).
  288. '''
  289. d = cls()
  290. for key in iterable:
  291. d[key] = value
  292. return d
  293. def __eq__(self, other):
  294. '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
  295. while comparison to a regular mapping is order-insensitive.
  296. '''
  297. if isinstance(other, IOrderedDict):
  298. return (
  299. len(self) == len(other)
  300. and set(self.items()) == set(other.items())
  301. )
  302. return dict.__eq__(self, other)
  303. def __del__(self):
  304. self.clear() # eliminate cyclical references
  305. def is_ssl_handshake(buf):
  306. """Detect an SSLv2 or SSLv3 handshake"""
  307. # SSLv3
  308. v = buf[:3]
  309. if v in ["\x16\x03\x00", "\x16\x03\x01", "\x16\x03\x02"]:
  310. return True
  311. # SSLv2
  312. v = list(iterbytes(buf))[:2]
  313. if (v[0] & 0x80 == 0x80) and ((v[0] & 0x7f) << 8 | v[1]) > 9:
  314. return True