Fixed #9199 -- We were erroneously only prepending "www" to the domain if we
[django.git] / django / http / __init__.py
blob60b6d1579546dae21e5a5f76c05fa66c19c77793
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
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 *
20 RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
22 absolute_http_url_re = re.compile(r"^https?://", re.I)
24 class Http404(Exception):
25 pass
27 class HttpRequest(object):
28 """A basic HTTP request."""
30 # The encoding used in GET/POST dicts. None means use default setting.
31 _encoding = None
32 _upload_handlers = []
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
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))
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
60 def get_full_path(self):
61 return ''
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
77 def is_secure(self):
78 return os.environ.get("HTTPS") == "on"
80 def is_ajax(self):
81 return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
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
95 def _get_encoding(self):
96 return self._encoding
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()
126 class QueryDict(MultiValueDict):
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
136 _mutable = True
137 _encoding = None
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
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)
165 def _assert_mutable(self):
166 if not self._mutable:
167 raise AttributeError("This QueryDict instance is immutable")
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)
175 def __delitem__(self, key):
176 self._assert_mutable()
177 super(QueryDict, self).__delitem__(key)
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
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
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_)
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)
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)
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)
222 def pop(self, key, *args):
223 self._assert_mutable()
224 return MultiValueDict.pop(self, key, *args)
226 def popitem(self):
227 self._assert_mutable()
228 return MultiValueDict.popitem(self)
230 def clear(self):
231 self._assert_mutable()
232 MultiValueDict.clear(self)
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)
240 def copy(self):
241 """Returns a mutable copy of this object."""
242 return self.__deepcopy__({})
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)
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 {}
261 cookiedict = {}
262 for key in c.keys():
263 cookiedict[key] = c.get(key).value
264 return cookiedict
266 class HttpResponse(object):
267 """A basic HTTP response, with content and dictionary-accessed headers."""
269 status_code = 200
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
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)}
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
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)
313 def __setitem__(self, header, value):
314 header, value = self._convert_to_ascii(header, value)
315 self._headers[header.lower()] = (header, value)
317 def __delitem__(self, header):
318 try:
319 del self._headers[header.lower()]
320 except KeyError:
321 pass
323 def __getitem__(self, header):
324 return self._headers[header.lower()][1]
326 def has_header(self, header):
327 """Case-insensitive check for a header."""
328 return self._headers.has_key(header.lower())
330 __contains__ = has_header
332 def items(self):
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):
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
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')
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)
361 def _set_content(self, value):
362 self._container = [value]
363 self._is_string = True
365 content = property(_get_content, _set_content)
367 def __iter__(self):
368 self._iterator = iter(self._container)
369 return self
371 def next(self):
372 chunk = self._iterator.next()
373 if isinstance(chunk, unicode):
374 chunk = chunk.encode(self._charset)
375 return str(chunk)
377 def close(self):
378 if hasattr(self._container, 'close'):
379 self._container.close()
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)
388 def flush(self):
389 pass
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])
396 class HttpResponseRedirect(HttpResponse):
397 status_code = 302
399 def __init__(self, redirect_to):
400 HttpResponse.__init__(self)
401 self['Location'] = iri_to_uri(redirect_to)
403 class HttpResponsePermanentRedirect(HttpResponse):
404 status_code = 301
406 def __init__(self, redirect_to):
407 HttpResponse.__init__(self)
408 self['Location'] = iri_to_uri(redirect_to)
410 class HttpResponseNotModified(HttpResponse):
411 status_code = 304
413 class HttpResponseBadRequest(HttpResponse):
414 status_code = 400
416 class HttpResponseNotFound(HttpResponse):
417 status_code = 404
419 class HttpResponseForbidden(HttpResponse):
420 status_code = 403
422 class HttpResponseNotAllowed(HttpResponse):
423 status_code = 405
425 def __init__(self, permitted_methods):
426 HttpResponse.__init__(self)
427 self['Allow'] = ', '.join(permitted_methods)
429 class HttpResponseGone(HttpResponse):
430 status_code = 410
432 def __init__(self, *args, **kwargs):
433 HttpResponse.__init__(self, *args, **kwargs)
435 class HttpResponseServerError(HttpResponse):
436 status_code = 500
438 def __init__(self, *args, **kwargs):
439 HttpResponse.__init__(self, *args, **kwargs)
441 # A backwards compatible alias for HttpRequest.get_host.
442 def get_host(request):
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
452 (\ufffd).
454 Returns any non-basestring objects without change.
456 if isinstance(s, str):
457 return unicode(s, encoding, 'replace')
458 else:
459 return s