Django

Code

root/django/trunk/django/http/__init__.py

Revision 10712, 15.6 kB (checked in by jacob, 2 months ago)

Fixed a silly function flow bug in [10711].

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
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
Note: See TracBrowser for help on using the browser.