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