Django

Code

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

Revision 7334, 15.2 kB (checked in by mtredinnick, 2 months ago)

Fixed #6616 -- Added an is_ajax() method to HttpRequest that uses the de facto
standard header for detecting an XmlHttpRequest? call. Thanks, Daniel Lindsley.

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