3 from Cookie
import SimpleCookie
, CookieError
4 from pprint
import pformat
5 from urllib
import urlencode
6 from urlparse
import urljoin
8 # The mod_python version is more efficient, so try importing it first.
9 from mod_python
.util
import parse_qsl
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
)
24 class Http404(Exception):
27 class HttpRequest(object):
28 """A basic HTTP request."""
30 # The encoding used in GET/POST dicts. None means use default setting.
35 self
.GET
, self
.POST
, self
.COOKIES
, self
.META
, self
.FILES
= {}, {}, {}, {}, {}
41 return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
42 (pformat(self
.GET
), pformat(self
.POST
), pformat(self
.COOKIES
),
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
)
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
)
78 return os
.environ
.get("HTTPS") == "on"
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()
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
139 def __init__(self
, query_string
, mutable
=False, encoding
=None):
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
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
)
180 result
= self
.__class
__('', mutable
=True)
181 for key
, value
in dict.items(self
):
182 dict.__setitem
__(result
, key
, value
)
185 def __deepcopy__(self
, memo
):
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
))
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
()
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
)})
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
)
227 self
._assert
_mutable
()
228 return MultiValueDict
.popitem(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
)
241 """Returns a mutable copy of this object."""
242 return self
.__deepcopy
__({})
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
):
263 cookiedict
[key
] = c
.get(key
).value
266 class HttpResponse(object):
267 """A basic HTTP response, with content and dictionary-accessed headers."""
271 def __init__(self
, content
='', mimetype
=None, status
=None,
273 from django
.conf
import settings
274 self
._charset
= settings
.DEFAULT_CHARSET
276 content_type
= mimetype
# For backwards compatibility
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
284 self
._container
= [content
]
285 self
._is
_string
= True
286 self
.cookies
= SimpleCookie()
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
)}
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."""
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'
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
):
319 del self
._headers
[header
.lower()]
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
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
346 self
.cookies
[key
]['path'] = path
347 if domain
is not None:
348 self
.cookies
[key
]['domain'] = domain
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
)
368 self
._iterator
= iter(self
._container
)
372 chunk
= self
._iterator
.next()
373 if isinstance(chunk
, unicode):
374 chunk
= chunk
.encode(self
._charset
)
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
)
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
):
399 def __init__(self
, redirect_to
):
400 HttpResponse
.__init
__(self
)
401 self
['Location'] = iri_to_uri(redirect_to
)
403 class HttpResponsePermanentRedirect(HttpResponse
):
406 def __init__(self
, redirect_to
):
407 HttpResponse
.__init
__(self
)
408 self
['Location'] = iri_to_uri(redirect_to
)
410 class HttpResponseNotModified(HttpResponse
):
413 class HttpResponseBadRequest(HttpResponse
):
416 class HttpResponseNotFound(HttpResponse
):
419 class HttpResponseForbidden(HttpResponse
):
422 class HttpResponseNotAllowed(HttpResponse
):
425 def __init__(self
, permitted_methods
):
426 HttpResponse
.__init
__(self
)
427 self
['Allow'] = ', '.join(permitted_methods
)
429 class HttpResponseGone(HttpResponse
):
432 def __init__(self
, *args
, **kwargs
):
433 HttpResponse
.__init
__(self
, *args
, **kwargs
)
435 class HttpResponseServerError(HttpResponse
):
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
454 Returns any non-basestring objects without change.
456 if isinstance(s
, str):
457 return unicode(s
, encoding
, 'replace')