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