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

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