2 from Cookie
import SimpleCookie
3 from pprint
import pformat
4 from urllib
import urlencode
5 from urlparse
import urljoin
6 from django
.utils
.datastructures
import MultiValueDict
, FileDict
7 from django
.utils
.encoding
import smart_str
, iri_to_uri
, force_unicode
9 RESERVED_CHARS
="!*'();:@&=+$,/?%#[]"
12 # The mod_python version is more efficient, so try importing it first.
13 from mod_python
.util
import parse_qsl
15 from cgi
import parse_qsl
17 class Http404(Exception):
20 class HttpRequest(object):
21 "A basic HTTP request"
23 # The encoding used in GET/POST dicts. None means use default setting.
27 self
.GET
, self
.POST
, self
.COOKIES
, self
.META
, self
.FILES
= {}, {}, {}, {}, {}
32 return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
33 (pformat(self
.GET
), pformat(self
.POST
), pformat(self
.COOKIES
),
36 def __getitem__(self
, key
):
37 for d
in (self
.POST
, self
.GET
):
40 raise KeyError, "%s not found in either POST or GET" % key
42 def has_key(self
, key
):
43 return key
in self
.GET
or key
in self
.POST
45 __contains__
= has_key
47 def get_full_path(self
):
50 def build_absolute_uri(self
, location
=None):
52 Builds an absolute URI from the location and the variables available in
53 this request. If no location is specified, the absolute URI is built on
54 ``request.get_full_path()``.
57 location
= request
.get_full_path()
58 if not ':' in location
:
59 current_uri
= '%s://%s%s' % (self
.is_secure() and 'https' or 'http',
60 get_host(self
), self
.path
)
61 location
= urljoin(current_uri
, location
)
65 return os
.environ
.get("HTTPS") == "on"
67 def _set_encoding(self
, val
):
69 Sets the encoding used for GET/POST accesses. If the GET or POST
70 dictionary has already been created, it is removed and recreated on the
71 next access (so that it is decoded correctly).
74 if hasattr(self
, '_get'):
76 if hasattr(self
, '_post'):
79 def _get_encoding(self
):
82 encoding
= property(_get_encoding
, _set_encoding
)
84 def parse_file_upload(header_dict
, post_data
):
85 "Returns a tuple of (POST QueryDict, FILES MultiValueDict)"
86 import email
, email
.Message
87 from cgi
import parse_header
88 raw_message
= '\r\n'.join(['%s:%s' % pair
for pair
in header_dict
.items()])
89 raw_message
+= '\r\n\r\n' + post_data
90 msg
= email
.message_from_string(raw_message
)
91 POST
= QueryDict('', mutable
=True)
92 FILES
= MultiValueDict()
93 for submessage
in msg
.get_payload():
94 if submessage
and isinstance(submessage
, email
.Message
.Message
):
95 name_dict
= parse_header(submessage
['Content-Disposition'])[1]
96 # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
97 # or {'name': 'blah'} for POST fields
98 # We assume all uploaded files have a 'filename' set.
99 if 'filename' in name_dict
:
100 assert type([]) != type(submessage
.get_payload()), "Nested MIME messages are not supported"
101 if not name_dict
['filename'].strip():
103 # IE submits the full path, so trim everything but the basename.
104 # (We can't use os.path.basename because that uses the server's
105 # directory separator, which may not be the same as the
107 filename
= name_dict
['filename'][name_dict
['filename'].rfind("\\")+1:]
108 FILES
.appendlist(name_dict
['name'], FileDict({
109 'filename': filename
,
110 'content-type': 'Content-Type' in submessage
and submessage
['Content-Type'] or None,
111 'content': submessage
.get_payload(),
114 POST
.appendlist(name_dict
['name'], submessage
.get_payload())
117 class QueryDict(MultiValueDict
):
119 A specialized MultiValueDict that takes a query string when initialized.
120 This is immutable unless you create a copy of it.
122 Values retrieved from this class are converted from the given encoding
123 (DEFAULT_CHARSET by default) to unicode.
125 def __init__(self
, query_string
, mutable
=False, encoding
=None):
126 MultiValueDict
.__init
__(self
)
128 # *Important*: do not import settings any earlier because of note
129 # in core.handlers.modpython.
130 from django
.conf
import settings
131 encoding
= settings
.DEFAULT_CHARSET
132 self
.encoding
= encoding
134 for key
, value
in parse_qsl((query_string
or ''), True): # keep_blank_values=True
135 self
.appendlist(force_unicode(key
, encoding
, errors
='replace'), force_unicode(value
, encoding
, errors
='replace'))
136 self
._mutable
= mutable
138 def _assert_mutable(self
):
139 if not self
._mutable
:
140 raise AttributeError, "This QueryDict instance is immutable"
142 def __setitem__(self
, key
, value
):
143 self
._assert
_mutable
()
144 key
= str_to_unicode(key
, self
.encoding
)
145 value
= str_to_unicode(value
, self
.encoding
)
146 MultiValueDict
.__setitem
__(self
, key
, value
)
148 def __delitem__(self
, key
):
149 self
._assert
_mutable
()
150 super(QueryDict
, self
).__delitem
__(key
)
153 result
= self
.__class
__('', mutable
=True)
154 for key
, value
in dict.items(self
):
155 dict.__setitem
__(result
, key
, value
)
158 def __deepcopy__(self
, memo
={}):
160 result
= self
.__class
__('', mutable
=True)
161 memo
[id(self
)] = result
162 for key
, value
in dict.items(self
):
163 dict.__setitem
__(result
, copy
.deepcopy(key
, memo
), copy
.deepcopy(value
, memo
))
166 def setlist(self
, key
, list_
):
167 self
._assert
_mutable
()
168 key
= str_to_unicode(key
, self
.encoding
)
169 list_
= [str_to_unicode(elt
, self
.encoding
) for elt
in list_
]
170 MultiValueDict
.setlist(self
, key
, list_
)
172 def setlistdefault(self
, key
, default_list
=()):
173 self
._assert
_mutable
()
175 self
.setlist(key
, default_list
)
176 return MultiValueDict
.getlist(self
, key
)
178 def appendlist(self
, key
, value
):
179 self
._assert
_mutable
()
180 key
= str_to_unicode(key
, self
.encoding
)
181 value
= str_to_unicode(value
, self
.encoding
)
182 MultiValueDict
.appendlist(self
, key
, value
)
184 def update(self
, other_dict
):
185 self
._assert
_mutable
()
186 f
= lambda s
: str_to_unicode(s
, self
.encoding
)
187 d
= dict([(f(k
), f(v
)) for k
, v
in other_dict
.items()])
188 MultiValueDict
.update(self
, d
)
190 def pop(self
, key
, *args
):
191 self
._assert
_mutable
()
192 return MultiValueDict
.pop(self
, key
, *args
)
195 self
._assert
_mutable
()
196 return MultiValueDict
.popitem(self
)
199 self
._assert
_mutable
()
200 MultiValueDict
.clear(self
)
202 def setdefault(self
, key
, default
=None):
203 self
._assert
_mutable
()
204 key
= str_to_unicode(key
, self
.encoding
)
205 default
= str_to_unicode(default
, self
.encoding
)
206 return MultiValueDict
.setdefault(self
, key
, default
)
209 "Returns a mutable copy of this object."
210 return self
.__deepcopy
__()
214 for k
, list_
in self
.lists():
215 k
= smart_str(k
, self
.encoding
)
216 output
.extend([urlencode({k
: smart_str(v
, self
.encoding
)}) for v
in list_
])
217 return '&'.join(output
)
219 def parse_cookie(cookie
):
226 cookiedict
[key
] = c
.get(key
).value
229 class HttpResponse(object):
230 "A basic HTTP response, with content and dictionary-accessed headers"
234 def __init__(self
, content
='', mimetype
=None, status
=None,
236 from django
.conf
import settings
237 self
._charset
= settings
.DEFAULT_CHARSET
239 content_type
= mimetype
# For backwards compatibility
241 content_type
= "%s; charset=%s" % (settings
.DEFAULT_CONTENT_TYPE
,
242 settings
.DEFAULT_CHARSET
)
243 if not isinstance(content
, basestring
) and hasattr(content
, '__iter__'):
244 self
._container
= content
245 self
._is
_string
= False
247 self
._container
= [content
]
248 self
._is
_string
= True
249 self
.headers
= {'Content-Type': content_type
}
250 self
.cookies
= SimpleCookie()
252 self
.status_code
= status
255 "Full HTTP message, including headers"
256 return '\n'.join(['%s: %s' % (key
, value
)
257 for key
, value
in self
.headers
.items()]) \
258 + '\n\n' + self
.content
260 def __setitem__(self
, header
, value
):
261 self
.headers
[header
] = value
263 def __delitem__(self
, header
):
265 del self
.headers
[header
]
269 def __getitem__(self
, header
):
270 return self
.headers
[header
]
272 def has_header(self
, header
):
273 "Case-insensitive check for a header"
274 header
= header
.lower()
275 for key
in self
.headers
.keys():
276 if key
.lower() == header
:
280 def set_cookie(self
, key
, value
='', max_age
=None, expires
=None, path
='/', domain
=None, secure
=None):
281 self
.cookies
[key
] = value
282 for var
in ('max_age', 'path', 'domain', 'secure', 'expires'):
285 self
.cookies
[key
][var
.replace('_', '-')] = val
287 def delete_cookie(self
, key
, path
='/', domain
=None):
288 self
.cookies
[key
] = ''
290 self
.cookies
[key
]['path'] = path
291 if domain
is not None:
292 self
.cookies
[key
]['domain'] = domain
293 self
.cookies
[key
]['expires'] = 0
294 self
.cookies
[key
]['max-age'] = 0
296 def _get_content(self
):
297 content
= smart_str(''.join(self
._container
), self
._charset
)
300 def _set_content(self
, value
):
301 self
._container
= [value
]
302 self
._is
_string
= True
304 content
= property(_get_content
, _set_content
)
307 self
._iterator
= self
._container
.__iter
__()
311 chunk
= self
._iterator
.next()
312 if isinstance(chunk
, unicode):
313 chunk
= chunk
.encode(self
._charset
)
317 if hasattr(self
._container
, 'close'):
318 self
._container
.close()
320 # The remaining methods partially implement the file-like object interface.
321 # See http://docs.python.org/lib/bltin-file-objects.html
322 def write(self
, content
):
323 if not self
._is
_string
:
324 raise Exception, "This %s instance is not writable" % self
.__class
__
325 self
._container
.append(content
)
331 if not self
._is
_string
:
332 raise Exception, "This %s instance cannot tell its position" % self
.__class
__
333 return sum([len(chunk
) for chunk
in self
._container
])
335 class HttpResponseRedirect(HttpResponse
):
338 def __init__(self
, redirect_to
):
339 HttpResponse
.__init
__(self
)
340 self
['Location'] = iri_to_uri(redirect_to
)
342 class HttpResponsePermanentRedirect(HttpResponse
):
345 def __init__(self
, redirect_to
):
346 HttpResponse
.__init
__(self
)
347 self
['Location'] = iri_to_uri(redirect_to
)
349 class HttpResponseNotModified(HttpResponse
):
352 class HttpResponseBadRequest(HttpResponse
):
355 class HttpResponseNotFound(HttpResponse
):
358 class HttpResponseForbidden(HttpResponse
):
361 class HttpResponseNotAllowed(HttpResponse
):
364 def __init__(self
, permitted_methods
):
365 HttpResponse
.__init
__(self
)
366 self
['Allow'] = ', '.join(permitted_methods
)
368 class HttpResponseGone(HttpResponse
):
371 def __init__(self
, *args
, **kwargs
):
372 HttpResponse
.__init
__(self
, *args
, **kwargs
)
374 class HttpResponseServerError(HttpResponse
):
377 def __init__(self
, *args
, **kwargs
):
378 HttpResponse
.__init
__(self
, *args
, **kwargs
)
380 def get_host(request
):
381 "Gets the HTTP host from the environment or request headers."
382 # We try three options, in order of decreasing preference.
383 host
= request
.META
.get('HTTP_X_FORWARDED_HOST', '')
384 if 'HTTP_HOST' in request
.META
:
385 host
= request
.META
['HTTP_HOST']
387 # Reconstruct the host using the algorithm from PEP 333.
388 host
= request
.META
['SERVER_NAME']
389 server_port
= request
.META
['SERVER_PORT']
390 if server_port
!= (request
.is_secure() and 443 or 80):
391 host
= '%s:%s' % (host
, server_port
)
394 # It's neither necessary nor appropriate to use
395 # django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus,
396 # this slightly more restricted function.
397 def str_to_unicode(s
, encoding
):
399 Convert basestring objects to unicode, using the given encoding. Illegaly
400 encoded input characters are replaced with Unicode "unknown" codepoint
403 Returns any non-basestring objects without change.
405 if isinstance(s
, str):
406 return unicode(s
, encoding
, 'replace')