Django

Code

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

Revision 8705, 15.4 kB (checked in by jacob, 4 months ago)

Fixed #8278: fixed QueryDict.update(QueryDict). Thanks, julien.

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