web2py.gluon.restricted

Package web2py :: Package gluon :: Module restricted |
[hide private]
[frames] | no frames]

# Source Code for Module web2py.gluon.restricted

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3
      4  """
      5  This file is part of the web2py Web Framework
      6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
      7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
      8  """
      9
     10  import sys
     11  import cPickle
     12  import traceback
     13  import types
     14  import os
     15  import logging
     16
     17  from storage import Storage
     18  from http import HTTP
     19  from html import BEAUTIFY
     20
     21  logger = logging.getLogger("web2py")
     22
     23  __all__ = ['RestrictedError', 'restricted', 'TicketStorage', 'compile2']
     24
     25 -class TicketStorage(Storage):
     26
     27      """
     28      defines the ticket object and the default values of its members (None)
     29      """
     30
     31 -    def __init__(
     32          self,
     33          db=None,
     34          tablename='web2py_ticket'
     35          ):
     36          self.db = db
     37          self.tablename = tablename
     38
     39 -    def store(self, request, ticket_id, ticket_data):
     40          """
     41          stores the ticket. It will figure out if this must be on disk or in db
     42          """
     43          if self.db:
     44              self._store_in_db(request, ticket_id, ticket_data)
     45          else:
     46              self._store_on_disk(request, ticket_id, ticket_data)
     47
     48 -    def _store_in_db(self, request, ticket_id, ticket_data):
     49          table = self._get_table(self.db, self.tablename, request.application)
     50          table.insert(ticket_id=ticket_id,
     51                       ticket_data=cPickle.dumps(ticket_data),
     52                       created_datetime=request.now)
     53          logger.error('In FILE: %(layer)s\n\n%(traceback)s\n' % ticket_data)
     54
     55 -    def _store_on_disk(self, request, ticket_id, ticket_data):
     56          ef = self._error_file(request, ticket_id, 'wb')
     57          try:
     58              cPickle.dump(ticket_data, ef)
     59          finally:
     60              ef.close()
     61
     62 -    def _error_file(self, request, ticket_id, mode, app=None):
     63          root = request.folder
     64          if app:
     65              root = os.path.join(os.path.join(root, '..'), app)
     66          errors_folder = os.path.abspath(os.path.join(root, 'errors'))#.replace('\\', '/')
     67          return open(os.path.join(errors_folder, ticket_id), mode)
     68
     69 -    def _get_table(self, db, tablename, app):
     70          tablename = tablename + '_' + app
     71          table = db.get(tablename, None)
     72          if table is None:
     73              db.rollback()   # not necessary but one day
     74                              # any app may store tickets on DB
     75              table = db.define_table(
     76                  tablename,
     77                  db.Field('ticket_id', length=100),
     78                  db.Field('ticket_data', 'text'),
     79                  db.Field('created_datetime', 'datetime'),
     80                  )
     81          return table
     82
     83 -    def load(
     84          self,
     85          request,
     86          app,
     87          ticket_id,
     88          ):
     89          if not self.db:
     90              ef = self._error_file(request, ticket_id, 'rb', app)
     91              try:
     92                  return cPickle.load(ef)
     93              finally:
     94                  ef.close()
     95          table = self._get_table(self.db, self.tablename, app)
     96          rows = self.db(table.ticket_id == ticket_id).select()
     97          if rows:
     98              return cPickle.loads(rows[0].ticket_data)
     99          return None
    100
    101
    102 -class RestrictedError(Exception):
    103      """
    104      class used to wrap an exception that occurs in the restricted environment
    105      below. the traceback is used to log the exception and generate a ticket.
    106      """
    107
    108 -    def __init__(
    109          self,
    110          layer='',
    111          code='',
    112          output='',
    113          environment=None,
    114          ):
    115          """
    116          layer here is some description of where in the system the exception
    117          occurred.
    118          """
    119          if environment is None: environment = {}
    120          self.layer = layer
    121          self.code = code
    122          self.output = output
    123          self.environment = environment
    124          if layer:
    125              try:
    126                  self.traceback = traceback.format_exc()
    127              except:
    128                  self.traceback = 'no traceback because template parting error'
    129              try:
    130                  self.snapshot = snapshot(context=10,code=code,
    131                                           environment=self.environment)
    132              except:
    133                  self.snapshot = {}
    134          else:
    135              self.traceback = '(no error)'
    136              self.snapshot = {}
    137
    138 -    def log(self, request):
    139          """
    140          logs the exception.
    141          """
    142
    143          try:
    144              d = {
    145                  'layer': str(self.layer),
    146                  'code': str(self.code),
    147                  'output': str(self.output),
    148                  'traceback': str(self.traceback),
    149                  'snapshot': self.snapshot,
    150                  }
    151              ticket_storage = TicketStorage(db=request.tickets_db)
    152              ticket_storage.store(request, request.uuid.split('/',1)[1], d)
    153              return request.uuid
    154          except:
    155              logger.error(self.traceback)
    156              return None
    157
    158
    159 -    def load(self, request, app, ticket_id):
    160          """
    161          loads a logged exception.
    162          """
    163          ticket_storage = TicketStorage(db=request.tickets_db)
    164          d = ticket_storage.load(request, app, ticket_id)
    165
    166          self.layer = d['layer']
    167          self.code = d['code']
    168          self.output = d['output']
    169          self.traceback = d['traceback']
    170          self.snapshot = d.get('snapshot')
    171
    172 -    def __str__(self):
    173          # safely show an useful message to the user
    174          try:
    175              output = self.output
    176              if isinstance(output, unicode):
    177                  output = output.encode("utf8")
    178              elif not isinstance(output, str):
    179                  output = str(output)
    180          except:
    181              output = ""
    182          return output
    183
    184
    185 -def compile2(code,layer):
    186      """
    187      The +'\n' is necessary else compile fails when code ends in a comment.
    188      """
    189      return compile(code.rstrip().replace('\r\n','\n')+'\n', layer, 'exec')
    190
    191 -def restricted(code, environment=None, layer='Unknown'):
    192      """
    193      runs code in environment and returns the output. if an exception occurs
    194      in code it raises a RestrictedError containing the traceback. layer is
    195      passed to RestrictedError to identify where the error occurred.
    196      """
    197      if environment is None: environment = {}
    198      environment['__file__'] = layer
    199      environment['__name__'] = '__restricted__'
    200      try:
    201          if type(code) == types.CodeType:
    202              ccode = code
    203          else:
    204              ccode = compile2(code,layer)
    205          exec ccode in environment
    206      except HTTP:
    207          raise
    208      except RestrictedError:
    209          # do not encapsulate (obfuscate) the original RestrictedError
    210          raise
    211      except Exception, error:
    212          # extract the exception type and value (used as output message)
    213          etype, evalue, tb = sys.exc_info()
    214          # XXX Show exception in Wing IDE if running in debugger
    215          if __debug__ and 'WINGDB_ACTIVE' in os.environ:
    216              sys.excepthook(etype, evalue, tb)
    217          output = "%s %s" % (etype, evalue)
    218          raise RestrictedError(layer, code, output, environment)
    219
    220 -def snapshot(info=None, context=5, code=None, environment=None):
    221      """Return a dict describing a given traceback (based on cgitb.text)."""
    222      import os, types, time, linecache, inspect, pydoc, cgitb
    223
    224      # if no exception info given, get current:
    225      etype, evalue, etb = info or sys.exc_info()
    226
    227      if type(etype) is types.ClassType:
    228          etype = etype.__name__
    229
    230      # create a snapshot dict with some basic information
    231      s = {}
    232      s['pyver'] = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
    233      s['date'] = time.ctime(time.time())
    234
    235      # start to process frames
    236      records = inspect.getinnerframes(etb, context)
    237      s['frames'] = []
    238      for frame, file, lnum, func, lines, index in records:
    239          file = file and os.path.abspath(file) or '?'
    240          args, varargs, varkw, locals = inspect.getargvalues(frame)
    241          call = ''
    242          if func != '?':
    243              call = inspect.formatargvalues(args, varargs, varkw, locals,
    244                      formatvalue=lambda value: '=' + pydoc.text.repr(value))
    245
    246          # basic frame information
    247          f = {'file': file, 'func': func, 'call': call, 'lines': {}, 'lnum': lnum}
    248
    249          highlight = {}
    250          def reader(lnum=[lnum]):
    251              highlight[lnum[0]] = 1
    252              try: return linecache.getline(file, lnum[0])
    253              finally: lnum[0] += 1
    254          vars = cgitb.scanvars(reader, frame, locals)
    255
    256          # if it is a view, replace with generated code
    257          if file.endswith('html'):
    258              lmin = lnum>context and (lnum-context) or 0
    259              lmax = lnum+context
    260              lines = code.split("\n")[lmin:lmax]
    261              index = min(context, lnum) - 1
    262
    263          if index is not None:
    264              i = lnum - index
    265              for line in lines:
    266                  f['lines'][i] = line.rstrip()
    267                  i += 1
    268
    269          # dump local variables (referenced in current line only)
    270          f['dump'] = {}
    271          for name, where, value in vars:
    272              if name in f['dump']: continue
    273              if value is not cgitb.__UNDEF__:
    274                  if where == 'global': name = 'global ' + name
    275                  elif where != 'local': name = where + name.split('.')[-1]
    276                  f['dump'][name] = pydoc.text.repr(value)
    277              else:
    278                  f['dump'][name] = 'undefined'
    279
    280          s['frames'].append(f)
    281
    282      # add exception type, value and attributes
    283      s['etype'] = str(etype)
    284      s['evalue'] = str(evalue)
    285      s['exception'] = {}
    286      if isinstance(evalue, BaseException):
    287          for name in dir(evalue):
    288              # prevent py26 DeprecatedWarning:
    289              if name!='message' or sys.version_info<(2.6):
    290                  value = pydoc.text.repr(getattr(evalue, name))
    291                  s['exception'][name] = value
    292
    293      # add all local values (of last frame) to the snapshot
    294      s['locals'] = {}
    295      for name, value in locals.items():
    296          s['locals'][name] = pydoc.text.repr(value)
    297
    298      # add web2py environment variables
    299      for k,v in environment.items():
    300          if k in ('request', 'response', 'session'):
    301              s[k] = BEAUTIFY(v)
    302
    303      return s
    304