2
from Cookie import SimpleCookie
3
from Cookie import SimpleCookie, CookieError
3
4
from pprint import pformat
4
from urllib import urlencode, quote
5
from django.utils.datastructures import MultiValueDict
7
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
5
from urllib import urlencode
6
from urlparse import urljoin
10
8
# The mod_python version is more efficient, so try importing it first.
11
9
from mod_python.util import parse_qsl
12
10
except ImportError:
13
11
from cgi import parse_qsl
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
20
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
22
absolute_http_url_re = re.compile(r"^https?://", re.I)
15
24
class Http404(Exception):
18
27
class HttpRequest(object):
19
"A basic HTTP request"
28
"""A basic HTTP request."""
30
# The encoding used in GET/POST dicts. None means use default setting.
20
34
def __init__(self):
21
35
self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
25
40
def __repr__(self):
27
42
(pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
28
43
pformat(self.META))
30
def __getitem__(self, key):
31
for d in (self.POST, self.GET):
34
raise KeyError, "%s not found in either POST or GET" % key
36
def has_key(self, key):
37
return self.GET.has_key(key) or self.POST.has_key(key)
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']
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)
39
60
def get_full_path(self):
63
def build_absolute_uri(self, location=None):
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()``.
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)
42
77
def is_secure(self):
43
78
return os.environ.get("HTTPS") == "on"
45
def parse_file_upload(header_dict, post_data):
46
"Returns a tuple of (POST MultiValueDict, FILES MultiValueDict)"
47
import email, email.Message
48
from cgi import parse_header
49
raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
50
raw_message += '\r\n\r\n' + post_data
51
msg = email.message_from_string(raw_message)
52
POST = MultiValueDict()
53
FILES = MultiValueDict()
54
for submessage in msg.get_payload():
55
if submessage and isinstance(submessage, email.Message.Message):
56
name_dict = parse_header(submessage['Content-Disposition'])[1]
57
# name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
58
# or {'name': 'blah'} for POST fields
59
# We assume all uploaded files have a 'filename' set.
60
if name_dict.has_key('filename'):
61
assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
62
if not name_dict['filename'].strip():
64
# IE submits the full path, so trim everything but the basename.
65
# (We can't use os.path.basename because it expects Linux paths.)
66
filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
67
FILES.appendlist(name_dict['name'], {
69
'content-type': (submessage.has_key('Content-Type') and submessage['Content-Type'] or None),
70
'content': submessage.get_payload(),
73
POST.appendlist(name_dict['name'], submessage.get_payload())
81
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
83
def _set_encoding(self, val):
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).
90
if hasattr(self, '_get'):
92
if hasattr(self, '_post'):
95
def _get_encoding(self):
98
encoding = property(_get_encoding, _set_encoding)
100
def _initialize_handlers(self):
101
self._upload_handlers = [uploadhandler.load_handler(handler, self)
102
for handler in settings.FILE_UPLOAD_HANDLERS]
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
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
115
upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
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."
123
parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
124
return parser.parse()
76
126
class QueryDict(MultiValueDict):
77
"""A specialized MultiValueDict that takes a query string when initialized.
78
This is immutable unless you create a copy of it."""
79
def __init__(self, query_string, mutable=False):
128
A specialized MultiValueDict that takes a query string when initialized.
129
This is immutable unless you create a copy of it.
131
Values retrieved from this class are converted from the given encoding
132
(DEFAULT_CHARSET by default) to unicode.
134
# These are both reset in __init__, but is specified here at the class
135
# level so that unpickling will have valid values
139
def __init__(self, query_string, mutable=False, encoding=None):
80
140
MultiValueDict.__init__(self)
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
82
147
for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
83
self.appendlist(key, value)
148
self.appendlist(force_unicode(key, encoding, errors='replace'),
149
force_unicode(value, encoding, errors='replace'))
84
150
self._mutable = mutable
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
160
def _set_encoding(self, value):
161
self._encoding = value
163
encoding = property(_get_encoding, _set_encoding)
86
165
def _assert_mutable(self):
87
166
if not self._mutable:
88
raise AttributeError, "This QueryDict instance is immutable"
167
raise AttributeError("This QueryDict instance is immutable")
90
169
def __setitem__(self, key, value):
91
170
self._assert_mutable()
171
key = str_to_unicode(key, self.encoding)
172
value = str_to_unicode(value, self.encoding)
92
173
MultiValueDict.__setitem__(self, key, value)
175
def __delitem__(self, key):
176
self._assert_mutable()
177
super(QueryDict, self).__delitem__(key)
94
179
def __copy__(self):
95
180
result = self.__class__('', mutable=True)
96
181
for key, value in dict.items(self):
97
182
dict.__setitem__(result, key, value)
100
def __deepcopy__(self, memo={}):
185
def __deepcopy__(self, memo):
102
187
result = self.__class__('', mutable=True)
103
188
memo[id(self)] = result
108
193
def setlist(self, key, list_):
109
194
self._assert_mutable()
195
key = str_to_unicode(key, self.encoding)
196
list_ = [str_to_unicode(elt, self.encoding) for elt in list_]
110
197
MultiValueDict.setlist(self, key, list_)
199
def setlistdefault(self, key, default_list=()):
200
self._assert_mutable()
202
self.setlist(key, default_list)
203
return MultiValueDict.getlist(self, key)
112
205
def appendlist(self, key, value):
113
206
self._assert_mutable()
207
key = str_to_unicode(key, self.encoding)
208
value = str_to_unicode(value, self.encoding)
114
209
MultiValueDict.appendlist(self, key, value)
116
211
def update(self, other_dict):
117
212
self._assert_mutable()
118
MultiValueDict.update(self, other_dict)
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)})
219
d = dict([(f(k), f(v)) for k, v in other_dict.items()])
220
MultiValueDict.update(self, d)
222
def pop(self, key, *args):
121
223
self._assert_mutable()
122
return MultiValueDict.pop(self, key)
224
return MultiValueDict.pop(self, key, *args)
124
226
def popitem(self):
125
227
self._assert_mutable()
129
231
self._assert_mutable()
130
232
MultiValueDict.clear(self)
132
def setdefault(self, *args):
234
def setdefault(self, key, default=None):
133
235
self._assert_mutable()
134
return MultiValueDict.setdefault(self, *args)
236
key = str_to_unicode(key, self.encoding)
237
default = str_to_unicode(default, self.encoding)
238
return MultiValueDict.setdefault(self, key, default)
137
"Returns a mutable copy of this object."
138
return self.__deepcopy__()
241
"""Returns a mutable copy of this object."""
242
return self.__deepcopy__({})
140
244
def urlencode(self):
142
246
for k, list_ in self.lists():
143
output.extend([urlencode({k: v}) for v in list_])
247
k = smart_str(k, self.encoding)
248
output.extend([urlencode({k: smart_str(v, self.encoding)}) for v in list_])
144
249
return '&'.join(output)
146
251
def parse_cookie(cookie):
152
262
for key in c.keys():
153
263
cookiedict[key] = c.get(key).value
154
264
return cookiedict
156
266
class HttpResponse(object):
157
"A basic HTTP response, with content and dictionary-accessed headers"
158
def __init__(self, content='', mimetype=None):
267
"""A basic HTTP response, with content and dictionary-accessed headers."""
271
def __init__(self, content='', mimetype=None, status=None,
159
273
from django.conf import settings
160
274
self._charset = settings.DEFAULT_CHARSET
162
mimetype = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, settings.DEFAULT_CHARSET)
276
content_type = mimetype # For backwards compatibility
278
content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
279
settings.DEFAULT_CHARSET)
163
280
if not isinstance(content, basestring) and hasattr(content, '__iter__'):
164
281
self._container = content
165
282
self._is_string = False
167
284
self._container = [content]
168
285
self._is_string = True
169
self.headers = {'Content-Type': mimetype}
170
286
self.cookies = SimpleCookie()
171
self.status_code = 200
288
self.status_code = status
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
293
self._headers = {'content-type': ('Content-Type', content_type)}
173
295
def __str__(self):
174
"Full HTTP message, including headers"
296
"""Full HTTP message, including headers."""
175
297
return '\n'.join(['%s: %s' % (key, value)
176
for key, value in self.headers.items()]) \
298
for key, value in self._headers.values()]) \
177
299
+ '\n\n' + self.content
301
def _convert_to_ascii(self, *values):
302
"""Converts all values to ascii strings."""
304
if isinstance(value, unicode):
306
yield value.encode('us-ascii')
307
except UnicodeError, e:
308
e.reason += ', HTTP response headers must be in US-ASCII format'
179
313
def __setitem__(self, header, value):
180
self.headers[header] = value
314
header, value = self._convert_to_ascii(header, value)
315
self._headers[header.lower()] = (header, value)
182
317
def __delitem__(self, header):
184
del self.headers[header]
319
del self._headers[header.lower()]
188
323
def __getitem__(self, header):
189
return self.headers[header]
324
return self._headers[header.lower()][1]
191
326
def has_header(self, header):
192
"Case-insensitive check for a header"
193
header = header.lower()
194
for key in self.headers.keys():
195
if key.lower() == header:
199
def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None):
327
"""Case-insensitive check for a header."""
328
return self._headers.has_key(header.lower())
330
__contains__ = has_header
333
return self._headers.values()
335
def get(self, header, alternate):
336
return self._headers.get(header.lower(), (None, alternate))[1]
338
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
339
domain=None, secure=False):
200
340
self.cookies[key] = value
201
for var in ('max_age', 'path', 'domain', 'secure', 'expires'):
204
self.cookies[key][var.replace('_', '-')] = val
206
def delete_cookie(self, key, path='/', domain=None):
207
self.cookies[key] = ''
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
208
345
if path is not None:
209
346
self.cookies[key]['path'] = path
210
347
if domain is not None:
211
348
self.cookies[key]['domain'] = domain
212
self.cookies[key]['expires'] = 0
213
self.cookies[key]['max-age'] = 0
350
self.cookies[key]['secure'] = True
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')
215
356
def _get_content(self):
216
content = ''.join(self._container)
217
if isinstance(content, unicode):
218
content = content.encode(self._charset)
357
if self.has_header('Content-Encoding'):
358
return ''.join(self._container)
359
return smart_str(''.join(self._container), self._charset)
221
361
def _set_content(self, value):
222
362
self._container = [value]
252
392
if not self._is_string:
253
raise Exception, "This %s instance cannot tell its position" % self.__class__
393
raise Exception("This %s instance cannot tell its position" % self.__class__)
254
394
return sum([len(chunk) for chunk in self._container])
256
396
class HttpResponseRedirect(HttpResponse):
257
399
def __init__(self, redirect_to):
258
400
HttpResponse.__init__(self)
259
self['Location'] = quote(redirect_to, safe=RESERVED_CHARS)
260
self.status_code = 302
401
self['Location'] = iri_to_uri(redirect_to)
262
403
class HttpResponsePermanentRedirect(HttpResponse):
263
406
def __init__(self, redirect_to):
264
407
HttpResponse.__init__(self)
265
self['Location'] = quote(redirect_to, safe=RESERVED_CHARS)
266
self.status_code = 301
408
self['Location'] = iri_to_uri(redirect_to)
268
410
class HttpResponseNotModified(HttpResponse):
270
HttpResponse.__init__(self)
271
self.status_code = 304
413
class HttpResponseBadRequest(HttpResponse):
273
416
class HttpResponseNotFound(HttpResponse):
274
def __init__(self, *args, **kwargs):
275
HttpResponse.__init__(self, *args, **kwargs)
276
self.status_code = 404
278
419
class HttpResponseForbidden(HttpResponse):
279
def __init__(self, *args, **kwargs):
280
HttpResponse.__init__(self, *args, **kwargs)
281
self.status_code = 403
283
422
class HttpResponseNotAllowed(HttpResponse):
284
425
def __init__(self, permitted_methods):
285
426
HttpResponse.__init__(self)
286
427
self['Allow'] = ', '.join(permitted_methods)
287
self.status_code = 405
289
429
class HttpResponseGone(HttpResponse):
290
432
def __init__(self, *args, **kwargs):
291
433
HttpResponse.__init__(self, *args, **kwargs)
292
self.status_code = 410
294
435
class HttpResponseServerError(HttpResponse):
295
438
def __init__(self, *args, **kwargs):
296
439
HttpResponse.__init__(self, *args, **kwargs)
297
self.status_code = 500
441
# A backwards compatible alias for HttpRequest.get_host.
299
442
def get_host(request):
300
"Gets the HTTP host from the environment or request headers."
301
host = request.META.get('HTTP_X_FORWARDED_HOST', '')
303
host = request.META.get('HTTP_HOST', '')
443
return request.get_host()
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):
450
Converts basestring objects to unicode, using the given encoding. Illegally
451
encoded input characters are replaced with Unicode "unknown" codepoint
454
Returns any non-basestring objects without change.
456
if isinstance(s, str):
457
return unicode(s, encoding, 'replace')