web/lib/django/http/__init__.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 import os
       
     2 import re
       
     3 from Cookie import BaseCookie, SimpleCookie, CookieError
       
     4 from pprint import pformat
       
     5 from urllib import urlencode
       
     6 from urlparse import urljoin
       
     7 try:
       
     8     # The mod_python version is more efficient, so try importing it first.
       
     9     from mod_python.util import parse_qsl
       
    10 except ImportError:
       
    11     from cgi import parse_qsl
       
    12 
       
    13 from django.utils.datastructures import MultiValueDict, ImmutableList
       
    14 from django.utils.encoding import smart_str, iri_to_uri, force_unicode
       
    15 from django.http.multipartparser import MultiPartParser
       
    16 from django.conf import settings
       
    17 from django.core.files import uploadhandler
       
    18 from utils import *
       
    19 
       
    20 RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
       
    21 
       
    22 absolute_http_url_re = re.compile(r"^https?://", re.I)
       
    23 
       
    24 class Http404(Exception):
       
    25     pass
       
    26 
       
    27 class HttpRequest(object):
       
    28     """A basic HTTP request."""
       
    29 
       
    30     # The encoding used in GET/POST dicts. None means use default setting.
       
    31     _encoding = None
       
    32     _upload_handlers = []
       
    33 
       
    34     def __init__(self):
       
    35         self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
       
    36         self.path = ''
       
    37         self.path_info = ''
       
    38         self.method = None
       
    39 
       
    40     def __repr__(self):
       
    41         return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
       
    42             (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
       
    43             pformat(self.META))
       
    44 
       
    45     def get_host(self):
       
    46         """Returns the HTTP host using the environment or request headers."""
       
    47         # We try three options, in order of decreasing preference.
       
    48         if 'HTTP_X_FORWARDED_HOST' in self.META:
       
    49             host = self.META['HTTP_X_FORWARDED_HOST']
       
    50         elif 'HTTP_HOST' in self.META:
       
    51             host = self.META['HTTP_HOST']
       
    52         else:
       
    53             # Reconstruct the host using the algorithm from PEP 333.
       
    54             host = self.META['SERVER_NAME']
       
    55             server_port = str(self.META['SERVER_PORT'])
       
    56             if server_port != (self.is_secure() and '443' or '80'):
       
    57                 host = '%s:%s' % (host, server_port)
       
    58         return host
       
    59 
       
    60     def get_full_path(self):
       
    61         return ''
       
    62 
       
    63     def build_absolute_uri(self, location=None):
       
    64         """
       
    65         Builds an absolute URI from the location and the variables available in
       
    66         this request. If no location is specified, the absolute URI is built on
       
    67         ``request.get_full_path()``.
       
    68         """
       
    69         if not location:
       
    70             location = self.get_full_path()
       
    71         if not absolute_http_url_re.match(location):
       
    72             current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http',
       
    73                                          self.get_host(), self.path)
       
    74             location = urljoin(current_uri, location)
       
    75         return iri_to_uri(location)
       
    76 
       
    77     def is_secure(self):
       
    78         return os.environ.get("HTTPS") == "on"
       
    79 
       
    80     def is_ajax(self):
       
    81         return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
       
    82 
       
    83     def _set_encoding(self, val):
       
    84         """
       
    85         Sets the encoding used for GET/POST accesses. If the GET or POST
       
    86         dictionary has already been created, it is removed and recreated on the
       
    87         next access (so that it is decoded correctly).
       
    88         """
       
    89         self._encoding = val
       
    90         if hasattr(self, '_get'):
       
    91             del self._get
       
    92         if hasattr(self, '_post'):
       
    93             del self._post
       
    94 
       
    95     def _get_encoding(self):
       
    96         return self._encoding
       
    97 
       
    98     encoding = property(_get_encoding, _set_encoding)
       
    99 
       
   100     def _initialize_handlers(self):
       
   101         self._upload_handlers = [uploadhandler.load_handler(handler, self)
       
   102                                  for handler in settings.FILE_UPLOAD_HANDLERS]
       
   103 
       
   104     def _set_upload_handlers(self, upload_handlers):
       
   105         if hasattr(self, '_files'):
       
   106             raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
       
   107         self._upload_handlers = upload_handlers
       
   108 
       
   109     def _get_upload_handlers(self):
       
   110         if not self._upload_handlers:
       
   111             # If thre are no upload handlers defined, initialize them from settings.
       
   112             self._initialize_handlers()
       
   113         return self._upload_handlers
       
   114 
       
   115     upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
       
   116 
       
   117     def parse_file_upload(self, META, post_data):
       
   118         """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
       
   119         self.upload_handlers = ImmutableList(
       
   120             self.upload_handlers,
       
   121             warning = "You cannot alter upload handlers after the upload has been processed."
       
   122         )
       
   123         parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
       
   124         return parser.parse()
       
   125 
       
   126 class QueryDict(MultiValueDict):
       
   127     """
       
   128     A specialized MultiValueDict that takes a query string when initialized.
       
   129     This is immutable unless you create a copy of it.
       
   130 
       
   131     Values retrieved from this class are converted from the given encoding
       
   132     (DEFAULT_CHARSET by default) to unicode.
       
   133     """
       
   134     # These are both reset in __init__, but is specified here at the class
       
   135     # level so that unpickling will have valid values
       
   136     _mutable = True
       
   137     _encoding = None
       
   138 
       
   139     def __init__(self, query_string, mutable=False, encoding=None):
       
   140         MultiValueDict.__init__(self)
       
   141         if not encoding:
       
   142             # *Important*: do not import settings any earlier because of note
       
   143             # in core.handlers.modpython.
       
   144             from django.conf import settings
       
   145             encoding = settings.DEFAULT_CHARSET
       
   146         self.encoding = encoding
       
   147         for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
       
   148             self.appendlist(force_unicode(key, encoding, errors='replace'),
       
   149                             force_unicode(value, encoding, errors='replace'))
       
   150         self._mutable = mutable
       
   151 
       
   152     def _get_encoding(self):
       
   153         if self._encoding is None:
       
   154             # *Important*: do not import settings at the module level because
       
   155             # of the note in core.handlers.modpython.
       
   156             from django.conf import settings
       
   157             self._encoding = settings.DEFAULT_CHARSET
       
   158         return self._encoding
       
   159 
       
   160     def _set_encoding(self, value):
       
   161         self._encoding = value
       
   162 
       
   163     encoding = property(_get_encoding, _set_encoding)
       
   164 
       
   165     def _assert_mutable(self):
       
   166         if not self._mutable:
       
   167             raise AttributeError("This QueryDict instance is immutable")
       
   168 
       
   169     def __setitem__(self, key, value):
       
   170         self._assert_mutable()
       
   171         key = str_to_unicode(key, self.encoding)
       
   172         value = str_to_unicode(value, self.encoding)
       
   173         MultiValueDict.__setitem__(self, key, value)
       
   174 
       
   175     def __delitem__(self, key):
       
   176         self._assert_mutable()
       
   177         super(QueryDict, self).__delitem__(key)
       
   178 
       
   179     def __copy__(self):
       
   180         result = self.__class__('', mutable=True)
       
   181         for key, value in dict.items(self):
       
   182             dict.__setitem__(result, key, value)
       
   183         return result
       
   184 
       
   185     def __deepcopy__(self, memo):
       
   186         import django.utils.copycompat as copy
       
   187         result = self.__class__('', mutable=True)
       
   188         memo[id(self)] = result
       
   189         for key, value in dict.items(self):
       
   190             dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo))
       
   191         return result
       
   192     
       
   193     def setlist(self, key, list_):
       
   194         self._assert_mutable()
       
   195         key = str_to_unicode(key, self.encoding)
       
   196         list_ = [str_to_unicode(elt, self.encoding) for elt in list_]
       
   197         MultiValueDict.setlist(self, key, list_)
       
   198 
       
   199     def setlistdefault(self, key, default_list=()):
       
   200         self._assert_mutable()
       
   201         if key not in self:
       
   202             self.setlist(key, default_list)
       
   203         return MultiValueDict.getlist(self, key)
       
   204 
       
   205     def appendlist(self, key, value):
       
   206         self._assert_mutable()
       
   207         key = str_to_unicode(key, self.encoding)
       
   208         value = str_to_unicode(value, self.encoding)
       
   209         MultiValueDict.appendlist(self, key, value)
       
   210 
       
   211     def update(self, other_dict):
       
   212         self._assert_mutable()
       
   213         f = lambda s: str_to_unicode(s, self.encoding)
       
   214         if hasattr(other_dict, 'lists'):
       
   215             for key, valuelist in other_dict.lists():
       
   216                 for value in valuelist:
       
   217                     MultiValueDict.update(self, {f(key): f(value)})
       
   218         else:
       
   219             d = dict([(f(k), f(v)) for k, v in other_dict.items()])
       
   220             MultiValueDict.update(self, d)
       
   221 
       
   222     def pop(self, key, *args):
       
   223         self._assert_mutable()
       
   224         return MultiValueDict.pop(self, key, *args)
       
   225 
       
   226     def popitem(self):
       
   227         self._assert_mutable()
       
   228         return MultiValueDict.popitem(self)
       
   229 
       
   230     def clear(self):
       
   231         self._assert_mutable()
       
   232         MultiValueDict.clear(self)
       
   233 
       
   234     def setdefault(self, key, default=None):
       
   235         self._assert_mutable()
       
   236         key = str_to_unicode(key, self.encoding)
       
   237         default = str_to_unicode(default, self.encoding)
       
   238         return MultiValueDict.setdefault(self, key, default)
       
   239 
       
   240     def copy(self):
       
   241         """Returns a mutable copy of this object."""
       
   242         return self.__deepcopy__({})
       
   243 
       
   244     def urlencode(self):
       
   245         output = []
       
   246         for k, list_ in self.lists():
       
   247             k = smart_str(k, self.encoding)
       
   248             output.extend([urlencode({k: smart_str(v, self.encoding)}) for v in list_])
       
   249         return '&'.join(output)
       
   250 
       
   251 class CompatCookie(SimpleCookie):
       
   252     """
       
   253     Cookie class that handles some issues with browser compatibility.
       
   254     """
       
   255     def value_encode(self, val):
       
   256         # Some browsers do not support quoted-string from RFC 2109,
       
   257         # including some versions of Safari and Internet Explorer.
       
   258         # These browsers split on ';', and some versions of Safari
       
   259         # are known to split on ', '. Therefore, we encode ';' and ','
       
   260 
       
   261         # SimpleCookie already does the hard work of encoding and decoding.
       
   262         # It uses octal sequences like '\\012' for newline etc.
       
   263         # and non-ASCII chars.  We just make use of this mechanism, to
       
   264         # avoid introducing two encoding schemes which would be confusing
       
   265         # and especially awkward for javascript.
       
   266 
       
   267         # NB, contrary to Python docs, value_encode returns a tuple containing
       
   268         # (real val, encoded_val)
       
   269         val, encoded = super(CompatCookie, self).value_encode(val)
       
   270 
       
   271         encoded = encoded.replace(";", "\\073").replace(",","\\054")
       
   272         # If encoded now contains any quoted chars, we need double quotes
       
   273         # around the whole string.
       
   274         if "\\" in encoded and not encoded.startswith('"'):
       
   275             encoded = '"' + encoded + '"'
       
   276 
       
   277         return val, encoded
       
   278 
       
   279 def parse_cookie(cookie):
       
   280     if cookie == '':
       
   281         return {}
       
   282     if not isinstance(cookie, BaseCookie):
       
   283         try:
       
   284             c = CompatCookie()
       
   285             c.load(cookie)
       
   286         except CookieError:
       
   287             # Invalid cookie
       
   288             return {}
       
   289     else:
       
   290         c = cookie
       
   291     cookiedict = {}
       
   292     for key in c.keys():
       
   293         cookiedict[key] = c.get(key).value
       
   294     return cookiedict
       
   295 
       
   296 class BadHeaderError(ValueError):
       
   297     pass
       
   298 
       
   299 class HttpResponse(object):
       
   300     """A basic HTTP response, with content and dictionary-accessed headers."""
       
   301 
       
   302     status_code = 200
       
   303 
       
   304     def __init__(self, content='', mimetype=None, status=None,
       
   305             content_type=None):
       
   306         from django.conf import settings
       
   307         self._charset = settings.DEFAULT_CHARSET
       
   308         if mimetype:
       
   309             content_type = mimetype     # For backwards compatibility
       
   310         if not content_type:
       
   311             content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
       
   312                     settings.DEFAULT_CHARSET)
       
   313         if not isinstance(content, basestring) and hasattr(content, '__iter__'):
       
   314             self._container = content
       
   315             self._is_string = False
       
   316         else:
       
   317             self._container = [content]
       
   318             self._is_string = True
       
   319         self.cookies = CompatCookie()
       
   320         if status:
       
   321             self.status_code = status
       
   322 
       
   323         # _headers is a mapping of the lower-case name to the original case of
       
   324         # the header (required for working with legacy systems) and the header
       
   325         # value.
       
   326         self._headers = {'content-type': ('Content-Type', content_type)}
       
   327 
       
   328     def __str__(self):
       
   329         """Full HTTP message, including headers."""
       
   330         return '\n'.join(['%s: %s' % (key, value)
       
   331             for key, value in self._headers.values()]) \
       
   332             + '\n\n' + self.content
       
   333 
       
   334     def _convert_to_ascii(self, *values):
       
   335         """Converts all values to ascii strings."""
       
   336         for value in values:
       
   337             if isinstance(value, unicode):
       
   338                 try:
       
   339                     value = value.encode('us-ascii')
       
   340                 except UnicodeError, e:
       
   341                     e.reason += ', HTTP response headers must be in US-ASCII format'
       
   342                     raise
       
   343             else:
       
   344                 value = str(value)
       
   345             if '\n' in value or '\r' in value:
       
   346                 raise BadHeaderError("Header values can't contain newlines (got %r)" % (value))
       
   347             yield value
       
   348 
       
   349     def __setitem__(self, header, value):
       
   350         header, value = self._convert_to_ascii(header, value)
       
   351         self._headers[header.lower()] = (header, value)
       
   352 
       
   353     def __delitem__(self, header):
       
   354         try:
       
   355             del self._headers[header.lower()]
       
   356         except KeyError:
       
   357             pass
       
   358 
       
   359     def __getitem__(self, header):
       
   360         return self._headers[header.lower()][1]
       
   361 
       
   362     def has_header(self, header):
       
   363         """Case-insensitive check for a header."""
       
   364         return self._headers.has_key(header.lower())
       
   365 
       
   366     __contains__ = has_header
       
   367 
       
   368     def items(self):
       
   369         return self._headers.values()
       
   370 
       
   371     def get(self, header, alternate):
       
   372         return self._headers.get(header.lower(), (None, alternate))[1]
       
   373 
       
   374     def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
       
   375                    domain=None, secure=False):
       
   376         self.cookies[key] = value
       
   377         if max_age is not None:
       
   378             self.cookies[key]['max-age'] = max_age
       
   379         if expires is not None:
       
   380             self.cookies[key]['expires'] = expires
       
   381         if path is not None:
       
   382             self.cookies[key]['path'] = path
       
   383         if domain is not None:
       
   384             self.cookies[key]['domain'] = domain
       
   385         if secure:
       
   386             self.cookies[key]['secure'] = True
       
   387 
       
   388     def delete_cookie(self, key, path='/', domain=None):
       
   389         self.set_cookie(key, max_age=0, path=path, domain=domain,
       
   390                         expires='Thu, 01-Jan-1970 00:00:00 GMT')
       
   391 
       
   392     def _get_content(self):
       
   393         if self.has_header('Content-Encoding'):
       
   394             return ''.join(self._container)
       
   395         return smart_str(''.join(self._container), self._charset)
       
   396 
       
   397     def _set_content(self, value):
       
   398         self._container = [value]
       
   399         self._is_string = True
       
   400 
       
   401     content = property(_get_content, _set_content)
       
   402 
       
   403     def __iter__(self):
       
   404         self._iterator = iter(self._container)
       
   405         return self
       
   406 
       
   407     def next(self):
       
   408         chunk = self._iterator.next()
       
   409         if isinstance(chunk, unicode):
       
   410             chunk = chunk.encode(self._charset)
       
   411         return str(chunk)
       
   412 
       
   413     def close(self):
       
   414         if hasattr(self._container, 'close'):
       
   415             self._container.close()
       
   416 
       
   417     # The remaining methods partially implement the file-like object interface.
       
   418     # See http://docs.python.org/lib/bltin-file-objects.html
       
   419     def write(self, content):
       
   420         if not self._is_string:
       
   421             raise Exception("This %s instance is not writable" % self.__class__)
       
   422         self._container.append(content)
       
   423 
       
   424     def flush(self):
       
   425         pass
       
   426 
       
   427     def tell(self):
       
   428         if not self._is_string:
       
   429             raise Exception("This %s instance cannot tell its position" % self.__class__)
       
   430         return sum([len(chunk) for chunk in self._container])
       
   431 
       
   432 class HttpResponseRedirect(HttpResponse):
       
   433     status_code = 302
       
   434 
       
   435     def __init__(self, redirect_to):
       
   436         HttpResponse.__init__(self)
       
   437         self['Location'] = iri_to_uri(redirect_to)
       
   438 
       
   439 class HttpResponsePermanentRedirect(HttpResponse):
       
   440     status_code = 301
       
   441 
       
   442     def __init__(self, redirect_to):
       
   443         HttpResponse.__init__(self)
       
   444         self['Location'] = iri_to_uri(redirect_to)
       
   445 
       
   446 class HttpResponseNotModified(HttpResponse):
       
   447     status_code = 304
       
   448 
       
   449 class HttpResponseBadRequest(HttpResponse):
       
   450     status_code = 400
       
   451 
       
   452 class HttpResponseNotFound(HttpResponse):
       
   453     status_code = 404
       
   454 
       
   455 class HttpResponseForbidden(HttpResponse):
       
   456     status_code = 403
       
   457 
       
   458 class HttpResponseNotAllowed(HttpResponse):
       
   459     status_code = 405
       
   460 
       
   461     def __init__(self, permitted_methods):
       
   462         HttpResponse.__init__(self)
       
   463         self['Allow'] = ', '.join(permitted_methods)
       
   464 
       
   465 class HttpResponseGone(HttpResponse):
       
   466     status_code = 410
       
   467 
       
   468     def __init__(self, *args, **kwargs):
       
   469         HttpResponse.__init__(self, *args, **kwargs)
       
   470 
       
   471 class HttpResponseServerError(HttpResponse):
       
   472     status_code = 500
       
   473 
       
   474     def __init__(self, *args, **kwargs):
       
   475         HttpResponse.__init__(self, *args, **kwargs)
       
   476 
       
   477 # A backwards compatible alias for HttpRequest.get_host.
       
   478 def get_host(request):
       
   479     return request.get_host()
       
   480 
       
   481 # It's neither necessary nor appropriate to use
       
   482 # django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus,
       
   483 # this slightly more restricted function.
       
   484 def str_to_unicode(s, encoding):
       
   485     """
       
   486     Converts basestring objects to unicode, using the given encoding. Illegally
       
   487     encoded input characters are replaced with Unicode "unknown" codepoint
       
   488     (\ufffd).
       
   489 
       
   490     Returns any non-basestring objects without change.
       
   491     """
       
   492     if isinstance(s, str):
       
   493         return unicode(s, encoding, 'replace')
       
   494     else:
       
   495         return s
       
   496