web/lib/django/http/__init__.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 import os
       
     2 import re
       
     3 from Cookie import 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 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 def parse_cookie(cookie):
       
   252     if cookie == '':
       
   253         return {}
       
   254     try:
       
   255         c = SimpleCookie()
       
   256         c.load(cookie)
       
   257     except CookieError:
       
   258         # Invalid cookie
       
   259         return {}
       
   260 
       
   261     cookiedict = {}
       
   262     for key in c.keys():
       
   263         cookiedict[key] = c.get(key).value
       
   264     return cookiedict
       
   265 
       
   266 class BadHeaderError(ValueError):
       
   267     pass
       
   268 
       
   269 class HttpResponse(object):
       
   270     """A basic HTTP response, with content and dictionary-accessed headers."""
       
   271 
       
   272     status_code = 200
       
   273 
       
   274     def __init__(self, content='', mimetype=None, status=None,
       
   275             content_type=None):
       
   276         from django.conf import settings
       
   277         self._charset = settings.DEFAULT_CHARSET
       
   278         if mimetype:
       
   279             content_type = mimetype     # For backwards compatibility
       
   280         if not content_type:
       
   281             content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
       
   282                     settings.DEFAULT_CHARSET)
       
   283         if not isinstance(content, basestring) and hasattr(content, '__iter__'):
       
   284             self._container = content
       
   285             self._is_string = False
       
   286         else:
       
   287             self._container = [content]
       
   288             self._is_string = True
       
   289         self.cookies = SimpleCookie()
       
   290         if status:
       
   291             self.status_code = status
       
   292 
       
   293         # _headers is a mapping of the lower-case name to the original case of
       
   294         # the header (required for working with legacy systems) and the header
       
   295         # value.
       
   296         self._headers = {'content-type': ('Content-Type', content_type)}
       
   297 
       
   298     def __str__(self):
       
   299         """Full HTTP message, including headers."""
       
   300         return '\n'.join(['%s: %s' % (key, value)
       
   301             for key, value in self._headers.values()]) \
       
   302             + '\n\n' + self.content
       
   303 
       
   304     def _convert_to_ascii(self, *values):
       
   305         """Converts all values to ascii strings."""
       
   306         for value in values:
       
   307             if isinstance(value, unicode):
       
   308                 try:
       
   309                     value = value.encode('us-ascii')
       
   310                 except UnicodeError, e:
       
   311                     e.reason += ', HTTP response headers must be in US-ASCII format'
       
   312                     raise
       
   313             else:
       
   314                 value = str(value)
       
   315             if '\n' in value or '\r' in value:
       
   316                 raise BadHeaderError("Header values can't contain newlines (got %r)" % (value))
       
   317             yield value
       
   318 
       
   319     def __setitem__(self, header, value):
       
   320         header, value = self._convert_to_ascii(header, value)
       
   321         self._headers[header.lower()] = (header, value)
       
   322 
       
   323     def __delitem__(self, header):
       
   324         try:
       
   325             del self._headers[header.lower()]
       
   326         except KeyError:
       
   327             pass
       
   328 
       
   329     def __getitem__(self, header):
       
   330         return self._headers[header.lower()][1]
       
   331 
       
   332     def has_header(self, header):
       
   333         """Case-insensitive check for a header."""
       
   334         return self._headers.has_key(header.lower())
       
   335 
       
   336     __contains__ = has_header
       
   337 
       
   338     def items(self):
       
   339         return self._headers.values()
       
   340 
       
   341     def get(self, header, alternate):
       
   342         return self._headers.get(header.lower(), (None, alternate))[1]
       
   343 
       
   344     def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
       
   345                    domain=None, secure=False):
       
   346         self.cookies[key] = value
       
   347         if max_age is not None:
       
   348             self.cookies[key]['max-age'] = max_age
       
   349         if expires is not None:
       
   350             self.cookies[key]['expires'] = expires
       
   351         if path is not None:
       
   352             self.cookies[key]['path'] = path
       
   353         if domain is not None:
       
   354             self.cookies[key]['domain'] = domain
       
   355         if secure:
       
   356             self.cookies[key]['secure'] = True
       
   357 
       
   358     def delete_cookie(self, key, path='/', domain=None):
       
   359         self.set_cookie(key, max_age=0, path=path, domain=domain,
       
   360                         expires='Thu, 01-Jan-1970 00:00:00 GMT')
       
   361 
       
   362     def _get_content(self):
       
   363         if self.has_header('Content-Encoding'):
       
   364             return ''.join(self._container)
       
   365         return smart_str(''.join(self._container), self._charset)
       
   366 
       
   367     def _set_content(self, value):
       
   368         self._container = [value]
       
   369         self._is_string = True
       
   370 
       
   371     content = property(_get_content, _set_content)
       
   372 
       
   373     def __iter__(self):
       
   374         self._iterator = iter(self._container)
       
   375         return self
       
   376 
       
   377     def next(self):
       
   378         chunk = self._iterator.next()
       
   379         if isinstance(chunk, unicode):
       
   380             chunk = chunk.encode(self._charset)
       
   381         return str(chunk)
       
   382 
       
   383     def close(self):
       
   384         if hasattr(self._container, 'close'):
       
   385             self._container.close()
       
   386 
       
   387     # The remaining methods partially implement the file-like object interface.
       
   388     # See http://docs.python.org/lib/bltin-file-objects.html
       
   389     def write(self, content):
       
   390         if not self._is_string:
       
   391             raise Exception("This %s instance is not writable" % self.__class__)
       
   392         self._container.append(content)
       
   393 
       
   394     def flush(self):
       
   395         pass
       
   396 
       
   397     def tell(self):
       
   398         if not self._is_string:
       
   399             raise Exception("This %s instance cannot tell its position" % self.__class__)
       
   400         return sum([len(chunk) for chunk in self._container])
       
   401 
       
   402 class HttpResponseRedirect(HttpResponse):
       
   403     status_code = 302
       
   404 
       
   405     def __init__(self, redirect_to):
       
   406         HttpResponse.__init__(self)
       
   407         self['Location'] = redirect_to
       
   408 
       
   409 class HttpResponsePermanentRedirect(HttpResponse):
       
   410     status_code = 301
       
   411 
       
   412     def __init__(self, redirect_to):
       
   413         HttpResponse.__init__(self)
       
   414         self['Location'] = redirect_to
       
   415 
       
   416 class HttpResponseNotModified(HttpResponse):
       
   417     status_code = 304
       
   418 
       
   419 class HttpResponseBadRequest(HttpResponse):
       
   420     status_code = 400
       
   421 
       
   422 class HttpResponseNotFound(HttpResponse):
       
   423     status_code = 404
       
   424 
       
   425 class HttpResponseForbidden(HttpResponse):
       
   426     status_code = 403
       
   427 
       
   428 class HttpResponseNotAllowed(HttpResponse):
       
   429     status_code = 405
       
   430 
       
   431     def __init__(self, permitted_methods):
       
   432         HttpResponse.__init__(self)
       
   433         self['Allow'] = ', '.join(permitted_methods)
       
   434 
       
   435 class HttpResponseGone(HttpResponse):
       
   436     status_code = 410
       
   437 
       
   438     def __init__(self, *args, **kwargs):
       
   439         HttpResponse.__init__(self, *args, **kwargs)
       
   440 
       
   441 class HttpResponseServerError(HttpResponse):
       
   442     status_code = 500
       
   443 
       
   444     def __init__(self, *args, **kwargs):
       
   445         HttpResponse.__init__(self, *args, **kwargs)
       
   446 
       
   447 # A backwards compatible alias for HttpRequest.get_host.
       
   448 def get_host(request):
       
   449     return request.get_host()
       
   450 
       
   451 # It's neither necessary nor appropriate to use
       
   452 # django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus,
       
   453 # this slightly more restricted function.
       
   454 def str_to_unicode(s, encoding):
       
   455     """
       
   456     Converts basestring objects to unicode, using the given encoding. Illegally
       
   457     encoded input characters are replaced with Unicode "unknown" codepoint
       
   458     (\ufffd).
       
   459 
       
   460     Returns any non-basestring objects without change.
       
   461     """
       
   462     if isinstance(s, str):
       
   463         return unicode(s, encoding, 'replace')
       
   464     else:
       
   465         return s
       
   466