diff options
Diffstat (limited to 'webapp/django/http/__init__.py')
-rw-r--r-- | webapp/django/http/__init__.py | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/webapp/django/http/__init__.py b/webapp/django/http/__init__.py new file mode 100644 index 0000000000..6d24e0a23a --- /dev/null +++ b/webapp/django/http/__init__.py @@ -0,0 +1,455 @@ +import os +import re +from Cookie import SimpleCookie, CookieError +from pprint import pformat +from urllib import urlencode +from urlparse import urljoin +try: + # The mod_python version is more efficient, so try importing it first. + from mod_python.util import parse_qsl +except ImportError: + from cgi import parse_qsl + +from django.utils.datastructures import MultiValueDict, ImmutableList +from django.utils.encoding import smart_str, iri_to_uri, force_unicode +from django.http.multipartparser import MultiPartParser +from django.conf import settings +from django.core.files import uploadhandler +from utils import * + +RESERVED_CHARS="!*'();:@&=+$,/?%#[]" + +absolute_http_url_re = re.compile(r"^https?://", re.I) + +class Http404(Exception): + pass + +class HttpRequest(object): + """A basic HTTP request.""" + + # The encoding used in GET/POST dicts. None means use default setting. + _encoding = None + _upload_handlers = [] + + def __init__(self): + self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {} + self.path = '' + self.path_info = '' + self.method = None + + def __repr__(self): + return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \ + (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES), + pformat(self.META)) + + def get_host(self): + """Returns the HTTP host using the environment or request headers.""" + # We try three options, in order of decreasing preference. + if 'HTTP_X_FORWARDED_HOST' in self.META: + host = self.META['HTTP_X_FORWARDED_HOST'] + elif 'HTTP_HOST' in self.META: + host = self.META['HTTP_HOST'] + else: + # Reconstruct the host using the algorithm from PEP 333. + host = self.META['SERVER_NAME'] + server_port = self.META['SERVER_PORT'] + if server_port != (self.is_secure() and 443 or 80): + host = '%s:%s' % (host, server_port) + return host + + def get_full_path(self): + return '' + + def build_absolute_uri(self, location=None): + """ + Builds an absolute URI from the location and the variables available in + this request. If no location is specified, the absolute URI is built on + ``request.get_full_path()``. + """ + if not location: + location = self.get_full_path() + if not absolute_http_url_re.match(location): + current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http', + self.get_host(), self.path) + location = urljoin(current_uri, location) + return location + + def is_secure(self): + return os.environ.get("HTTPS") == "on" + + def is_ajax(self): + return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest' + + def _set_encoding(self, val): + """ + Sets the encoding used for GET/POST accesses. If the GET or POST + dictionary has already been created, it is removed and recreated on the + next access (so that it is decoded correctly). + """ + self._encoding = val + if hasattr(self, '_get'): + del self._get + if hasattr(self, '_post'): + del self._post + + def _get_encoding(self): + return self._encoding + + encoding = property(_get_encoding, _set_encoding) + + def _initialize_handlers(self): + self._upload_handlers = [uploadhandler.load_handler(handler, self) + for handler in settings.FILE_UPLOAD_HANDLERS] + + def _set_upload_handlers(self, upload_handlers): + if hasattr(self, '_files'): + raise AttributeError("You cannot set the upload handlers after the upload has been processed.") + self._upload_handlers = upload_handlers + + def _get_upload_handlers(self): + if not self._upload_handlers: + # If thre are no upload handlers defined, initialize them from settings. + self._initialize_handlers() + return self._upload_handlers + + upload_handlers = property(_get_upload_handlers, _set_upload_handlers) + + def parse_file_upload(self, META, post_data): + """Returns a tuple of (POST QueryDict, FILES MultiValueDict).""" + self.upload_handlers = ImmutableList( + self.upload_handlers, + warning = "You cannot alter upload handlers after the upload has been processed." + ) + parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) + return parser.parse() + +class QueryDict(MultiValueDict): + """ + A specialized MultiValueDict that takes a query string when initialized. + This is immutable unless you create a copy of it. + + Values retrieved from this class are converted from the given encoding + (DEFAULT_CHARSET by default) to unicode. + """ + # These are both reset in __init__, but is specified here at the class + # level so that unpickling will have valid values + _mutable = True + _encoding = None + + def __init__(self, query_string, mutable=False, encoding=None): + MultiValueDict.__init__(self) + if not encoding: + # *Important*: do not import settings any earlier because of note + # in core.handlers.modpython. + from django.conf import settings + encoding = settings.DEFAULT_CHARSET + self.encoding = encoding + for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True + self.appendlist(force_unicode(key, encoding, errors='replace'), + force_unicode(value, encoding, errors='replace')) + self._mutable = mutable + + def _get_encoding(self): + if self._encoding is None: + # *Important*: do not import settings at the module level because + # of the note in core.handlers.modpython. + from django.conf import settings + self._encoding = settings.DEFAULT_CHARSET + return self._encoding + + def _set_encoding(self, value): + self._encoding = value + + encoding = property(_get_encoding, _set_encoding) + + def _assert_mutable(self): + if not self._mutable: + raise AttributeError("This QueryDict instance is immutable") + + def __setitem__(self, key, value): + self._assert_mutable() + key = str_to_unicode(key, self.encoding) + value = str_to_unicode(value, self.encoding) + MultiValueDict.__setitem__(self, key, value) + + def __delitem__(self, key): + self._assert_mutable() + super(QueryDict, self).__delitem__(key) + + def __copy__(self): + result = self.__class__('', mutable=True) + for key, value in dict.items(self): + dict.__setitem__(result, key, value) + return result + + def __deepcopy__(self, memo): + import copy + result = self.__class__('', mutable=True) + memo[id(self)] = result + for key, value in dict.items(self): + dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo)) + return result + + def setlist(self, key, list_): + self._assert_mutable() + key = str_to_unicode(key, self.encoding) + list_ = [str_to_unicode(elt, self.encoding) for elt in list_] + MultiValueDict.setlist(self, key, list_) + + def setlistdefault(self, key, default_list=()): + self._assert_mutable() + if key not in self: + self.setlist(key, default_list) + return MultiValueDict.getlist(self, key) + + def appendlist(self, key, value): + self._assert_mutable() + key = str_to_unicode(key, self.encoding) + value = str_to_unicode(value, self.encoding) + MultiValueDict.appendlist(self, key, value) + + def update(self, other_dict): + self._assert_mutable() + f = lambda s: str_to_unicode(s, self.encoding) + d = dict([(f(k), f(v)) for k, v in other_dict.items()]) + MultiValueDict.update(self, d) + + def pop(self, key, *args): + self._assert_mutable() + return MultiValueDict.pop(self, key, *args) + + def popitem(self): + self._assert_mutable() + return MultiValueDict.popitem(self) + + def clear(self): + self._assert_mutable() + MultiValueDict.clear(self) + + def setdefault(self, key, default=None): + self._assert_mutable() + key = str_to_unicode(key, self.encoding) + default = str_to_unicode(default, self.encoding) + return MultiValueDict.setdefault(self, key, default) + + def copy(self): + """Returns a mutable copy of this object.""" + return self.__deepcopy__({}) + + def urlencode(self): + output = [] + for k, list_ in self.lists(): + k = smart_str(k, self.encoding) + output.extend([urlencode({k: smart_str(v, self.encoding)}) for v in list_]) + return '&'.join(output) + +def parse_cookie(cookie): + if cookie == '': + return {} + try: + c = SimpleCookie() + c.load(cookie) + except CookieError: + # Invalid cookie + return {} + + cookiedict = {} + for key in c.keys(): + cookiedict[key] = c.get(key).value + return cookiedict + +class HttpResponse(object): + """A basic HTTP response, with content and dictionary-accessed headers.""" + + status_code = 200 + + def __init__(self, content='', mimetype=None, status=None, + content_type=None): + from django.conf import settings + self._charset = settings.DEFAULT_CHARSET + if mimetype: + content_type = mimetype # For backwards compatibility + if not content_type: + content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, + settings.DEFAULT_CHARSET) + if not isinstance(content, basestring) and hasattr(content, '__iter__'): + self._container = content + self._is_string = False + else: + self._container = [content] + self._is_string = True + self.cookies = SimpleCookie() + if status: + self.status_code = status + + # _headers is a mapping of the lower-case name to the original case of + # the header (required for working with legacy systems) and the header + # value. + self._headers = {'content-type': ('Content-Type', content_type)} + + def __str__(self): + """Full HTTP message, including headers.""" + return '\n'.join(['%s: %s' % (key, value) + for key, value in self._headers.values()]) \ + + '\n\n' + self.content + + def _convert_to_ascii(self, *values): + """Converts all values to ascii strings.""" + for value in values: + if isinstance(value, unicode): + try: + yield value.encode('us-ascii') + except UnicodeError, e: + e.reason += ', HTTP response headers must be in US-ASCII format' + raise + else: + yield str(value) + + def __setitem__(self, header, value): + header, value = self._convert_to_ascii(header, value) + self._headers[header.lower()] = (header, value) + + def __delitem__(self, header): + try: + del self._headers[header.lower()] + except KeyError: + pass + + def __getitem__(self, header): + return self._headers[header.lower()][1] + + def has_header(self, header): + """Case-insensitive check for a header.""" + return self._headers.has_key(header.lower()) + + __contains__ = has_header + + def items(self): + return self._headers.values() + + def get(self, header, alternate): + return self._headers.get(header.lower(), (None, alternate))[1] + + def set_cookie(self, key, value='', max_age=None, expires=None, path='/', + domain=None, secure=False): + self.cookies[key] = value + if max_age is not None: + self.cookies[key]['max-age'] = max_age + if expires is not None: + self.cookies[key]['expires'] = expires + if path is not None: + self.cookies[key]['path'] = path + if domain is not None: + self.cookies[key]['domain'] = domain + if secure: + self.cookies[key]['secure'] = True + + def delete_cookie(self, key, path='/', domain=None): + self.set_cookie(key, max_age=0, path=path, domain=domain, + expires='Thu, 01-Jan-1970 00:00:00 GMT') + + def _get_content(self): + if self.has_header('Content-Encoding'): + return ''.join(self._container) + return smart_str(''.join(self._container), self._charset) + + def _set_content(self, value): + self._container = [value] + self._is_string = True + + content = property(_get_content, _set_content) + + def __iter__(self): + self._iterator = iter(self._container) + return self + + def next(self): + chunk = self._iterator.next() + if isinstance(chunk, unicode): + chunk = chunk.encode(self._charset) + return str(chunk) + + def close(self): + if hasattr(self._container, 'close'): + self._container.close() + + # The remaining methods partially implement the file-like object interface. + # See http://docs.python.org/lib/bltin-file-objects.html + def write(self, content): + if not self._is_string: + raise Exception("This %s instance is not writable" % self.__class__) + self._container.append(content) + + def flush(self): + pass + + def tell(self): + if not self._is_string: + raise Exception("This %s instance cannot tell its position" % self.__class__) + return sum([len(chunk) for chunk in self._container]) + +class HttpResponseRedirect(HttpResponse): + status_code = 302 + + def __init__(self, redirect_to): + HttpResponse.__init__(self) + self['Location'] = iri_to_uri(redirect_to) + +class HttpResponsePermanentRedirect(HttpResponse): + status_code = 301 + + def __init__(self, redirect_to): + HttpResponse.__init__(self) + self['Location'] = iri_to_uri(redirect_to) + +class HttpResponseNotModified(HttpResponse): + status_code = 304 + +class HttpResponseBadRequest(HttpResponse): + status_code = 400 + +class HttpResponseNotFound(HttpResponse): + status_code = 404 + +class HttpResponseForbidden(HttpResponse): + status_code = 403 + +class HttpResponseNotAllowed(HttpResponse): + status_code = 405 + + def __init__(self, permitted_methods): + HttpResponse.__init__(self) + self['Allow'] = ', '.join(permitted_methods) + +class HttpResponseGone(HttpResponse): + status_code = 410 + + def __init__(self, *args, **kwargs): + HttpResponse.__init__(self, *args, **kwargs) + +class HttpResponseServerError(HttpResponse): + status_code = 500 + + def __init__(self, *args, **kwargs): + HttpResponse.__init__(self, *args, **kwargs) + +# A backwards compatible alias for HttpRequest.get_host. +def get_host(request): + return request.get_host() + +# It's neither necessary nor appropriate to use +# django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus, +# this slightly more restricted function. +def str_to_unicode(s, encoding): + """ + Converts basestring objects to unicode, using the given encoding. Illegally + encoded input characters are replaced with Unicode "unknown" codepoint + (\ufffd). + + Returns any non-basestring objects without change. + """ + if isinstance(s, str): + return unicode(s, encoding, 'replace') + else: + return s + |