PageRenderTime 22ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/desktop/core/ext-py/Paste-1.7.2/paste/request.py

https://github.com/jcrobak/hue
Python | 405 lines | 343 code | 14 blank | 48 comment | 13 complexity | 767e6821b43f6a5d8c9a4fa93036ee7c MD5 | raw file
  1. # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
  2. # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
  3. # (c) 2005 Ian Bicking and contributors
  4. # This module is part of the Python Paste Project and is released under
  5. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  6. """
  7. This module provides helper routines with work directly on a WSGI
  8. environment to solve common requirements.
  9. * get_cookies(environ)
  10. * parse_querystring(environ)
  11. * parse_formvars(environ, include_get_vars=True)
  12. * construct_url(environ, with_query_string=True, with_path_info=True,
  13. script_name=None, path_info=None, querystring=None)
  14. * path_info_split(path_info)
  15. * path_info_pop(environ)
  16. * resolve_relative_url(url, environ)
  17. """
  18. import cgi
  19. from Cookie import SimpleCookie
  20. from StringIO import StringIO
  21. import urlparse
  22. import urllib
  23. try:
  24. from UserDict import DictMixin
  25. except ImportError:
  26. from paste.util.UserDict24 import DictMixin
  27. from paste.util.multidict import MultiDict
  28. __all__ = ['get_cookies', 'get_cookie_dict', 'parse_querystring',
  29. 'parse_formvars', 'construct_url', 'path_info_split',
  30. 'path_info_pop', 'resolve_relative_url', 'EnvironHeaders']
  31. def get_cookies(environ):
  32. """
  33. Gets a cookie object (which is a dictionary-like object) from the
  34. request environment; caches this value in case get_cookies is
  35. called again for the same request.
  36. """
  37. header = environ.get('HTTP_COOKIE', '')
  38. if environ.has_key('paste.cookies'):
  39. cookies, check_header = environ['paste.cookies']
  40. if check_header == header:
  41. return cookies
  42. cookies = SimpleCookie()
  43. cookies.load(header)
  44. environ['paste.cookies'] = (cookies, header)
  45. return cookies
  46. def get_cookie_dict(environ):
  47. """Return a *plain* dictionary of cookies as found in the request.
  48. Unlike ``get_cookies`` this returns a dictionary, not a
  49. ``SimpleCookie`` object. For incoming cookies a dictionary fully
  50. represents the information. Like ``get_cookies`` this caches and
  51. checks the cache.
  52. """
  53. header = environ.get('HTTP_COOKIE')
  54. if not header:
  55. return {}
  56. if environ.has_key('paste.cookies.dict'):
  57. cookies, check_header = environ['paste.cookies.dict']
  58. if check_header == header:
  59. return cookies
  60. cookies = SimpleCookie()
  61. cookies.load(header)
  62. result = {}
  63. for name in cookies:
  64. result[name] = cookies[name].value
  65. environ['paste.cookies.dict'] = (result, header)
  66. return result
  67. def parse_querystring(environ):
  68. """
  69. Parses a query string into a list like ``[(name, value)]``.
  70. Caches this value in case parse_querystring is called again
  71. for the same request.
  72. You can pass the result to ``dict()``, but be aware that keys that
  73. appear multiple times will be lost (only the last value will be
  74. preserved).
  75. """
  76. source = environ.get('QUERY_STRING', '')
  77. if not source:
  78. return []
  79. if 'paste.parsed_querystring' in environ:
  80. parsed, check_source = environ['paste.parsed_querystring']
  81. if check_source == source:
  82. return parsed
  83. parsed = cgi.parse_qsl(source, keep_blank_values=True,
  84. strict_parsing=False)
  85. environ['paste.parsed_querystring'] = (parsed, source)
  86. return parsed
  87. def parse_dict_querystring(environ):
  88. """Parses a query string like parse_querystring, but returns a MultiDict
  89. Caches this value in case parse_dict_querystring is called again
  90. for the same request.
  91. Example::
  92. >>> environ = {'QUERY_STRING': 'day=Monday&user=fred&user=jane'}
  93. >>> parsed = parse_dict_querystring(environ)
  94. >>> parsed['day']
  95. 'Monday'
  96. >>> parsed['user']
  97. 'fred'
  98. >>> parsed.getall('user')
  99. ['fred', 'jane']
  100. """
  101. source = environ.get('QUERY_STRING', '')
  102. if not source:
  103. return MultiDict()
  104. if 'paste.parsed_dict_querystring' in environ:
  105. parsed, check_source = environ['paste.parsed_dict_querystring']
  106. if check_source == source:
  107. return parsed
  108. parsed = cgi.parse_qsl(source, keep_blank_values=True,
  109. strict_parsing=False)
  110. multi = MultiDict(parsed)
  111. environ['paste.parsed_dict_querystring'] = (multi, source)
  112. return multi
  113. def parse_formvars(environ, include_get_vars=True):
  114. """Parses the request, returning a MultiDict of form variables.
  115. If ``include_get_vars`` is true then GET (query string) variables
  116. will also be folded into the MultiDict.
  117. All values should be strings, except for file uploads which are
  118. left as ``FieldStorage`` instances.
  119. If the request was not a normal form request (e.g., a POST with an
  120. XML body) then ``environ['wsgi.input']`` won't be read.
  121. """
  122. source = environ['wsgi.input']
  123. if 'paste.parsed_formvars' in environ:
  124. parsed, check_source = environ['paste.parsed_formvars']
  125. if check_source == source:
  126. if include_get_vars:
  127. parsed.update(parse_querystring(environ))
  128. return parsed
  129. # @@: Shouldn't bother FieldStorage parsing during GET/HEAD and
  130. # fake_out_cgi requests
  131. type = environ.get('CONTENT_TYPE', '').lower()
  132. if ';' in type:
  133. type = type.split(';', 1)[0]
  134. fake_out_cgi = type not in ('', 'application/x-www-form-urlencoded',
  135. 'multipart/form-data')
  136. # FieldStorage assumes a default CONTENT_LENGTH of -1, but a
  137. # default of 0 is better:
  138. if not environ.get('CONTENT_LENGTH'):
  139. environ['CONTENT_LENGTH'] = '0'
  140. # Prevent FieldStorage from parsing QUERY_STRING during GET/HEAD
  141. # requests
  142. old_query_string = environ.get('QUERY_STRING','')
  143. environ['QUERY_STRING'] = ''
  144. if fake_out_cgi:
  145. input = StringIO('')
  146. old_content_type = environ.get('CONTENT_TYPE')
  147. old_content_length = environ.get('CONTENT_LENGTH')
  148. environ['CONTENT_LENGTH'] = '0'
  149. environ['CONTENT_TYPE'] = ''
  150. else:
  151. input = environ['wsgi.input']
  152. fs = cgi.FieldStorage(fp=input,
  153. environ=environ,
  154. keep_blank_values=1)
  155. environ['QUERY_STRING'] = old_query_string
  156. if fake_out_cgi:
  157. environ['CONTENT_TYPE'] = old_content_type
  158. environ['CONTENT_LENGTH'] = old_content_length
  159. formvars = MultiDict()
  160. if isinstance(fs.value, list):
  161. for name in fs.keys():
  162. values = fs[name]
  163. if not isinstance(values, list):
  164. values = [values]
  165. for value in values:
  166. if not value.filename:
  167. value = value.value
  168. formvars.add(name, value)
  169. environ['paste.parsed_formvars'] = (formvars, source)
  170. if include_get_vars:
  171. formvars.update(parse_querystring(environ))
  172. return formvars
  173. def construct_url(environ, with_query_string=True, with_path_info=True,
  174. script_name=None, path_info=None, querystring=None):
  175. """Reconstructs the URL from the WSGI environment.
  176. You may override SCRIPT_NAME, PATH_INFO, and QUERYSTRING with
  177. the keyword arguments.
  178. """
  179. url = environ['wsgi.url_scheme']+'://'
  180. if environ.get('HTTP_HOST'):
  181. host = environ['HTTP_HOST']
  182. port = None
  183. if ':' in host:
  184. host, port = host.split(':', 1)
  185. if environ['wsgi.url_scheme'] == 'https':
  186. if port == '443':
  187. port = None
  188. elif environ['wsgi.url_scheme'] == 'http':
  189. if port == '80':
  190. port = None
  191. url += host
  192. if port:
  193. url += ':%s' % port
  194. else:
  195. url += environ['SERVER_NAME']
  196. if environ['wsgi.url_scheme'] == 'https':
  197. if environ['SERVER_PORT'] != '443':
  198. url += ':' + environ['SERVER_PORT']
  199. else:
  200. if environ['SERVER_PORT'] != '80':
  201. url += ':' + environ['SERVER_PORT']
  202. if script_name is None:
  203. url += urllib.quote(environ.get('SCRIPT_NAME',''))
  204. else:
  205. url += urllib.quote(script_name)
  206. if with_path_info:
  207. if path_info is None:
  208. url += urllib.quote(environ.get('PATH_INFO',''))
  209. else:
  210. url += urllib.quote(path_info)
  211. if with_query_string:
  212. if querystring is None:
  213. if environ.get('QUERY_STRING'):
  214. url += '?' + environ['QUERY_STRING']
  215. elif querystring:
  216. url += '?' + querystring
  217. return url
  218. def resolve_relative_url(url, environ):
  219. """
  220. Resolve the given relative URL as being relative to the
  221. location represented by the environment. This can be used
  222. for redirecting to a relative path. Note: if url is already
  223. absolute, this function will (intentionally) have no effect
  224. on it.
  225. """
  226. cur_url = construct_url(environ, with_query_string=False)
  227. return urlparse.urljoin(cur_url, url)
  228. def path_info_split(path_info):
  229. """
  230. Splits off the first segment of the path. Returns (first_part,
  231. rest_of_path). first_part can be None (if PATH_INFO is empty), ''
  232. (if PATH_INFO is '/'), or a name without any /'s. rest_of_path
  233. can be '' or a string starting with /.
  234. """
  235. if not path_info:
  236. return None, ''
  237. assert path_info.startswith('/'), (
  238. "PATH_INFO should start with /: %r" % path_info)
  239. path_info = path_info.lstrip('/')
  240. if '/' in path_info:
  241. first, rest = path_info.split('/', 1)
  242. return first, '/' + rest
  243. else:
  244. return path_info, ''
  245. def path_info_pop(environ):
  246. """
  247. 'Pops' off the next segment of PATH_INFO, pushing it onto
  248. SCRIPT_NAME, and returning that segment.
  249. For instance::
  250. >>> def call_it(script_name, path_info):
  251. ... env = {'SCRIPT_NAME': script_name, 'PATH_INFO': path_info}
  252. ... result = path_info_pop(env)
  253. ... print 'SCRIPT_NAME=%r; PATH_INFO=%r; returns=%r' % (
  254. ... env['SCRIPT_NAME'], env['PATH_INFO'], result)
  255. >>> call_it('/foo', '/bar')
  256. SCRIPT_NAME='/foo/bar'; PATH_INFO=''; returns='bar'
  257. >>> call_it('/foo/bar', '')
  258. SCRIPT_NAME='/foo/bar'; PATH_INFO=''; returns=None
  259. >>> call_it('/foo/bar', '/')
  260. SCRIPT_NAME='/foo/bar/'; PATH_INFO=''; returns=''
  261. >>> call_it('', '/1/2/3')
  262. SCRIPT_NAME='/1'; PATH_INFO='/2/3'; returns='1'
  263. >>> call_it('', '//1/2')
  264. SCRIPT_NAME='//1'; PATH_INFO='/2'; returns='1'
  265. """
  266. path = environ.get('PATH_INFO', '')
  267. if not path:
  268. return None
  269. while path.startswith('/'):
  270. environ['SCRIPT_NAME'] += '/'
  271. path = path[1:]
  272. if '/' not in path:
  273. environ['SCRIPT_NAME'] += path
  274. environ['PATH_INFO'] = ''
  275. return path
  276. else:
  277. segment, path = path.split('/', 1)
  278. environ['PATH_INFO'] = '/' + path
  279. environ['SCRIPT_NAME'] += segment
  280. return segment
  281. _parse_headers_special = {
  282. # This is a Zope convention, but we'll allow it here:
  283. 'HTTP_CGI_AUTHORIZATION': 'Authorization',
  284. 'CONTENT_LENGTH': 'Content-Length',
  285. 'CONTENT_TYPE': 'Content-Type',
  286. }
  287. def parse_headers(environ):
  288. """
  289. Parse the headers in the environment (like ``HTTP_HOST``) and
  290. yield a sequence of those (header_name, value) tuples.
  291. """
  292. # @@: Maybe should parse out comma-separated headers?
  293. for cgi_var, value in environ.iteritems():
  294. if cgi_var in _parse_headers_special:
  295. yield _parse_headers_special[cgi_var], value
  296. elif cgi_var.startswith('HTTP_'):
  297. yield cgi_var[5:].title().replace('_', '-'), value
  298. class EnvironHeaders(DictMixin):
  299. """An object that represents the headers as present in a
  300. WSGI environment.
  301. This object is a wrapper (with no internal state) for a WSGI
  302. request object, representing the CGI-style HTTP_* keys as a
  303. dictionary. Because a CGI environment can only hold one value for
  304. each key, this dictionary is single-valued (unlike outgoing
  305. headers).
  306. """
  307. def __init__(self, environ):
  308. self.environ = environ
  309. def _trans_name(self, name):
  310. key = 'HTTP_'+name.replace('-', '_').upper()
  311. if key == 'HTTP_CONTENT_LENGTH':
  312. key = 'CONTENT_LENGTH'
  313. elif key == 'HTTP_CONTENT_TYPE':
  314. key = 'CONTENT_TYPE'
  315. return key
  316. def _trans_key(self, key):
  317. if key == 'CONTENT_TYPE':
  318. return 'Content-Type'
  319. elif key == 'CONTENT_LENGTH':
  320. return 'Content-Length'
  321. elif key.startswith('HTTP_'):
  322. return key[5:].replace('_', '-').title()
  323. else:
  324. return None
  325. def __getitem__(self, item):
  326. return self.environ[self._trans_name(item)]
  327. def __setitem__(self, item, value):
  328. # @@: Should this dictionary be writable at all?
  329. self.environ[self._trans_name(item)] = value
  330. def __delitem__(self, item):
  331. del self.environ[self._trans_name(item)]
  332. def __iter__(self):
  333. for key in self.environ:
  334. name = self._trans_key(key)
  335. if name is not None:
  336. yield name
  337. def keys(self):
  338. return list(iter(self))
  339. def __contains__(self, item):
  340. return self._trans_name(item) in self.environ
  341. def _cgi_FieldStorage__repr__patch(self):
  342. """ monkey patch for FieldStorage.__repr__
  343. Unbelievely, the default __repr__ on FieldStorage reads
  344. the entire file content instead of being sane about it.
  345. This is a simple replacement that doesn't do that
  346. """
  347. if self.file:
  348. return "FieldStorage(%r, %r)" % (
  349. self.name, self.filename)
  350. return "FieldStorage(%r, %r, %r)" % (
  351. self.name, self.filename, self.value)
  352. cgi.FieldStorage.__repr__ = _cgi_FieldStorage__repr__patch
  353. if __name__ == '__main__':
  354. import doctest
  355. doctest.testmod()