om@3
|
1 |
"""
|
om@3
|
2 |
Web API (wrapper around WSGI)
|
om@3
|
3 |
(from web.py)
|
om@3
|
4 |
"""
|
om@3
|
5 |
|
om@3
|
6 |
__all__ = [
|
om@3
|
7 |
"config",
|
om@3
|
8 |
"header", "debug",
|
om@3
|
9 |
"input", "data",
|
om@3
|
10 |
"setcookie", "cookies",
|
om@3
|
11 |
"ctx",
|
om@3
|
12 |
"HTTPError",
|
om@3
|
13 |
|
om@3
|
14 |
# 200, 201, 202
|
om@3
|
15 |
"OK", "Created", "Accepted",
|
om@3
|
16 |
"ok", "created", "accepted",
|
om@3
|
17 |
|
om@3
|
18 |
# 301, 302, 303, 304, 307
|
om@3
|
19 |
"Redirect", "Found", "SeeOther", "NotModified", "TempRedirect",
|
om@3
|
20 |
"redirect", "found", "seeother", "notmodified", "tempredirect",
|
om@3
|
21 |
|
om@3
|
22 |
# 400, 401, 403, 404, 405, 406, 409, 410, 412, 415
|
om@3
|
23 |
"BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", "NotAcceptable", "Conflict", "Gone", "PreconditionFailed", "UnsupportedMediaType",
|
om@3
|
24 |
"badrequest", "unauthorized", "forbidden", "notfound", "nomethod", "notacceptable", "conflict", "gone", "preconditionfailed", "unsupportedmediatype",
|
om@3
|
25 |
|
om@3
|
26 |
# 500
|
om@3
|
27 |
"InternalError",
|
om@3
|
28 |
"internalerror",
|
om@3
|
29 |
]
|
om@3
|
30 |
|
om@3
|
31 |
import sys, cgi, Cookie, pprint, urlparse, urllib
|
om@3
|
32 |
from utils import storage, storify, threadeddict, dictadd, intget, safestr
|
om@3
|
33 |
|
om@3
|
34 |
config = storage()
|
om@3
|
35 |
config.__doc__ = """
|
om@3
|
36 |
A configuration object for various aspects of web.py.
|
om@3
|
37 |
|
om@3
|
38 |
`debug`
|
om@3
|
39 |
: when True, enables reloading, disabled template caching and sets internalerror to debugerror.
|
om@3
|
40 |
"""
|
om@3
|
41 |
|
om@3
|
42 |
class HTTPError(Exception):
|
om@3
|
43 |
def __init__(self, status, headers={}, data=""):
|
om@3
|
44 |
ctx.status = status
|
om@3
|
45 |
for k, v in headers.items():
|
om@3
|
46 |
header(k, v)
|
om@3
|
47 |
self.data = data
|
om@3
|
48 |
Exception.__init__(self, status)
|
om@3
|
49 |
|
om@3
|
50 |
def _status_code(status, data=None, classname=None, docstring=None):
|
om@3
|
51 |
if data is None:
|
om@3
|
52 |
data = status.split(" ", 1)[1]
|
om@3
|
53 |
classname = status.split(" ", 1)[1].replace(' ', '') # 304 Not Modified -> NotModified
|
om@3
|
54 |
docstring = docstring or '`%s` status' % status
|
om@3
|
55 |
|
om@3
|
56 |
def __init__(self, data=data, headers={}):
|
om@3
|
57 |
HTTPError.__init__(self, status, headers, data)
|
om@3
|
58 |
|
om@3
|
59 |
# trick to create class dynamically with dynamic docstring.
|
om@3
|
60 |
return type(classname, (HTTPError, object), {
|
om@3
|
61 |
'__doc__': docstring,
|
om@3
|
62 |
'__init__': __init__
|
om@3
|
63 |
})
|
om@3
|
64 |
|
om@3
|
65 |
ok = OK = _status_code("200 OK", data="")
|
om@3
|
66 |
created = Created = _status_code("201 Created")
|
om@3
|
67 |
accepted = Accepted = _status_code("202 Accepted")
|
om@3
|
68 |
|
om@3
|
69 |
class Redirect(HTTPError):
|
om@3
|
70 |
"""A `301 Moved Permanently` redirect."""
|
om@3
|
71 |
def __init__(self, url, status='301 Moved Permanently', absolute=False):
|
om@3
|
72 |
"""
|
om@3
|
73 |
Returns a `status` redirect to the new URL.
|
om@3
|
74 |
`url` is joined with the base URL so that things like
|
om@3
|
75 |
`redirect("about") will work properly.
|
om@3
|
76 |
"""
|
om@3
|
77 |
newloc = urlparse.urljoin(ctx.path, url)
|
om@3
|
78 |
|
om@3
|
79 |
if newloc.startswith('/'):
|
om@3
|
80 |
if absolute:
|
om@3
|
81 |
home = ctx.realhome
|
om@3
|
82 |
else:
|
om@3
|
83 |
home = ctx.home
|
om@3
|
84 |
newloc = home + newloc
|
om@3
|
85 |
|
om@3
|
86 |
headers = {
|
om@3
|
87 |
'Content-Type': 'text/html',
|
om@3
|
88 |
'Location': newloc
|
om@3
|
89 |
}
|
om@3
|
90 |
HTTPError.__init__(self, status, headers, "")
|
om@3
|
91 |
|
om@3
|
92 |
redirect = Redirect
|
om@3
|
93 |
|
om@3
|
94 |
class Found(Redirect):
|
om@3
|
95 |
"""A `302 Found` redirect."""
|
om@3
|
96 |
def __init__(self, url, absolute=False):
|
om@3
|
97 |
Redirect.__init__(self, url, '302 Found', absolute=absolute)
|
om@3
|
98 |
|
om@3
|
99 |
found = Found
|
om@3
|
100 |
|
om@3
|
101 |
class SeeOther(Redirect):
|
om@3
|
102 |
"""A `303 See Other` redirect."""
|
om@3
|
103 |
def __init__(self, url, absolute=False):
|
om@3
|
104 |
Redirect.__init__(self, url, '303 See Other', absolute=absolute)
|
om@3
|
105 |
|
om@3
|
106 |
seeother = SeeOther
|
om@3
|
107 |
|
om@3
|
108 |
class NotModified(HTTPError):
|
om@3
|
109 |
"""A `304 Not Modified` status."""
|
om@3
|
110 |
def __init__(self):
|
om@3
|
111 |
HTTPError.__init__(self, "304 Not Modified")
|
om@3
|
112 |
|
om@3
|
113 |
notmodified = NotModified
|
om@3
|
114 |
|
om@3
|
115 |
class TempRedirect(Redirect):
|
om@3
|
116 |
"""A `307 Temporary Redirect` redirect."""
|
om@3
|
117 |
def __init__(self, url, absolute=False):
|
om@3
|
118 |
Redirect.__init__(self, url, '307 Temporary Redirect', absolute=absolute)
|
om@3
|
119 |
|
om@3
|
120 |
tempredirect = TempRedirect
|
om@3
|
121 |
|
om@3
|
122 |
class BadRequest(HTTPError):
|
om@3
|
123 |
"""`400 Bad Request` error."""
|
om@3
|
124 |
message = "bad request"
|
om@3
|
125 |
def __init__(self, message=None):
|
om@3
|
126 |
status = "400 Bad Request"
|
om@3
|
127 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
128 |
HTTPError.__init__(self, status, headers, message or self.message)
|
om@3
|
129 |
|
om@3
|
130 |
badrequest = BadRequest
|
om@3
|
131 |
|
om@3
|
132 |
class Unauthorized(HTTPError):
|
om@3
|
133 |
"""`401 Unauthorized` error."""
|
om@3
|
134 |
message = "unauthorized"
|
om@3
|
135 |
def __init__(self):
|
om@3
|
136 |
status = "401 Unauthorized"
|
om@3
|
137 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
138 |
HTTPError.__init__(self, status, headers, self.message)
|
om@3
|
139 |
|
om@3
|
140 |
unauthorized = Unauthorized
|
om@3
|
141 |
|
om@3
|
142 |
class Forbidden(HTTPError):
|
om@3
|
143 |
"""`403 Forbidden` error."""
|
om@3
|
144 |
message = "forbidden"
|
om@3
|
145 |
def __init__(self):
|
om@3
|
146 |
status = "403 Forbidden"
|
om@3
|
147 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
148 |
HTTPError.__init__(self, status, headers, self.message)
|
om@3
|
149 |
|
om@3
|
150 |
forbidden = Forbidden
|
om@3
|
151 |
|
om@3
|
152 |
class _NotFound(HTTPError):
|
om@3
|
153 |
"""`404 Not Found` error."""
|
om@3
|
154 |
message = "not found"
|
om@3
|
155 |
def __init__(self, message=None):
|
om@3
|
156 |
status = '404 Not Found'
|
om@3
|
157 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
158 |
HTTPError.__init__(self, status, headers, message or self.message)
|
om@3
|
159 |
|
om@3
|
160 |
def NotFound(message=None):
|
om@3
|
161 |
"""Returns HTTPError with '404 Not Found' error from the active application.
|
om@3
|
162 |
"""
|
om@3
|
163 |
if message:
|
om@3
|
164 |
return _NotFound(message)
|
om@3
|
165 |
elif ctx.get('app_stack'):
|
om@3
|
166 |
return ctx.app_stack[-1].notfound()
|
om@3
|
167 |
else:
|
om@3
|
168 |
return _NotFound()
|
om@3
|
169 |
|
om@3
|
170 |
notfound = NotFound
|
om@3
|
171 |
|
om@3
|
172 |
class NoMethod(HTTPError):
|
om@3
|
173 |
"""A `405 Method Not Allowed` error."""
|
om@3
|
174 |
def __init__(self, cls=None):
|
om@3
|
175 |
status = '405 Method Not Allowed'
|
om@3
|
176 |
headers = {}
|
om@3
|
177 |
headers['Content-Type'] = 'text/html'
|
om@3
|
178 |
|
om@3
|
179 |
methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']
|
om@3
|
180 |
if cls:
|
om@3
|
181 |
methods = [method for method in methods if hasattr(cls, method)]
|
om@3
|
182 |
|
om@3
|
183 |
headers['Allow'] = ', '.join(methods)
|
om@3
|
184 |
data = None
|
om@3
|
185 |
HTTPError.__init__(self, status, headers, data)
|
om@3
|
186 |
|
om@3
|
187 |
nomethod = NoMethod
|
om@3
|
188 |
|
om@3
|
189 |
class NotAcceptable(HTTPError):
|
om@3
|
190 |
"""`406 Not Acceptable` error."""
|
om@3
|
191 |
message = "not acceptable"
|
om@3
|
192 |
def __init__(self):
|
om@3
|
193 |
status = "406 Not Acceptable"
|
om@3
|
194 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
195 |
HTTPError.__init__(self, status, headers, self.message)
|
om@3
|
196 |
|
om@3
|
197 |
notacceptable = NotAcceptable
|
om@3
|
198 |
|
om@3
|
199 |
class Conflict(HTTPError):
|
om@3
|
200 |
"""`409 Conflict` error."""
|
om@3
|
201 |
message = "conflict"
|
om@3
|
202 |
def __init__(self):
|
om@3
|
203 |
status = "409 Conflict"
|
om@3
|
204 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
205 |
HTTPError.__init__(self, status, headers, self.message)
|
om@3
|
206 |
|
om@3
|
207 |
conflict = Conflict
|
om@3
|
208 |
|
om@3
|
209 |
class Gone(HTTPError):
|
om@3
|
210 |
"""`410 Gone` error."""
|
om@3
|
211 |
message = "gone"
|
om@3
|
212 |
def __init__(self):
|
om@3
|
213 |
status = '410 Gone'
|
om@3
|
214 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
215 |
HTTPError.__init__(self, status, headers, self.message)
|
om@3
|
216 |
|
om@3
|
217 |
gone = Gone
|
om@3
|
218 |
|
om@3
|
219 |
class PreconditionFailed(HTTPError):
|
om@3
|
220 |
"""`412 Precondition Failed` error."""
|
om@3
|
221 |
message = "precondition failed"
|
om@3
|
222 |
def __init__(self):
|
om@3
|
223 |
status = "412 Precondition Failed"
|
om@3
|
224 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
225 |
HTTPError.__init__(self, status, headers, self.message)
|
om@3
|
226 |
|
om@3
|
227 |
preconditionfailed = PreconditionFailed
|
om@3
|
228 |
|
om@3
|
229 |
class UnsupportedMediaType(HTTPError):
|
om@3
|
230 |
"""`415 Unsupported Media Type` error."""
|
om@3
|
231 |
message = "unsupported media type"
|
om@3
|
232 |
def __init__(self):
|
om@3
|
233 |
status = "415 Unsupported Media Type"
|
om@3
|
234 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
235 |
HTTPError.__init__(self, status, headers, self.message)
|
om@3
|
236 |
|
om@3
|
237 |
unsupportedmediatype = UnsupportedMediaType
|
om@3
|
238 |
|
om@3
|
239 |
class _InternalError(HTTPError):
|
om@3
|
240 |
"""500 Internal Server Error`."""
|
om@3
|
241 |
message = "internal server error"
|
om@3
|
242 |
|
om@3
|
243 |
def __init__(self, message=None):
|
om@3
|
244 |
status = '500 Internal Server Error'
|
om@3
|
245 |
headers = {'Content-Type': 'text/html'}
|
om@3
|
246 |
HTTPError.__init__(self, status, headers, message or self.message)
|
om@3
|
247 |
|
om@3
|
248 |
def InternalError(message=None):
|
om@3
|
249 |
"""Returns HTTPError with '500 internal error' error from the active application.
|
om@3
|
250 |
"""
|
om@3
|
251 |
if message:
|
om@3
|
252 |
return _InternalError(message)
|
om@3
|
253 |
elif ctx.get('app_stack'):
|
om@3
|
254 |
return ctx.app_stack[-1].internalerror()
|
om@3
|
255 |
else:
|
om@3
|
256 |
return _InternalError()
|
om@3
|
257 |
|
om@3
|
258 |
internalerror = InternalError
|
om@3
|
259 |
|
om@3
|
260 |
def header(hdr, value, unique=False):
|
om@3
|
261 |
"""
|
om@3
|
262 |
Adds the header `hdr: value` with the response.
|
om@3
|
263 |
|
om@3
|
264 |
If `unique` is True and a header with that name already exists,
|
om@3
|
265 |
it doesn't add a new one.
|
om@3
|
266 |
"""
|
om@3
|
267 |
hdr, value = safestr(hdr), safestr(value)
|
om@3
|
268 |
# protection against HTTP response splitting attack
|
om@3
|
269 |
if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value:
|
om@3
|
270 |
raise ValueError, 'invalid characters in header'
|
om@3
|
271 |
|
om@3
|
272 |
if unique is True:
|
om@3
|
273 |
for h, v in ctx.headers:
|
om@3
|
274 |
if h.lower() == hdr.lower(): return
|
om@3
|
275 |
|
om@3
|
276 |
ctx.headers.append((hdr, value))
|
om@3
|
277 |
|
om@3
|
278 |
def rawinput(method=None):
|
om@3
|
279 |
"""Returns storage object with GET or POST arguments.
|
om@3
|
280 |
"""
|
om@3
|
281 |
method = method or "both"
|
om@3
|
282 |
from cStringIO import StringIO
|
om@3
|
283 |
|
om@3
|
284 |
def dictify(fs):
|
om@3
|
285 |
# hack to make web.input work with enctype='text/plain.
|
om@3
|
286 |
if fs.list is None:
|
om@3
|
287 |
fs.list = []
|
om@3
|
288 |
|
om@3
|
289 |
return dict([(k, fs[k]) for k in fs.keys()])
|
om@3
|
290 |
|
om@3
|
291 |
e = ctx.env.copy()
|
om@3
|
292 |
a = b = {}
|
om@3
|
293 |
|
om@3
|
294 |
if method.lower() in ['both', 'post', 'put']:
|
om@3
|
295 |
if e['REQUEST_METHOD'] in ['POST', 'PUT']:
|
om@3
|
296 |
if e.get('CONTENT_TYPE', '').lower().startswith('multipart/'):
|
om@3
|
297 |
# since wsgi.input is directly passed to cgi.FieldStorage,
|
om@3
|
298 |
# it can not be called multiple times. Saving the FieldStorage
|
om@3
|
299 |
# object in ctx to allow calling web.input multiple times.
|
om@3
|
300 |
a = ctx.get('_fieldstorage')
|
om@3
|
301 |
if not a:
|
om@3
|
302 |
fp = e['wsgi.input']
|
om@3
|
303 |
a = cgi.FieldStorage(fp=fp, environ=e, keep_blank_values=1)
|
om@3
|
304 |
ctx._fieldstorage = a
|
om@3
|
305 |
else:
|
om@3
|
306 |
fp = StringIO(data())
|
om@3
|
307 |
a = cgi.FieldStorage(fp=fp, environ=e, keep_blank_values=1)
|
om@3
|
308 |
a = dictify(a)
|
om@3
|
309 |
|
om@3
|
310 |
if method.lower() in ['both', 'get']:
|
om@3
|
311 |
e['REQUEST_METHOD'] = 'GET'
|
om@3
|
312 |
b = dictify(cgi.FieldStorage(environ=e, keep_blank_values=1))
|
om@3
|
313 |
|
om@3
|
314 |
def process_fieldstorage(fs):
|
om@3
|
315 |
if isinstance(fs, list):
|
om@3
|
316 |
return [process_fieldstorage(x) for x in fs]
|
om@3
|
317 |
elif fs.filename is None:
|
om@3
|
318 |
return fs.value
|
om@3
|
319 |
else:
|
om@3
|
320 |
return fs
|
om@3
|
321 |
|
om@3
|
322 |
return storage([(k, process_fieldstorage(v)) for k, v in dictadd(b, a).items()])
|
om@3
|
323 |
|
om@3
|
324 |
def input(*requireds, **defaults):
|
om@3
|
325 |
"""
|
om@3
|
326 |
Returns a `storage` object with the GET and POST arguments.
|
om@3
|
327 |
See `storify` for how `requireds` and `defaults` work.
|
om@3
|
328 |
"""
|
om@3
|
329 |
_method = defaults.pop('_method', 'both')
|
om@3
|
330 |
out = rawinput(_method)
|
om@3
|
331 |
try:
|
om@3
|
332 |
defaults.setdefault('_unicode', True) # force unicode conversion by default.
|
om@3
|
333 |
return storify(out, *requireds, **defaults)
|
om@3
|
334 |
except KeyError:
|
om@3
|
335 |
raise badrequest()
|
om@3
|
336 |
|
om@3
|
337 |
def data():
|
om@3
|
338 |
"""Returns the data sent with the request."""
|
om@3
|
339 |
if 'data' not in ctx:
|
om@3
|
340 |
cl = intget(ctx.env.get('CONTENT_LENGTH'), 0)
|
om@3
|
341 |
ctx.data = ctx.env['wsgi.input'].read(cl)
|
om@3
|
342 |
return ctx.data
|
om@3
|
343 |
|
om@3
|
344 |
def setcookie(name, value, expires='', domain=None,
|
om@3
|
345 |
secure=False, httponly=False, path=None):
|
om@3
|
346 |
"""Sets a cookie."""
|
om@3
|
347 |
morsel = Cookie.Morsel()
|
om@3
|
348 |
name, value = safestr(name), safestr(value)
|
om@3
|
349 |
morsel.set(name, value, urllib.quote(value))
|
om@3
|
350 |
if expires < 0:
|
om@3
|
351 |
expires = -1000000000
|
om@3
|
352 |
morsel['expires'] = expires
|
om@3
|
353 |
morsel['path'] = path or ctx.homepath+'/'
|
om@3
|
354 |
if domain:
|
om@3
|
355 |
morsel['domain'] = domain
|
om@3
|
356 |
if secure:
|
om@3
|
357 |
morsel['secure'] = secure
|
om@3
|
358 |
value = morsel.OutputString()
|
om@3
|
359 |
if httponly:
|
om@3
|
360 |
value += '; httponly'
|
om@3
|
361 |
header('Set-Cookie', value)
|
om@3
|
362 |
|
om@3
|
363 |
def decode_cookie(value):
|
om@3
|
364 |
r"""Safely decodes a cookie value to unicode.
|
om@3
|
365 |
|
om@3
|
366 |
Tries us-ascii, utf-8 and io8859 encodings, in that order.
|
om@3
|
367 |
|
om@3
|
368 |
>>> decode_cookie('')
|
om@3
|
369 |
u''
|
om@3
|
370 |
>>> decode_cookie('asdf')
|
om@3
|
371 |
u'asdf'
|
om@3
|
372 |
>>> decode_cookie('foo \xC3\xA9 bar')
|
om@3
|
373 |
u'foo \xe9 bar'
|
om@3
|
374 |
>>> decode_cookie('foo \xE9 bar')
|
om@3
|
375 |
u'foo \xe9 bar'
|
om@3
|
376 |
"""
|
om@3
|
377 |
try:
|
om@3
|
378 |
# First try plain ASCII encoding
|
om@3
|
379 |
return unicode(value, 'us-ascii')
|
om@3
|
380 |
except UnicodeError:
|
om@3
|
381 |
# Then try UTF-8, and if that fails, ISO8859
|
om@3
|
382 |
try:
|
om@3
|
383 |
return unicode(value, 'utf-8')
|
om@3
|
384 |
except UnicodeError:
|
om@3
|
385 |
return unicode(value, 'iso8859', 'ignore')
|
om@3
|
386 |
|
om@3
|
387 |
def parse_cookies(http_cookie):
|
om@3
|
388 |
r"""Parse a HTTP_COOKIE header and return dict of cookie names and decoded values.
|
om@3
|
389 |
|
om@3
|
390 |
>>> sorted(parse_cookies('').items())
|
om@3
|
391 |
[]
|
om@3
|
392 |
>>> sorted(parse_cookies('a=1').items())
|
om@3
|
393 |
[('a', '1')]
|
om@3
|
394 |
>>> sorted(parse_cookies('a=1%202').items())
|
om@3
|
395 |
[('a', '1 2')]
|
om@3
|
396 |
>>> sorted(parse_cookies('a=Z%C3%A9Z').items())
|
om@3
|
397 |
[('a', 'Z\xc3\xa9Z')]
|
om@3
|
398 |
>>> sorted(parse_cookies('a=1; b=2; c=3').items())
|
om@3
|
399 |
[('a', '1'), ('b', '2'), ('c', '3')]
|
om@3
|
400 |
>>> sorted(parse_cookies('a=1; b=w("x")|y=z; c=3').items())
|
om@3
|
401 |
[('a', '1'), ('b', 'w('), ('c', '3')]
|
om@3
|
402 |
>>> sorted(parse_cookies('a=1; b=w(%22x%22)|y=z; c=3').items())
|
om@3
|
403 |
[('a', '1'), ('b', 'w("x")|y=z'), ('c', '3')]
|
om@3
|
404 |
|
om@3
|
405 |
>>> sorted(parse_cookies('keebler=E=mc2').items())
|
om@3
|
406 |
[('keebler', 'E=mc2')]
|
om@3
|
407 |
>>> sorted(parse_cookies(r'keebler="E=mc2; L=\"Loves\"; fudge=\012;"').items())
|
om@3
|
408 |
[('keebler', 'E=mc2; L="Loves"; fudge=\n;')]
|
om@3
|
409 |
"""
|
om@3
|
410 |
#print "parse_cookies"
|
om@3
|
411 |
if '"' in http_cookie:
|
om@3
|
412 |
# HTTP_COOKIE has quotes in it, use slow but correct cookie parsing
|
om@3
|
413 |
cookie = Cookie.SimpleCookie()
|
om@3
|
414 |
try:
|
om@3
|
415 |
cookie.load(http_cookie)
|
om@3
|
416 |
except Cookie.CookieError:
|
om@3
|
417 |
# If HTTP_COOKIE header is malformed, try at least to load the cookies we can by
|
om@3
|
418 |
# first splitting on ';' and loading each attr=value pair separately
|
om@3
|
419 |
cookie = Cookie.SimpleCookie()
|
om@3
|
420 |
for attr_value in http_cookie.split(';'):
|
om@3
|
421 |
try:
|
om@3
|
422 |
cookie.load(attr_value)
|
om@3
|
423 |
except Cookie.CookieError:
|
om@3
|
424 |
pass
|
om@3
|
425 |
cookies = dict((k, urllib.unquote(v.value)) for k, v in cookie.iteritems())
|
om@3
|
426 |
else:
|
om@3
|
427 |
# HTTP_COOKIE doesn't have quotes, use fast cookie parsing
|
om@3
|
428 |
cookies = {}
|
om@3
|
429 |
for key_value in http_cookie.split(';'):
|
om@3
|
430 |
key_value = key_value.split('=', 1)
|
om@3
|
431 |
if len(key_value) == 2:
|
om@3
|
432 |
key, value = key_value
|
om@3
|
433 |
cookies[key.strip()] = urllib.unquote(value.strip())
|
om@3
|
434 |
return cookies
|
om@3
|
435 |
|
om@3
|
436 |
def cookies(*requireds, **defaults):
|
om@3
|
437 |
r"""Returns a `storage` object with all the request cookies in it.
|
om@3
|
438 |
|
om@3
|
439 |
See `storify` for how `requireds` and `defaults` work.
|
om@3
|
440 |
|
om@3
|
441 |
This is forgiving on bad HTTP_COOKIE input, it tries to parse at least
|
om@3
|
442 |
the cookies it can.
|
om@3
|
443 |
|
om@3
|
444 |
The values are converted to unicode if _unicode=True is passed.
|
om@3
|
445 |
"""
|
om@3
|
446 |
# If _unicode=True is specified, use decode_cookie to convert cookie value to unicode
|
om@3
|
447 |
if defaults.get("_unicode") is True:
|
om@3
|
448 |
defaults['_unicode'] = decode_cookie
|
om@3
|
449 |
|
om@3
|
450 |
# parse cookie string and cache the result for next time.
|
om@3
|
451 |
if '_parsed_cookies' not in ctx:
|
om@3
|
452 |
http_cookie = ctx.env.get("HTTP_COOKIE", "")
|
om@3
|
453 |
ctx._parsed_cookies = parse_cookies(http_cookie)
|
om@3
|
454 |
|
om@3
|
455 |
try:
|
om@3
|
456 |
return storify(ctx._parsed_cookies, *requireds, **defaults)
|
om@3
|
457 |
except KeyError:
|
om@3
|
458 |
badrequest()
|
om@3
|
459 |
raise StopIteration
|
om@3
|
460 |
|
om@3
|
461 |
def debug(*args):
|
om@3
|
462 |
"""
|
om@3
|
463 |
Prints a prettyprinted version of `args` to stderr.
|
om@3
|
464 |
"""
|
om@3
|
465 |
try:
|
om@3
|
466 |
out = ctx.environ['wsgi.errors']
|
om@3
|
467 |
except:
|
om@3
|
468 |
out = sys.stderr
|
om@3
|
469 |
for arg in args:
|
om@3
|
470 |
print >> out, pprint.pformat(arg)
|
om@3
|
471 |
return ''
|
om@3
|
472 |
|
om@3
|
473 |
def _debugwrite(x):
|
om@3
|
474 |
try:
|
om@3
|
475 |
out = ctx.environ['wsgi.errors']
|
om@3
|
476 |
except:
|
om@3
|
477 |
out = sys.stderr
|
om@3
|
478 |
out.write(x)
|
om@3
|
479 |
debug.write = _debugwrite
|
om@3
|
480 |
|
om@3
|
481 |
ctx = context = threadeddict()
|
om@3
|
482 |
|
om@3
|
483 |
ctx.__doc__ = """
|
om@3
|
484 |
A `storage` object containing various information about the request:
|
om@3
|
485 |
|
om@3
|
486 |
`environ` (aka `env`)
|
om@3
|
487 |
: A dictionary containing the standard WSGI environment variables.
|
om@3
|
488 |
|
om@3
|
489 |
`host`
|
om@3
|
490 |
: The domain (`Host` header) requested by the user.
|
om@3
|
491 |
|
om@3
|
492 |
`home`
|
om@3
|
493 |
: The base path for the application.
|
om@3
|
494 |
|
om@3
|
495 |
`ip`
|
om@3
|
496 |
: The IP address of the requester.
|
om@3
|
497 |
|
om@3
|
498 |
`method`
|
om@3
|
499 |
: The HTTP method used.
|
om@3
|
500 |
|
om@3
|
501 |
`path`
|
om@3
|
502 |
: The path request.
|
om@3
|
503 |
|
om@3
|
504 |
`query`
|
om@3
|
505 |
: If there are no query arguments, the empty string. Otherwise, a `?` followed
|
om@3
|
506 |
by the query string.
|
om@3
|
507 |
|
om@3
|
508 |
`fullpath`
|
om@3
|
509 |
: The full path requested, including query arguments (`== path + query`).
|
om@3
|
510 |
|
om@3
|
511 |
### Response Data
|
om@3
|
512 |
|
om@3
|
513 |
`status` (default: "200 OK")
|
om@3
|
514 |
: The status code to be used in the response.
|
om@3
|
515 |
|
om@3
|
516 |
`headers`
|
om@3
|
517 |
: A list of 2-tuples to be used in the response.
|
om@3
|
518 |
|
om@3
|
519 |
`output`
|
om@3
|
520 |
: A string to be used as the response.
|
om@3
|
521 |
"""
|
om@3
|
522 |
|
om@3
|
523 |
if __name__ == "__main__":
|
om@3
|
524 |
import doctest
|
om@3
|
525 |
doctest.testmod() |