1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Request object
19 ==============
20
21 This module implements a request object.
22 """
23 __author__ = u"Andr\xe9 Malo"
24 __docformat__ = "restructuredtext en"
25
26 import binascii as _binascii
27 import codecs as _codecs
28 import cgi as _cgi
29 import encodings as _encodings
30 import re as _re
31 import urlparse as _urlparse
32 import weakref as _weakref
33
34 from wtf import webutil as _webutil
35 from wtf.util import Property
39 """
40 Container for uploaded files
41
42 :IVariables:
43 - `_pairs`: Dict of form names and file objects
44
45 :Types:
46 - `_pairs`: ``dict``
47 """
48
50 """
51 Initialization
52
53 :Parameters:
54 - `uploads`: Dict of form names and file objects
55
56 :Types:
57 - `uploads`: ``dict``
58 """
59 self._pairs = uploads
60
62 """
63 Return a single value for that name
64
65 :Parameters:
66 - `name`: The form name
67
68 :Types:
69 - `name`: ``str``
70
71 :return: File object
72 :rtype: ``file``
73
74 :Exceptions:
75 - `KeyError`: The name does not exist
76 """
77 return self._pairs[name][0]
78
80 """
81 Return an iterator over the regular keys
82 """
83 return self._pairs.iterkeys()
84
86 """
87 Determines whether a certain key exists
88
89 :Parameters:
90 - `name`: The key to check
91
92 :Types:
93 - `name`: ``str``
94
95 :return: Does the key exist?
96 :rtype: ``bool``
97 """
98 return name in self._pairs
99
101 """
102 Determine the list of available keys
103
104 :return: The key list
105 :rtype: ``list``
106 """
107 return self._pairs.keys()
108
110 """
111 Return a list of all values under that name
112
113 :Parameters:
114 - `name`: The name to look up
115
116 :Types:
117 - `name`: ``str``
118
119 :return: List of files belonging to this key
120 :rtype: ``tuple``
121 """
122 return tuple(self._pairs.get(name, ()))
123
126 """
127 Wrapper around cgi.FieldStorage
128
129 This wrapper provides a better interface and unicode awareness
130 """
131
133 """
134 Initialization
135
136 :Parameters:
137 - `request`: request object
138
139 :Types:
140 - `request`: `Request`
141 """
142 env = request.env
143 try:
144 store = _cgi.FieldStorage(
145 fp=env['wsgi.input'], environ=env, keep_blank_values=True
146 )
147 if store.list is None:
148 raise ValueError()
149 except ValueError:
150 self._encoding = 'utf-8'
151 self._uploads = Uploads({})
152 self._pairs = {}
153 else:
154 encoding = self._determine_encoding(store)
155
156 uploads = {}
157 regular = {}
158 for key in store:
159 values = store[key]
160 if type(values) is not list:
161 values = [values]
162 for value in values:
163 if value.filename:
164 uploads.setdefault(key, []).append(value)
165 else:
166 try:
167 value = value.value.decode(encoding)
168 except UnicodeError:
169 value = value.value.decode('cp1252')
170 regular.setdefault(key, []).append(value)
171
172 self._encoding = encoding
173 self._uploads = Uploads(uploads)
174 self._pairs = regular
175
176 @Property
178 """
179 Assumed encoding of the request
180
181 :Type: ``str``
182 """
183
184
185 def fget(self):
186 return self._encoding
187 return locals()
188
189 @Property
191 """
192 Upload container
193
194 :Type: `Uploads`
195 """
196
197
198 def fget(self):
199 return self._uploads
200 return locals()
201
203 """
204 Set a single item value
205
206 :Parameters:
207 - `name`: The name to set
208 - `value`: The value to assign
209
210 :Types:
211 - `name`: ``str``
212 - `value`: ``unicode``
213 """
214 try:
215 iter(value)
216 except TypeError:
217 value = [unicode(value)]
218 else:
219 if isinstance(value, basestring):
220 value = [unicode(value)]
221 else:
222 value = map(unicode, value)
223 self._pairs[name] = value
224
226 """ Return a single value for that name """
227 return (self._pairs.get(name) or [u''])[0]
228
230 """ Return an iterator over the regular keys """
231 return self._pairs.iterkeys()
232
234 """
235 Determines whether a certain key exists
236
237 :Parameters:
238 - `name`: The key to check
239
240 :Types:
241 - `name`: ``str``
242
243 :return: Does the key exist?
244 :rtype: ``bool``
245 """
246 return name in self._pairs
247 has_key = __contains__
248
250 """
251 Determine the list of available keys
252
253 :return: The key list
254 :rtype: ``list``
255 """
256 return self._pairs.keys()
257
259 """ Return a list of all values under that name """
260 return tuple(self._pairs.get(name, ()))
261
262 @staticmethod
264 """ Guess encoding of the request parameters """
265
266 encoding = store.getfirst('_charset_')
267 if not encoding:
268
269 encoding = {
270 '\xc3\xa4': 'utf-8',
271 None : 'utf-8',
272 '' : 'utf-8',
273 '\x84' : 'cp437',
274 }.get(store.getfirst('_peek_'), 'cp1252')
275 encoding = _encodings.normalize_encoding(encoding)
276
277
278 if encoding.replace('_', '').decode('latin-1').lower() == u'iso88591':
279 encoding = 'cp1252'
280 else:
281 try:
282 _codecs.lookup(encoding)
283 except LookupError:
284
285 encoding = 'cp1252'
286 return encoding
287
290 """
291 Cookies parsed out of the request
292
293 :IVariables:
294 `_cookies` : ``dict``
295 Cookie mapping (``{'name': ['value', ...], ...}``)
296
297 `_cookiestring` : ``str``
298 Initial cookie string
299 """
300
301
302
303 _SPLIT_ITER = _re.compile(r"""(
304 (?:
305 \s*
306 (?P<key> [^"=\s;,]+ )
307 (?:
308 \s* = \s*
309 (?P<val> " [^\\"]* (?:\\. [^\\"]* )* " | [^",;\s]+ )
310 )?
311 )+
312 )""", _re.X).finditer
313
314 - def __init__(self, cookiestring, codec=None):
315 """
316 Initialization
317
318 :Parameters:
319 - `cookiestring`: Cookie string out of the request
320 - `codec`: Cookie en-/decoder; if ``None``, an identity decoder
321 is used.
322
323 :Types:
324 - `cookiestring`: ``str``
325 - `codec`: `CookieCodecInterface`
326 """
327 pairs, cookies = [item.group('key', 'val') for item in
328 self._SPLIT_ITER(cookiestring)], {}
329 if codec is None:
330 decode = lambda x: x
331 else:
332 decode = codec.decode
333 for key, value in pairs:
334 if value is not None and not key.startswith('$'):
335 try:
336 cookies.setdefault(key, []).append(decode(value))
337 except ValueError:
338 continue
339 self._cookies = cookies
340 self._cookiestring = cookiestring
341
343 """
344 Create a cookie collection with a particular codec
345
346 :Parameters:
347 - `codec`: The codec to aplpy
348
349 :Types:
350 - `codec`: `CookieCodecInterface`
351
352 :return: New CookieCollection instance
353 :rtype: `CookieCollection`
354 """
355 return self.__class__(self._cookiestring, codec)
356
358 """
359 Determine the value of a cookie
360
361 :Parameters:
362 - `name`: The cookie name
363
364 :Types:
365 - `name`: ``str``
366
367 :return: The cookie value
368 :rtype: ``unicode``
369
370 :Exceptions:
371 - `KeyError`: The cookie name does not exist
372 """
373 return self._cookies[name][0]
374
376 """
377 Create an iterator over the available cookie names
378
379 :return: Iterator over the names
380 :rtype: ``iterable``
381 """
382 return self._cookies.iterkeys()
383
385 """
386 Determine whether a particular cookie name was submitted
387
388 :Parameters:
389 - `name`: The cookie name to look up
390
391 :Types:
392 - `name`: ``str``
393
394 :return: Is the name available?
395 :rtype: ``bool``
396 """
397 return name in self._cookies
398 has_key = __contains__
399
401 """
402 Determine the list of all available cookie names
403
404 :return: The cookie name list (``['name', ...]``)
405 :rtype: ``list``
406 """
407 return self._cookies.keys()
408
410 """
411 Determine a list of all cookies under that name
412
413 :Parameters:
414 - `name`: Name of the cookie(s)
415
416 :Types:
417 - `name`: ``str``
418
419 :return: Tuple of cookie values (``(u'value', ...)``); maybe empty
420 :rtype: ``tuple``
421 """
422 return tuple(self._cookies.get(name, ()))
423
426 """
427 Request object passed to the application
428
429 :CVariables:
430 - `_PORTS`: Default port mapping accessor for http and https
431 - `_PORTMATCH`: Host:port matching function
432
433 :IVariables:
434 - `_param`: Request parameter store
435 - `env`: WSGI environment
436 - `match`: URL regex match or ``None``, filled from outside
437
438 :Types:
439 - `_PORTS`: ``callable``
440 - `_PORTMATCH`: ``callable``
441 - `_param`: `ParameterWrapper`
442 - `env`: ``dict``
443 - `match`: regex match
444 """
445 _param, _cookie, match, _PORTS = None, None, None, {
446 'http': 80,
447 'https': 443,
448 }.get
449 _PORTMATCH = _re.compile(r'(?P<host>.+):(?P<port>\d+)$').match
450
452 """
453 Initialization
454
455 :Parameters:
456 - `environ`: WSGI environment
457
458 :Types:
459 - `environ`: ``dict``
460 """
461 self.env = environ
462 self.url = self.abs_uri(_webutil.URL.fromcomponents(_urlparse.urljoin(
463 '/',
464 ''.join((environ['SCRIPT_NAME'], environ.get('PATH_INFO', '')))
465 ), netloc=environ.get('HTTP_HOST'),
466 query=_webutil.Query(environ.get('QUERY_STRING', ''))
467 ))
468
470 """
471 Resolve unknown attributes
472
473 We're looking for special env variables inserted by the middleware
474 stack: ``wtf.request.<name>``. These are expected to be factories,
475 which are lazily initialized with the request object and return
476 the actual attribute, which is cached in the request object for
477 further use.
478
479 :Parameters:
480 - `name`: The name to look up
481
482 :Types:
483 - `name`: ``str``
484
485 :return: The attribute in question
486 :rtype: any
487
488 :Exceptions:
489 - `AttributeError`: Attribute could not be resolved
490 """
491 try:
492 factory = self.env['wtf.request.%s' % name]
493 except KeyError:
494 pass
495 else:
496 setattr(self, name, factory(_weakref.proxy(self)))
497 return super(Request, self).__getattribute__(name)
498
499 @Property
501 """
502 The request parameters (Query string or POST data)
503
504 This property is lazily initialized on first request.
505
506 :Type: `ParameterWrapper`
507 """
508
509
510 def fget(self):
511 result = self._param
512 if result is None:
513 result = self._param = ParameterWrapper(self)
514 return result
515 return locals()
516
517 @Property
519 """
520 The cookies
521
522 This property is lazily initialized on first request.
523
524 :Type: `CookieCollection`
525 """
526
527
528 def fget(self):
529 result = self._cookie
530 if result is None:
531 result = self._cookie = CookieCollection(
532 self.env.get('HTTP_COOKIE', ''),
533 self.env.get('wtf.codec.cookie'),
534 )
535 return result
536 return locals()
537
538 @Property
540 """
541 Credentials of HTTP basic authentication
542
543 (username, password) tuple or (None, None)
544
545 :Type: ``tuple``
546 """
547
548
549 def fget(self):
550 header = self.env.get('HTTP_AUTHORIZATION')
551 if header is None:
552 header = self.env.get('Authorization')
553 if header is not None:
554 header = header.strip().split()
555 if len(header) == 2 and header[0].lower() == 'basic':
556 try:
557 header = header[1].decode('base64')
558 except (ValueError, _binascii.Error):
559 pass
560 else:
561 header = header.split(':', 1)
562 if len(header) == 1:
563 header = (header, None)
564 return header
565 return None, None
566 return locals()
567
568 @Property
570 """
571 Is the request is SSL enabled?
572
573 :Type: ``bool``
574 """
575
576
577 def fget(self):
578 return self.env['wsgi.url_scheme'] == 'https'
579 return locals()
580
581 @Property
583 """
584 The request method
585
586 :Type: ``str``
587 """
588
589
590 def fget(self):
591 return self.env['REQUEST_METHOD']
592 return locals()
593
595 """
596 Determine the remote address
597
598 :Parameters:
599 - `check_proxy`: Check for ``X-Forwarded-For``?
600
601 :Types:
602 - `check_proxy`: ``bool``
603
604 :return: The remote address
605 :rtype: ``str``
606 """
607 addr = self.env['REMOTE_ADDR']
608 if check_proxy:
609 addr = self.env.get('HTTP_X_FORWARDED_FOR', addr
610 ).split(',')[-1].strip()
611 return addr
612
613 - def abs_uri(self, url, decode=None):
614 """
615 Determine absolute URI out of a (possibly) path only one
616
617 :Parameters:
618 - `url`: URL to expand
619
620 :Types:
621 - `url`: ``basestring`` or `wtf.webutil.URL`
622
623 :return: The expanded URL
624 :rtype: `wtf.webutil.URL`
625 """
626 env = self.env
627 parsed = _webutil.URL(url, decode=decode)
628 if not parsed.scheme:
629 parsed.scheme = env['wsgi.url_scheme']
630 if not parsed.netloc:
631 parsed.netloc = env['SERVER_NAME']
632 match = self._PORTMATCH(parsed.netloc)
633 if not match and \
634 env['SERVER_PORT'] != str(self._PORTS(parsed.scheme)):
635 parsed.netloc = "%s:%s" % (parsed.netloc, env['SERVER_PORT'])
636 else:
637 match = self._PORTMATCH(parsed.netloc)
638 if match and \
639 match.group('port') == str(self._PORTS(parsed.scheme)):
640 parsed.netloc = match.group('host')
641
642 return parsed
643