Fixed #3526 -- Added content_type as an alias for mimetype to the HttpResponse constr...
[django.git] / django / http / __init__.py
blobfe3b9896633768ae3b7a5afa43dcdb4bc38d895b
1 import os
2 from Cookie import SimpleCookie
3 from pprint import pformat
4 from urllib import urlencode
5 from django.utils.datastructures import MultiValueDict
6 from django.utils.encoding import smart_str, iri_to_uri, force_unicode
8 RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
10 try:
11 # The mod_python version is more efficient, so try importing it first.
12 from mod_python.util import parse_qsl
13 except ImportError:
14 from cgi import parse_qsl
16 class Http404(Exception):
17 pass
19 class HttpRequest(object):
20 "A basic HTTP request"
22 # The encoding used in GET/POST dicts. None means use default setting.
23 _encoding = None
25 def __init__(self):
26 self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
27 self.path = ''
28 self.method = None
30 def __repr__(self):
31 return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
32 (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
33 pformat(self.META))
35 def __getitem__(self, key):
36 for d in (self.POST, self.GET):
37 if key in d:
38 return d[key]
39 raise KeyError, "%s not found in either POST or GET" % key
41 def has_key(self, key):
42 return key in self.GET or key in self.POST
44 def get_full_path(self):
45 return ''
47 def is_secure(self):
48 return os.environ.get("HTTPS") == "on"
50 def _set_encoding(self, val):
51 """
52 Sets the encoding used for GET/POST accesses. If the GET or POST
53 dictionary has already been created, it is removed and recreated on the
54 next access (so that it is decoded correctly).
55 """
56 self._encoding = val
57 if hasattr(self, '_get'):
58 del self._get
59 if hasattr(self, '_post'):
60 del self._post
62 def _get_encoding(self):
63 return self._encoding
65 encoding = property(_get_encoding, _set_encoding)
67 def parse_file_upload(header_dict, post_data):
68 "Returns a tuple of (POST QueryDict, FILES MultiValueDict)"
69 import email, email.Message
70 from cgi import parse_header
71 raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
72 raw_message += '\r\n\r\n' + post_data
73 msg = email.message_from_string(raw_message)
74 POST = QueryDict('', mutable=True)
75 FILES = MultiValueDict()
76 for submessage in msg.get_payload():
77 if submessage and isinstance(submessage, email.Message.Message):
78 name_dict = parse_header(submessage['Content-Disposition'])[1]
79 # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
80 # or {'name': 'blah'} for POST fields
81 # We assume all uploaded files have a 'filename' set.
82 if 'filename' in name_dict:
83 assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
84 if not name_dict['filename'].strip():
85 continue
86 # IE submits the full path, so trim everything but the basename.
87 # (We can't use os.path.basename because it expects Linux paths.)
88 filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
89 FILES.appendlist(name_dict['name'], {
90 'filename': filename,
91 'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None,
92 'content': submessage.get_payload(),
94 else:
95 POST.appendlist(name_dict['name'], submessage.get_payload())
96 return POST, FILES
98 class QueryDict(MultiValueDict):
99 """
100 A specialized MultiValueDict that takes a query string when initialized.
101 This is immutable unless you create a copy of it.
103 Values retrieved from this class are converted from the given encoding
104 (DEFAULT_CHARSET by default) to unicode.
106 def __init__(self, query_string, mutable=False, encoding=None):
107 MultiValueDict.__init__(self)
108 if not encoding:
109 # *Important*: do not import settings any earlier because of note
110 # in core.handlers.modpython.
111 from django.conf import settings
112 encoding = settings.DEFAULT_CHARSET
113 self.encoding = encoding
114 self._mutable = True
115 for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
116 self.appendlist(force_unicode(key, encoding, errors='replace'), force_unicode(value, encoding, errors='replace'))
117 self._mutable = mutable
119 def _assert_mutable(self):
120 if not self._mutable:
121 raise AttributeError, "This QueryDict instance is immutable"
123 def __setitem__(self, key, value):
124 self._assert_mutable()
125 key = str_to_unicode(key, self.encoding)
126 value = str_to_unicode(value, self.encoding)
127 MultiValueDict.__setitem__(self, key, value)
129 def __delitem__(self, key):
130 self._assert_mutable()
131 super(QueryDict, self).__delitem__(key)
133 def __copy__(self):
134 result = self.__class__('', mutable=True)
135 for key, value in dict.items(self):
136 dict.__setitem__(result, key, value)
137 return result
139 def __deepcopy__(self, memo={}):
140 import copy
141 result = self.__class__('', mutable=True)
142 memo[id(self)] = result
143 for key, value in dict.items(self):
144 dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo))
145 return result
147 def setlist(self, key, list_):
148 self._assert_mutable()
149 key = str_to_unicode(key, self.encoding)
150 list_ = [str_to_unicode(elt, self.encoding) for elt in list_]
151 MultiValueDict.setlist(self, key, list_)
153 def setlistdefault(self, key, default_list=()):
154 self._assert_mutable()
155 if key not in self:
156 self.setlist(key, default_list)
157 return MultiValueDict.getlist(self, key)
159 def appendlist(self, key, value):
160 self._assert_mutable()
161 key = str_to_unicode(key, self.encoding)
162 value = str_to_unicode(value, self.encoding)
163 MultiValueDict.appendlist(self, key, value)
165 def update(self, other_dict):
166 self._assert_mutable()
167 f = lambda s: str_to_unicode(s, self.encoding)
168 d = dict([(f(k), f(v)) for k, v in other_dict.items()])
169 MultiValueDict.update(self, d)
171 def pop(self, key, *args):
172 self._assert_mutable()
173 return MultiValueDict.pop(self, key, *args)
175 def popitem(self):
176 self._assert_mutable()
177 return MultiValueDict.popitem(self)
179 def clear(self):
180 self._assert_mutable()
181 MultiValueDict.clear(self)
183 def setdefault(self, key, default=None):
184 self._assert_mutable()
185 key = str_to_unicode(key, self.encoding)
186 default = str_to_unicode(default, self.encoding)
187 return MultiValueDict.setdefault(self, key, default)
189 def copy(self):
190 "Returns a mutable copy of this object."
191 return self.__deepcopy__()
193 def urlencode(self):
194 output = []
195 for k, list_ in self.lists():
196 k = smart_str(k, self.encoding)
197 output.extend([urlencode({k: smart_str(v, self.encoding)}) for v in list_])
198 return '&'.join(output)
200 def parse_cookie(cookie):
201 if cookie == '':
202 return {}
203 c = SimpleCookie()
204 c.load(cookie)
205 cookiedict = {}
206 for key in c.keys():
207 cookiedict[key] = c.get(key).value
208 return cookiedict
210 class HttpResponse(object):
211 "A basic HTTP response, with content and dictionary-accessed headers"
213 status_code = 200
215 def __init__(self, content='', mimetype=None, status=None,
216 content_type=None):
217 from django.conf import settings
218 self._charset = settings.DEFAULT_CHARSET
219 if mimetype:
220 content_type = mimetype # For backwards compatibility
221 if not content_type:
222 content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
223 settings.DEFAULT_CHARSET)
224 if not isinstance(content, basestring) and hasattr(content, '__iter__'):
225 self._container = content
226 self._is_string = False
227 else:
228 self._container = [content]
229 self._is_string = True
230 self.headers = {'Content-Type': content_type}
231 self.cookies = SimpleCookie()
232 if status:
233 self.status_code = status
235 def __str__(self):
236 "Full HTTP message, including headers"
237 return '\n'.join(['%s: %s' % (key, value)
238 for key, value in self.headers.items()]) \
239 + '\n\n' + self.content
241 def __setitem__(self, header, value):
242 self.headers[header] = value
244 def __delitem__(self, header):
245 try:
246 del self.headers[header]
247 except KeyError:
248 pass
250 def __getitem__(self, header):
251 return self.headers[header]
253 def has_header(self, header):
254 "Case-insensitive check for a header"
255 header = header.lower()
256 for key in self.headers.keys():
257 if key.lower() == header:
258 return True
259 return False
261 def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None):
262 self.cookies[key] = value
263 for var in ('max_age', 'path', 'domain', 'secure', 'expires'):
264 val = locals()[var]
265 if val is not None:
266 self.cookies[key][var.replace('_', '-')] = val
268 def delete_cookie(self, key, path='/', domain=None):
269 self.cookies[key] = ''
270 if path is not None:
271 self.cookies[key]['path'] = path
272 if domain is not None:
273 self.cookies[key]['domain'] = domain
274 self.cookies[key]['expires'] = 0
275 self.cookies[key]['max-age'] = 0
277 def _get_content(self):
278 content = smart_str(''.join(self._container), self._charset)
279 return content
281 def _set_content(self, value):
282 self._container = [value]
283 self._is_string = True
285 content = property(_get_content, _set_content)
287 def __iter__(self):
288 self._iterator = self._container.__iter__()
289 return self
291 def next(self):
292 chunk = self._iterator.next()
293 if isinstance(chunk, unicode):
294 chunk = chunk.encode(self._charset)
295 return chunk
297 def close(self):
298 if hasattr(self._container, 'close'):
299 self._container.close()
301 # The remaining methods partially implement the file-like object interface.
302 # See http://docs.python.org/lib/bltin-file-objects.html
303 def write(self, content):
304 if not self._is_string:
305 raise Exception, "This %s instance is not writable" % self.__class__
306 self._container.append(content)
308 def flush(self):
309 pass
311 def tell(self):
312 if not self._is_string:
313 raise Exception, "This %s instance cannot tell its position" % self.__class__
314 return sum([len(chunk) for chunk in self._container])
316 class HttpResponseRedirect(HttpResponse):
317 status_code = 302
319 def __init__(self, redirect_to):
320 HttpResponse.__init__(self)
321 self['Location'] = iri_to_uri(redirect_to)
323 class HttpResponsePermanentRedirect(HttpResponse):
324 status_code = 301
326 def __init__(self, redirect_to):
327 HttpResponse.__init__(self)
328 self['Location'] = iri_to_uri(redirect_to)
330 class HttpResponseNotModified(HttpResponse):
331 status_code = 304
333 class HttpResponseBadRequest(HttpResponse):
334 status_code = 400
336 class HttpResponseNotFound(HttpResponse):
337 status_code = 404
339 class HttpResponseForbidden(HttpResponse):
340 status_code = 403
342 class HttpResponseNotAllowed(HttpResponse):
343 status_code = 405
345 def __init__(self, permitted_methods):
346 HttpResponse.__init__(self)
347 self['Allow'] = ', '.join(permitted_methods)
349 class HttpResponseGone(HttpResponse):
350 status_code = 410
352 def __init__(self, *args, **kwargs):
353 HttpResponse.__init__(self, *args, **kwargs)
355 class HttpResponseServerError(HttpResponse):
356 status_code = 500
358 def __init__(self, *args, **kwargs):
359 HttpResponse.__init__(self, *args, **kwargs)
361 def get_host(request):
362 "Gets the HTTP host from the environment or request headers."
363 host = request.META.get('HTTP_X_FORWARDED_HOST', '')
364 if not host:
365 host = request.META.get('HTTP_HOST', '')
366 return host
368 # It's neither necessary nor appropriate to use
369 # django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus,
370 # this slightly more restricted function.
371 def str_to_unicode(s, encoding):
373 Convert basestring objects to unicode, using the given encoding. Illegaly
374 encoded input characters are replaced with Unicode "unknown" codepoint
375 (\ufffd).
377 Returns any non-basestring objects without change.
379 if isinstance(s, str):
380 return unicode(s, encoding, 'replace')
381 else:
382 return s