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