Package wtf :: Package app :: Module request
[hide private]
[frames] | no frames]

Source Code for Module wtf.app.request

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2006-2012 
  4  # Andr\xe9 Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 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 
36 37 38 -class Uploads(object):
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
49 - def __init__(self, uploads):
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
61 - def __getitem__(self, name):
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
79 - def __iter__(self):
80 """ 81 Return an iterator over the regular keys 82 """ 83 return self._pairs.iterkeys()
84
85 - def __contains__(self, name):
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
100 - def keys(self):
101 """ 102 Determine the list of available keys 103 104 :return: The key list 105 :rtype: ``list`` 106 """ 107 return self._pairs.keys()
108
109 - def multi(self, name):
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
124 125 -class ParameterWrapper(object):
126 """ 127 Wrapper around cgi.FieldStorage 128 129 This wrapper provides a better interface and unicode awareness 130 """ 131
132 - def __init__(self, request):
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
177 - def encoding():
178 """ 179 Assumed encoding of the request 180 181 :Type: ``str`` 182 """ 183 # pylint: disable = E0211, C0111, W0212, W0612 184 185 def fget(self): 186 return self._encoding
187 return locals()
188 189 @Property
190 - def uploads():
191 """ 192 Upload container 193 194 :Type: `Uploads` 195 """ 196 # pylint: disable = E0211, C0111, W0212, W0612 197 198 def fget(self): 199 return self._uploads
200 return locals() 201
202 - def __setitem__(self, name, value):
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
225 - def __getitem__(self, name):
226 """ Return a single value for that name """ 227 return (self._pairs.get(name) or [u''])[0]
228
229 - def __iter__(self):
230 """ Return an iterator over the regular keys """ 231 return self._pairs.iterkeys()
232
233 - def __contains__(self, name):
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
249 - def keys(self):
250 """ 251 Determine the list of available keys 252 253 :return: The key list 254 :rtype: ``list`` 255 """ 256 return self._pairs.keys()
257
258 - def multi(self, name):
259 """ Return a list of all values under that name """ 260 return tuple(self._pairs.get(name, ()))
261 262 @staticmethod
263 - def _determine_encoding(store):
264 """ Guess encoding of the request parameters """ 265 # try simple method first... 266 encoding = store.getfirst('_charset_') 267 if not encoding: 268 # peek is assumed to be '\xe4', i.e. ä 269 encoding = { 270 '\xc3\xa4': 'utf-8', 271 None : 'utf-8', 272 '' : 'utf-8', 273 '\x84' : 'cp437', # default lynx on dos 274 }.get(store.getfirst('_peek_'), 'cp1252') 275 encoding = _encodings.normalize_encoding(encoding) 276 277 # fix known browser bug, but it doesn't exactly hurt: 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 # doh! 285 encoding = 'cp1252' 286 return encoding
287
288 289 -class CookieCollection(object):
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 #: Split iterator 301 #: 302 #: :Type: ``callable`` 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 # ignore the unreadable 339 self._cookies = cookies 340 self._cookiestring = cookiestring
341
342 - def __call__(self, codec):
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
357 - def __getitem__(self, name):
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
375 - def __iter__(self):
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
384 - def __contains__(self, name):
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
400 - def keys(self):
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
409 - def multi(self, name):
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
424 425 -class Request(object):
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
451 - def __init__(self, environ):
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
469 - def __getattr__(self, name):
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
500 - def param():
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 # pylint: disable = E0211, C0111, W0212, W0612 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
518 - def cookie():
519 """ 520 The cookies 521 522 This property is lazily initialized on first request. 523 524 :Type: `CookieCollection` 525 """ 526 # pylint: disable = E0211, C0111, W0212, W0612 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
539 - def basic_auth():
540 """ 541 Credentials of HTTP basic authentication 542 543 (username, password) tuple or (None, None) 544 545 :Type: ``tuple`` 546 """ 547 # pylint: disable = E0211, C0111, W0612 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
569 - def is_ssl():
570 """ 571 Is the request is SSL enabled? 572 573 :Type: ``bool`` 574 """ 575 # pylint: disable = E0211, C0111, W0612 576 577 def fget(self): 578 return self.env['wsgi.url_scheme'] == 'https'
579 return locals() 580 581 @Property
582 - def method():
583 """ 584 The request method 585 586 :Type: ``str`` 587 """ 588 # pylint: disable = E0211, C0111, W0612 589 590 def fget(self): 591 return self.env['REQUEST_METHOD']
592 return locals() 593
594 - def remote_addr(self, check_proxy=False):
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