diff -r 000000000000 -r b97547f5f1fa wsgi/request.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wsgi/request.py Wed Nov 05 15:52:50 2008 +0100 @@ -0,0 +1,181 @@ +"""WSGI request adapter for cubicweb + +NOTE: each docstring tagged with ``COME FROM DJANGO`` means that +the code has been taken (or adapted) from Djanco source code : + http://www.djangoproject.com/ + +:organization: Logilab +:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +__docformat__ = "restructuredtext en" + +from StringIO import StringIO +from urllib import quote + +from logilab.common.decorators import cached + +from cubicweb.web.request import CubicWebRequestBase +from cubicweb.wsgi import (pformat, qs2dict, safe_copyfileobj, parse_file_upload, + normalize_header) + + + +class CubicWebWsgiRequest(CubicWebRequestBase): + """most of this code COMES FROM DJANO + """ + + def __init__(self, environ, vreg, base_url=None): + self.environ = environ + self.path = environ['PATH_INFO'] + self.method = environ['REQUEST_METHOD'].upper() + self._headers = dict([(normalize_header(k[5:]), v) for k, v in self.environ.items() + if k.startswith('HTTP_')]) + https = environ.get("HTTPS") in ('yes', 'on', '1') + self._base_url = base_url or self.application_uri() + post, files = self.get_posted_data() + super(CubicWebWsgiRequest, self).__init__(vreg, https, post) + if files is not None: + for fdef in files.itervalues(): + fdef[0] = unicode(fdef[0], self.encoding) + self.form.update(files) + # prepare output headers + self.headers_out = {} + + def __repr__(self): + # Since this is called as part of error handling, we need to be very + # robust against potentially malformed input. + form = pformat(self.form) + meta = pformat(self.environ) + return '' % \ + (form, meta) + + ## cubicweb request interface ################################################ + + def base_url(self): + return self._base_url + + def http_method(self): + """returns 'POST', 'GET', 'HEAD', etc.""" + return self.method + + def relative_path(self, includeparams=True): + """return the normalized path of the request (ie at least relative + to the application's root, but some other normalization may be needed + so that the returned path may be used to compare to generated urls + + :param includeparams: + boolean indicating if GET form parameters should be kept in the path + """ + path = self.environ['PATH_INFO'] + path = path[1:] # remove leading '/' + if includeparams: + qs = self.environ.get('QUERY_STRING') + if qs: + return '%s?%s' % (path, qs) + + return path + + def get_header(self, header, default=None): + """return the value associated with the given input HTTP header, + raise KeyError if the header is not set + """ + return self._headers.get(normalize_header(header), default) + + def set_header(self, header, value, raw=True): + """set an output HTTP header""" + assert raw, "don't know anything about non-raw headers for wsgi requests" + self.headers_out[header] = value + + def add_header(self, header, value): + """add an output HTTP header""" + self.headers_out[header] = value + + def remove_header(self, header): + """remove an output HTTP header""" + self.headers_out.pop(header, None) + + def header_if_modified_since(self): + """If the HTTP header If-modified-since is set, return the equivalent + mx date time value (GMT), else return None + """ + return None + + ## wsgi request helpers ################################################### + + def application_uri(self): + """Return the application's base URI (no PATH_INFO or QUERY_STRING) + + see python2.5's wsgiref.util.application_uri code + """ + environ = self.environ + url = environ['wsgi.url_scheme'] + '://' + if environ.get('HTTP_HOST'): + url += environ['HTTP_HOST'] + else: + url += environ['SERVER_NAME'] + if environ['wsgi.url_scheme'] == 'https': + if environ['SERVER_PORT'] != '443': + url += ':' + environ['SERVER_PORT'] + else: + if environ['SERVER_PORT'] != '80': + url += ':' + environ['SERVER_PORT'] + url += quote(environ.get('SCRIPT_NAME') or '/') + return url + + def get_full_path(self): + return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '') + + def is_secure(self): + return 'wsgi.url_scheme' in self.environ \ + and self.environ['wsgi.url_scheme'] == 'https' + + def get_posted_data(self): + files = None + if self.method == 'POST': + if self.environ.get('CONTENT_TYPE', '').startswith('multipart'): + header_dict = dict((normalize_header(k[5:]), v) + for k, v in self.environ.items() + if k.startswith('HTTP_')) + header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '') + post, files = parse_file_upload(header_dict, self.raw_post_data) + else: + post = qs2dict(self.raw_post_data) + else: + # The WSGI spec says 'QUERY_STRING' may be absent. + post = qs2dict(self.environ.get('QUERY_STRING', '')) + return post, files + + @property + @cached + def raw_post_data(self): + buf = StringIO() + try: + # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd) + content_length = int(self.environ.get('CONTENT_LENGTH', 0)) + except ValueError: # if CONTENT_LENGTH was empty string or not an integer + content_length = 0 + if content_length > 0: + safe_copyfileobj(self.environ['wsgi.input'], buf, + size=content_length) + postdata = buf.getvalue() + buf.close() + return postdata + + def _validate_cache(self): + """raise a `DirectResponse` exception if a cached page along the way + exists and is still usable + """ + # XXX +# if self.get_header('Cache-Control') in ('max-age=0', 'no-cache'): +# # Expires header seems to be required by IE7 +# self.add_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT') +# return +# try: +# http.checkPreconditions(self._twreq, _PreResponse(self)) +# except http.HTTPError, ex: +# self.info('valid http cache, no actual rendering') +# raise DirectResponse(ex.response) + # Expires header seems to be required by IE7 + self.add_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')