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