web2py / gluon / restricted.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
This file is part of web2py Web Framework (Copyrighted, 2007-2009).
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>.
License: GPL v2
"""

import uuid
import sys
import cStringIO
import cPickle
import traceback
import copy
import cgi
import types
import time
import os
import datetime
import logging

from storage import Storage
from html import BEAUTIFY
from http import HTTP

__all__ = ['RestrictedError', 'restricted', 'TicketStorage']


class TicketStorage(Storage):

    """
    defines the ticket object and the default values of its members (None)
    """

    def __init__(
        self,
        db=None,
        tablename='web2py_ticket'
        ):
        self.db = db
        self.tablename = tablename

    def store(self, request, ticket_id, ticket_data):
        """
        stores the ticket. It will figure out if this must be on disk or in db
        """
        if self.db:
            self._store_in_db(request, ticket_id, ticket_data)
        else:
            self._store_on_disk(request, ticket_id, ticket_data)

    def _store_in_db(self, request, ticket_id, ticket_data):
        table = self._get_table(self.db, self.tablename, request.application)
        table.insert(ticket_id=ticket_id,
                     ticket_data=cPickle.dumps(ticket_data),
                     created_datetime=request.now)
        logging.error('In FILE: %(layer)s\n\n%(traceback)s\n' % ticket_data)

    def _store_on_disk(self, request, ticket_id, ticket_data):
        cPickle.dump(ticket_data, self._error_file(request, ticket_id, 'wb'))

    def _error_file(self, request, ticket_id, mode, app=None):
        root = request.folder
        if app:
            root = os.path.join(os.path.join(root, '..'), app)
        errors_folder = os.path.join(root, 'errors') #.replace('\\', '/')
        if not os.path.exists(errors_folder) and mode == 'wb':
            os.mkdir(errors_folder)

        return open(os.path.join(errors_folder, ticket_id), mode)

    def _get_table(self, db, tablename, app):
        tablename = tablename + '_' + app
        table = db.get(tablename, None)
        if table is None:
            db.rollback()   # not necessary but one day
                            # any app may store tickets on DB
            table = db.define_table(
                tablename,
                db.Field('ticket_id', length=100),
                db.Field('ticket_data', 'text'),
                db.Field('created_datetime', 'datetime'),
                )
        return table

    def load(
        self,
        request,
        app,
        ticket_id,
        ):
        if not self.db:
            return cPickle.load(self._error_file(request, ticket_id, 'rb', app))
        table=self._get_table(self.db, self.tablename, app)
        rows = self.db(table.ticket_id == ticket_id).select()
        if rows:
            return cPickle.loads(rows[0].ticket_data)
        return None


class RestrictedError:
    """
    class used to wrap an exception that occurs in the restricted environment
    below. the traceback is used to log the exception and generate a ticket.
    """

    def __init__(
        self,
        layer='',
        code='',
        output='',
        environment={},
        ):
        """
        layer here is some description of where in the system the exception
        occurred.
        """

        self.layer = layer
        self.code = code
        self.output = output
        if layer:
            self.traceback = traceback.format_exc()
        else:
            self.traceback = '(no error)'
        self.environment = environment

    def log(self, request):
        """
        logs the exception.
        """

        a = request.application

        d = {
            'layer': str(self.layer),
            'code': str(self.code),
            'output': str(self.output),
            'traceback': str(self.traceback),
            }

        f = '%s.%s.%s' % (request.client.replace(':', '_'),
                          datetime.datetime.now().strftime('%Y-%m-%d.%H-%M-%S'),
                          uuid.uuid4())

        ticket_storage = TicketStorage(db=request.tickets_db)
        ticket_storage.store(request, f, d)

        return '%s/%s' % (a, f)

    def load(self, request, app, ticket_id):
        """
        loads a logged exception.
        """
        ticket_storage = TicketStorage(db=request.tickets_db)
        d = ticket_storage.load(request, app, ticket_id)

        self.layer = d['layer']
        self.code = d['code']
        self.output = d['output']
        self.traceback = d['traceback']


def restricted(code, environment={}, layer='Unknown'):
    """
    runs code in environment and returns the output. if an exception occurs
    in code it raises a RestrictedError containing the traceback. layer is
    passed to RestrictedError to identify where the error occurred.
    """

    try:
        if type(code) == types.CodeType:
            ccode = code
        else:
            ccode = compile(code.replace('\r\n', '\n'), layer, 'exec')

        exec ccode in environment
    except HTTP:
        raise
    except Exception, exception:
        # XXX Show exception in Wing IDE if running in debugger
        if __debug__ and 'WINGDB_ACTIVE' in os.environ:
            etype, evalue, tb = sys.exc_info()
            sys.excepthook(etype, evalue, tb)
        raise RestrictedError(layer, code, '', environment)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.