1
2
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
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 ):
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
67
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):
82
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
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
196
197
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
225 raise
226 except Exception, error:
227
228 etype, evalue, tb = sys.exc_info()
229
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
247 etype, evalue, etb = info or sys.exc_info()
248
249 if isinstance(etype, types.ClassType):
250 etype = etype.__name__
251
252
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
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
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
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
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
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
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
323 s['locals'] = {}
324 for name, value in locals.items():
325 s['locals'][name] = pydoc.text.repr(value)
326
327
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