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
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
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
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
58 cPickle.dump(ticket_data, self._error_file(request, ticket_id, 'wb'))
59
60 - def _error_file(self, request, ticket_id, mode, app=None):
61 root = request.folder
62 if app:
63 root = os.path.join(os.path.join(root, '..'), app)
64 errors_folder = os.path.abspath(os.path.join(root, 'errors'))
65 return open(os.path.join(errors_folder, ticket_id), mode)
66
68 tablename = tablename + '_' + app
69 table = db.get(tablename, None)
70 if table is None:
71 db.rollback()
72
73 table = db.define_table(
74 tablename,
75 db.Field('ticket_id', length=100),
76 db.Field('ticket_data', 'text'),
77 db.Field('created_datetime', 'datetime'),
78 )
79 return table
80
81 - def load(
82 self,
83 request,
84 app,
85 ticket_id,
86 ):
87 if not self.db:
88 return cPickle.load(self._error_file(request, ticket_id, 'rb', app))
89 table=self._get_table(self.db, self.tablename, app)
90 rows = self.db(table.ticket_id == ticket_id).select()
91 if rows:
92 return cPickle.loads(rows[0].ticket_data)
93 return None
94
95
97 """
98 class used to wrap an exception that occurs in the restricted environment
99 below. the traceback is used to log the exception and generate a ticket.
100 """
101
102 - def __init__(
103 self,
104 layer='',
105 code='',
106 output='',
107 environment={},
108 ):
109 """
110 layer here is some description of where in the system the exception
111 occurred.
112 """
113
114 self.layer = layer
115 self.code = code
116 self.output = output
117 if layer:
118 self.traceback = traceback.format_exc()
119 try:
120 self.snapshot = snapshot(context=10,code=code,environment=environment)
121 except:
122 self.snapshot = {}
123 else:
124 self.traceback = '(no error)'
125 self.snapshot = {}
126 self.environment = environment
127
128 - def log(self, request):
129 """
130 logs the exception.
131 """
132
133 try:
134 d = {
135 'layer': str(self.layer),
136 'code': str(self.code),
137 'output': str(self.output),
138 'traceback': str(self.traceback),
139 'snapshot': self.snapshot,
140 }
141 ticket_storage = TicketStorage(db=request.tickets_db)
142 ticket_storage.store(request, request.uuid.split('/',1)[1], d)
143 return request.uuid
144 except:
145 logger.error(self.traceback)
146 return None
147
148
149 - def load(self, request, app, ticket_id):
150 """
151 loads a logged exception.
152 """
153 ticket_storage = TicketStorage(db=request.tickets_db)
154 d = ticket_storage.load(request, app, ticket_id)
155
156 self.layer = d['layer']
157 self.code = d['code']
158 self.output = d['output']
159 self.traceback = d['traceback']
160 self.snapshot = d.get('snapshot')
161
162
164 """
165 The +'\n' is necessary else compile fails when code ends in a comment.
166 """
167 return compile(code.rstrip().replace('\r\n','\n')+'\n', layer, 'exec')
168
169 -def restricted(code, environment={}, layer='Unknown'):
170 """
171 runs code in environment and returns the output. if an exception occurs
172 in code it raises a RestrictedError containing the traceback. layer is
173 passed to RestrictedError to identify where the error occurred.
174 """
175 environment['__file__'] = layer
176 try:
177 if type(code) == types.CodeType:
178 ccode = code
179 else:
180 ccode = compile2(code,layer)
181 exec ccode in environment
182 except HTTP:
183 raise
184 except Exception:
185
186 if __debug__ and 'WINGDB_ACTIVE' in os.environ:
187 etype, evalue, tb = sys.exc_info()
188 sys.excepthook(etype, evalue, tb)
189 raise RestrictedError(layer, code, '', environment)
190
191 -def snapshot(info=None, context=5, code=None, environment=None):
192 """Return a dict describing a given traceback (based on cgitb.text)."""
193 import os, types, time, traceback, linecache, inspect, pydoc, cgitb
194
195
196 etype, evalue, etb = info or sys.exc_info()
197
198 if type(etype) is types.ClassType:
199 etype = etype.__name__
200
201
202 s = {}
203 s['pyver'] = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
204 s['date'] = time.ctime(time.time())
205
206
207 records = inspect.getinnerframes(etb, context)
208 s['frames'] = []
209 for frame, file, lnum, func, lines, index in records:
210 file = file and os.path.abspath(file) or '?'
211 args, varargs, varkw, locals = inspect.getargvalues(frame)
212 call = ''
213 if func != '?':
214 call = inspect.formatargvalues(args, varargs, varkw, locals,
215 formatvalue=lambda value: '=' + pydoc.text.repr(value))
216
217
218 f = {'file': file, 'func': func, 'call': call, 'lines': {}, 'lnum': lnum}
219
220 highlight = {}
221 def reader(lnum=[lnum]):
222 highlight[lnum[0]] = 1
223 try: return linecache.getline(file, lnum[0])
224 finally: lnum[0] += 1
225 vars = cgitb.scanvars(reader, frame, locals)
226
227
228 if file.endswith('html'):
229 lmin = lnum>context and (lnum-context) or 0
230 lmax = lnum+context
231 lines = code.split("\n")[lmin:lmax]
232 index = min(context, lnum) - 1
233
234 if index is not None:
235 i = lnum - index
236 for line in lines:
237 f['lines'][i] = line.rstrip()
238 i += 1
239
240
241 f['dump'] = {}
242 for name, where, value in vars:
243 if name in f['dump']: continue
244 if value is not cgitb.__UNDEF__:
245 if where == 'global': name = 'global ' + name
246 elif where != 'local': name = where + name.split('.')[-1]
247 f['dump'][name] = pydoc.text.repr(value)
248 else:
249 f['dump'][name] = 'undefined'
250
251 s['frames'].append(f)
252
253
254 s['etype'] = str(etype)
255 s['evalue'] = str(evalue)
256 s['exception'] = {}
257 if isinstance(evalue, BaseException):
258 for name in dir(evalue):
259
260 if name!='message' or sys.version_info<(2.6):
261 value = pydoc.text.repr(getattr(evalue, name))
262 s['exception'][name] = value
263
264
265 s['locals'] = {}
266 for name, value in locals.items():
267 s['locals'][name] = pydoc.text.repr(value)
268
269
270 for k,v in environment.items():
271 if k in ('request', 'response', 'session'):
272 s[k] = BEAUTIFY(v)
273
274 return s
275