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
="!*'();:@&=+$,/?%#[]"
11 # The mod_python version is more efficient, so try importing it first.
12 from mod_python
.util
import parse_qsl
14 from cgi
import parse_qsl
16 class Http404(Exception):
19 class HttpRequest(object):
20 "A basic HTTP request"
22 # The encoding used in GET/POST dicts. None means use default setting.
26 self
.GET
, self
.POST
, self
.COOKIES
, self
.META
, self
.FILES
= {}, {}, {}, {}, {}
31 return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
32 (pformat(self
.GET
), pformat(self
.POST
), pformat(self
.COOKIES
),
35 def __getitem__(self
, key
):
36 for d
in (self
.POST
, self
.GET
):
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
):
48 return os
.environ
.get("HTTPS") == "on"
50 def _set_encoding(self
, val
):
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).
57 if hasattr(self
, '_get'):
59 if hasattr(self
, '_post'):
62 def _get_encoding(self
):
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():
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'], {
91 'content-type': 'Content-Type' in submessage
and submessage
['Content-Type'] or None,
92 'content': submessage
.get_payload(),
95 POST
.appendlist(name_dict
['name'], submessage
.get_payload())
98 class QueryDict(MultiValueDict
):
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
)
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
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
)
134 result
= self
.__class
__('', mutable
=True)
135 for key
, value
in dict.items(self
):
136 dict.__setitem
__(result
, key
, value
)
139 def __deepcopy__(self
, memo
={}):
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
))
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
()
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
)
176 self
._assert
_mutable
()
177 return MultiValueDict
.popitem(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
)
190 "Returns a mutable copy of this object."
191 return self
.__deepcopy
__()
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
):
207 cookiedict
[key
] = c
.get(key
).value
210 class HttpResponse(object):
211 "A basic HTTP response, with content and dictionary-accessed headers"
215 def __init__(self
, content
='', mimetype
=None, status
=None,
217 from django
.conf
import settings
218 self
._charset
= settings
.DEFAULT_CHARSET
220 content_type
= mimetype
# For backwards compatibility
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
228 self
._container
= [content
]
229 self
._is
_string
= True
230 self
.headers
= {'Content-Type': content_type
}
231 self
.cookies
= SimpleCookie()
233 self
.status_code
= status
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
):
246 del self
.headers
[header
]
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
:
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'):
266 self
.cookies
[key
][var
.replace('_', '-')] = val
268 def delete_cookie(self
, key
, path
='/', domain
=None):
269 self
.cookies
[key
] = ''
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
)
281 def _set_content(self
, value
):
282 self
._container
= [value
]
283 self
._is
_string
= True
285 content
= property(_get_content
, _set_content
)
288 self
._iterator
= self
._container
.__iter
__()
292 chunk
= self
._iterator
.next()
293 if isinstance(chunk
, unicode):
294 chunk
= chunk
.encode(self
._charset
)
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
)
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
):
319 def __init__(self
, redirect_to
):
320 HttpResponse
.__init
__(self
)
321 self
['Location'] = iri_to_uri(redirect_to
)
323 class HttpResponsePermanentRedirect(HttpResponse
):
326 def __init__(self
, redirect_to
):
327 HttpResponse
.__init
__(self
)
328 self
['Location'] = iri_to_uri(redirect_to
)
330 class HttpResponseNotModified(HttpResponse
):
333 class HttpResponseBadRequest(HttpResponse
):
336 class HttpResponseNotFound(HttpResponse
):
339 class HttpResponseForbidden(HttpResponse
):
342 class HttpResponseNotAllowed(HttpResponse
):
345 def __init__(self
, permitted_methods
):
346 HttpResponse
.__init
__(self
)
347 self
['Allow'] = ', '.join(permitted_methods
)
349 class HttpResponseGone(HttpResponse
):
352 def __init__(self
, *args
, **kwargs
):
353 HttpResponse
.__init
__(self
, *args
, **kwargs
)
355 class HttpResponseServerError(HttpResponse
):
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', '')
365 host
= request
.META
.get('HTTP_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
377 Returns any non-basestring objects without change.
379 if isinstance(s
, str):
380 return unicode(s
, encoding
, 'replace')