1b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik"""
4b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikException-catching middleware that allows interactive debugging.
5b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
6b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikThis middleware catches all unexpected exceptions.  A normal
7b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craiktraceback, like produced by
8b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik``paste.exceptions.errormiddleware.ErrorMiddleware`` is given, plus
9b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikcontrols to see local variables and evaluate expressions in a local
10b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikcontext.
11b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
12b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikThis can only be used in single-process environments, because
13b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craiksubsequent requests must go back to the same process that the
14b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikexception originally occurred in.  Threaded or non-concurrent
15b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikenvironments both work.
16b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
17b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikThis shouldn't be used in production in any way.  That would just be
18b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craiksilly.
19b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
20b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikIf calling from an XMLHttpRequest call, if the GET variable ``_`` is
21b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikgiven then it will make the response more compact (and less
22b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikJavascripty), since if you use innerHTML it'll kill your browser.  You
23b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikcan look for the header X-Debug-URL in your 500 responses if you want
24b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikto see the full debuggable traceback.  Also, this URL is printed to
25b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik``wsgi.errors``, so you can open it up in another browser window.
26b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik"""
27b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
28b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom __future__ import print_function
29b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
30b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport sys
31b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport os
32b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport cgi
33b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport traceback
34b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport six
35b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom six.moves import cStringIO as StringIO
36b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport pprint
37b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport itertools
38b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport time
39b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport re
40b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste.exceptions import errormiddleware, formatter, collector
41b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste import wsgilib
42b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste import urlparser
43b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste import httpexceptions
44b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste import registry
45b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste import request
46b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste import response
47b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste.evalexception import evalcontext
48b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
49b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craiklimit = 200
50b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
51b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef html_quote(v):
52b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
53b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Escape HTML characters, plus translate None to ''
54b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
55b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if v is None:
56b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return ''
57b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return cgi.escape(str(v), 1)
58b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
59b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef preserve_whitespace(v, quote=True):
60b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
61b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Quote a value for HTML, preserving whitespace (translating
62b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    newlines to ``<br>`` and multiple spaces to use ``&nbsp;``).
63b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
64b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    If ``quote`` is true, then the value will be HTML quoted first.
65b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
66b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if quote:
67b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        v = html_quote(v)
68b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    v = v.replace('\n', '<br>\n')
69b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    v = re.sub(r'()(  +)', _repl_nbsp, v)
70b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    v = re.sub(r'(\n)( +)', _repl_nbsp, v)
71b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    v = re.sub(r'^()( +)', _repl_nbsp, v)
72b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return '<code>%s</code>' % v
73b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
74b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef _repl_nbsp(match):
75b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if len(match.group(2)) == 1:
76b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return '&nbsp;'
77b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return match.group(1) + '&nbsp;' * (len(match.group(2))-1) + ' '
78b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
79b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef simplecatcher(application):
80b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
81b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    A simple middleware that catches errors and turns them into simple
82b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    tracebacks.
83b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
84b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def simplecatcher_app(environ, start_response):
85b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        try:
86b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return application(environ, start_response)
87b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        except:
88b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            out = StringIO()
89b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            traceback.print_exc(file=out)
90b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            start_response('500 Server Error',
91b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                           [('content-type', 'text/html')],
92b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                           sys.exc_info())
93b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            res = out.getvalue()
94b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return ['<h3>Error</h3><pre>%s</pre>'
95b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    % html_quote(res)]
96b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return simplecatcher_app
97b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
98b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef wsgiapp():
99b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
100b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Turns a function or method into a WSGI application.
101b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
102b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def decorator(func):
103b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        def wsgiapp_wrapper(*args):
104b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # we get 3 args when this is a method, two when it is
105b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # a function :(
106b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if len(args) == 3:
107b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                environ = args[1]
108b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                start_response = args[2]
109b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                args = [args[0]]
110b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else:
111b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                environ, start_response = args
112b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                args = []
113b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            def application(environ, start_response):
114b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                form = wsgilib.parse_formvars(environ,
115b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                                              include_get_vars=True)
116b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                headers = response.HeaderDict(
117b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    {'content-type': 'text/html',
118b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                     'status': '200 OK'})
119b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                form['environ'] = environ
120b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                form['headers'] = headers
121b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                res = func(*args, **form.mixed())
122b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                status = headers.pop('status')
123b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                start_response(status, headers.headeritems())
124b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                return [res]
125b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            app = httpexceptions.make_middleware(application)
126b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            app = simplecatcher(app)
127b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return app(environ, start_response)
128b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        wsgiapp_wrapper.exposed = True
129b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return wsgiapp_wrapper
130b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return decorator
131b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
132b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef get_debug_info(func):
133b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
134b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    A decorator (meant to be used under ``wsgiapp()``) that resolves
135b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    the ``debugcount`` variable to a ``DebugInfo`` object (or gives an
136b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    error if it can't be found).
137b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
138b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def debug_info_replacement(self, **form):
139b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        try:
140b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if 'debugcount' not in form:
141b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                raise ValueError('You must provide a debugcount parameter')
142b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            debugcount = form.pop('debugcount')
143b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            try:
144b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                debugcount = int(debugcount)
145b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            except ValueError:
146b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                raise ValueError('Bad value for debugcount')
147b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if debugcount not in self.debug_infos:
148b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                raise ValueError(
149b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    'Debug %s no longer found (maybe it has expired?)'
150b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    % debugcount)
151b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            debug_info = self.debug_infos[debugcount]
152b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return func(self, debug_info=debug_info, **form)
153b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        except ValueError as e:
154b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            form['headers']['status'] = '500 Server Error'
155b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return '<html>There was an error: %s</html>' % html_quote(e)
156b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return debug_info_replacement
157b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
158b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdebug_counter = itertools.count(int(time.time()))
159b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef get_debug_count(environ):
160b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
161b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Return the unique debug count for the current request
162b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
163b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if 'paste.evalexception.debug_count' in environ:
164b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return environ['paste.evalexception.debug_count']
165b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    else:
166b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        environ['paste.evalexception.debug_count'] = next = six.next(debug_counter)
167b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return next
168b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
169b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikclass EvalException(object):
170b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
171b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def __init__(self, application, global_conf=None,
172b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                 xmlhttp_key=None):
173b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.application = application
174b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.debug_infos = {}
175b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if xmlhttp_key is None:
176b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if global_conf is None:
177b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                xmlhttp_key = '_'
178b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else:
179b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                xmlhttp_key = global_conf.get('xmlhttp_key', '_')
180b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.xmlhttp_key = xmlhttp_key
181b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
182b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def __call__(self, environ, start_response):
183b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        assert not environ['wsgi.multiprocess'], (
184b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            "The EvalException middleware is not usable in a "
185b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            "multi-process environment")
186b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        environ['paste.evalexception'] = self
187b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if environ.get('PATH_INFO', '').startswith('/_debug/'):
188b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return self.debug(environ, start_response)
189b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
190b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return self.respond(environ, start_response)
191b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
192b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def debug(self, environ, start_response):
193b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        assert request.path_info_pop(environ) == '_debug'
194b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        next_part = request.path_info_pop(environ)
195b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        method = getattr(self, next_part, None)
196b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if not method:
197b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            exc = httpexceptions.HTTPNotFound(
198b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                '%r not found when parsing %r'
199b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                % (next_part, wsgilib.construct_url(environ)))
200b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return exc.wsgi_application(environ, start_response)
201b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if not getattr(method, 'exposed', False):
202b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            exc = httpexceptions.HTTPForbidden(
203b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                '%r not allowed' % next_part)
204b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return exc.wsgi_application(environ, start_response)
205b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return method(environ, start_response)
206b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
207b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def media(self, environ, start_response):
208b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
209b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        Static path where images and other files live
210b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
211b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        app = urlparser.StaticURLParser(
212b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            os.path.join(os.path.dirname(__file__), 'media'))
213b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return app(environ, start_response)
214b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    media.exposed = True
215b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
216b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def mochikit(self, environ, start_response):
217b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
218b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        Static path where MochiKit lives
219b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
220b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        app = urlparser.StaticURLParser(
221b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            os.path.join(os.path.dirname(__file__), 'mochikit'))
222b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return app(environ, start_response)
223b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    mochikit.exposed = True
224b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
225b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def summary(self, environ, start_response):
226b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
227b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        Returns a JSON-format summary of all the cached
228b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        exception reports
229b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
230b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        start_response('200 OK', [('Content-type', 'text/x-json')])
231b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        data = [];
232b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        items = self.debug_infos.values()
233b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        items.sort(lambda a, b: cmp(a.created, b.created))
234b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        data = [item.json() for item in items]
235b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return [repr(data)]
236b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    summary.exposed = True
237b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
238b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def view(self, environ, start_response):
239b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
240b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        View old exception reports
241b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
242b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        id = int(request.path_info_pop(environ))
243b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if id not in self.debug_infos:
244b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            start_response(
245b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                '500 Server Error',
246b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                [('Content-type', 'text/html')])
247b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return [
248b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                "Traceback by id %s does not exist (maybe "
249b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                "the server has been restarted?)"
250b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                % id]
251b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        debug_info = self.debug_infos[id]
252b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return debug_info.wsgi_application(environ, start_response)
253b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    view.exposed = True
254b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
255b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def make_view_url(self, environ, base_path, count):
256b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return base_path + '/_debug/view/%s' % count
257b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
258b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    #@wsgiapp()
259b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    #@get_debug_info
260b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def show_frame(self, tbid, debug_info, **kw):
261b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        frame = debug_info.frame(int(tbid))
262b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        vars = frame.tb_frame.f_locals
263b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if vars:
264b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            registry.restorer.restoration_begin(debug_info.counter)
265b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            local_vars = make_table(vars)
266b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            registry.restorer.restoration_end()
267b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
268b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            local_vars = 'No local vars'
269b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return input_form(tbid, debug_info) + local_vars
270b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
271b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    show_frame = wsgiapp()(get_debug_info(show_frame))
272b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
273b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    #@wsgiapp()
274b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    #@get_debug_info
275b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def exec_input(self, tbid, debug_info, input, **kw):
276b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if not input.strip():
277b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return ''
278b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        input = input.rstrip() + '\n'
279b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        frame = debug_info.frame(int(tbid))
280b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        vars = frame.tb_frame.f_locals
281b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        glob_vars = frame.tb_frame.f_globals
282b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        context = evalcontext.EvalContext(vars, glob_vars)
283b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        registry.restorer.restoration_begin(debug_info.counter)
284b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        output = context.exec_expr(input)
285b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        registry.restorer.restoration_end()
286b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        input_html = formatter.str2html(input)
287b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return ('<code style="color: #060">&gt;&gt;&gt;</code> '
288b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                '<code>%s</code><br>\n%s'
289b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                % (preserve_whitespace(input_html, quote=False),
290b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                   preserve_whitespace(output)))
291b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
292b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    exec_input = wsgiapp()(get_debug_info(exec_input))
293b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
294b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def respond(self, environ, start_response):
295b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if environ.get('paste.throw_errors'):
296b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return self.application(environ, start_response)
297b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        base_path = request.construct_url(environ, with_path_info=False,
298b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                                          with_query_string=False)
299b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        environ['paste.throw_errors'] = True
300b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        started = []
301b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        def detect_start_response(status, headers, exc_info=None):
302b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            try:
303b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                return start_response(status, headers, exc_info)
304b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            except:
305b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                raise
306b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else:
307b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                started.append(True)
308b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        try:
309b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            __traceback_supplement__ = errormiddleware.Supplement, self, environ
310b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            app_iter = self.application(environ, detect_start_response)
311b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            try:
312b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                return_iter = list(app_iter)
313b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                return return_iter
314b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            finally:
315b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                if hasattr(app_iter, 'close'):
316b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    app_iter.close()
317b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        except:
318b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            exc_info = sys.exc_info()
319b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            for expected in environ.get('paste.expected_exceptions', []):
320b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                if isinstance(exc_info[1], expected):
321b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    raise
322b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
323b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Tell the Registry to save its StackedObjectProxies current state
324b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # for later restoration
325b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            registry.restorer.save_registry_state(environ)
326b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
327b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            count = get_debug_count(environ)
328b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            view_uri = self.make_view_url(environ, base_path, count)
329b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if not started:
330b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                headers = [('content-type', 'text/html')]
331b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                headers.append(('X-Debug-URL', view_uri))
332b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                start_response('500 Internal Server Error',
333b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                               headers,
334b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                               exc_info)
335b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            msg = 'Debug at: %s\n' % view_uri
336b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if six.PY3:
337b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                msg = msg.encode('utf8')
338b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            environ['wsgi.errors'].write(msg)
339b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
340b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            exc_data = collector.collect_exception(*exc_info)
341b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            debug_info = DebugInfo(count, exc_info, exc_data, base_path,
342b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                                   environ, view_uri)
343b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            assert count not in self.debug_infos
344b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            self.debug_infos[count] = debug_info
345b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
346b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if self.xmlhttp_key:
347b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                get_vars = request.parse_querystring(environ)
348b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                if dict(get_vars).get(self.xmlhttp_key):
349b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    exc_data = collector.collect_exception(*exc_info)
350b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    html = formatter.format_html(
351b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                        exc_data, include_hidden_frames=False,
352b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                        include_reusable=False, show_extra_data=False)
353b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    return [html]
354b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
355b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # @@: it would be nice to deal with bad content types here
356b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return debug_info.content()
357b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
358b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def exception_handler(self, exc_info, environ):
359b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        simple_html_error = False
360b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if self.xmlhttp_key:
361b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            get_vars = request.parse_querystring(environ)
362b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if dict(get_vars).get(self.xmlhttp_key):
363b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                simple_html_error = True
364b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return errormiddleware.handle_exception(
365b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            exc_info, environ['wsgi.errors'],
366b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            html=True,
367b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            debug_mode=True,
368b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            simple_html_error=simple_html_error)
369b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
370b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikclass DebugInfo(object):
371b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
372b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def __init__(self, counter, exc_info, exc_data, base_path,
373b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                 environ, view_uri):
374b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.counter = counter
375b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.exc_data = exc_data
376b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.base_path = base_path
377b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.environ = environ
378b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.view_uri = view_uri
379b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.created = time.time()
380b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.exc_type, self.exc_value, self.tb = exc_info
381b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        __exception_formatter__ = 1
382b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.frames = []
383b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        n = 0
384b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        tb = self.tb
385b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        while tb is not None and (limit is None or n < limit):
386b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if tb.tb_frame.f_locals.get('__exception_formatter__'):
387b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                # Stop recursion. @@: should make a fake ExceptionFrame
388b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                break
389b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            self.frames.append(tb)
390b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            tb = tb.tb_next
391b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            n += 1
392b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
393b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def json(self):
394b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """Return the JSON-able representation of this object"""
395b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return {
396b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'uri': self.view_uri,
397b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'created': time.strftime('%c', time.gmtime(self.created)),
398b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'created_timestamp': self.created,
399b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'exception_type': str(self.exc_type),
400b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'exception': str(self.exc_value),
401b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            }
402b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
403b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def frame(self, tbid):
404b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        for frame in self.frames:
405b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if id(frame) == tbid:
406b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                return frame
407b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
408b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            raise ValueError("No frame by id %s found from %r" % (tbid, self.frames))
409b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
410b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def wsgi_application(self, environ, start_response):
411b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        start_response('200 OK', [('content-type', 'text/html')])
412b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return self.content()
413b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
414b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def content(self):
415b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        html = format_eval_html(self.exc_data, self.base_path, self.counter)
416b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        head_html = (formatter.error_css + formatter.hide_display_js)
417b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        head_html += self.eval_javascript()
418b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        repost_button = make_repost_button(self.environ)
419b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        page = error_template % {
420b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'repost_button': repost_button or '',
421b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'head_html': head_html,
422b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'body': html}
423b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if six.PY3:
424b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            page = page.encode('utf8')
425b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return [page]
426b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
427b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def eval_javascript(self):
428b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        base_path = self.base_path + '/_debug'
429b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return (
430b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            '<script type="text/javascript" src="%s/media/MochiKit.packed.js">'
431b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            '</script>\n'
432b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            '<script type="text/javascript" src="%s/media/debug.js">'
433b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            '</script>\n'
434b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            '<script type="text/javascript">\n'
435b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'debug_base = %r;\n'
436b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'debug_count = %r;\n'
437b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            '</script>\n'
438b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            % (base_path, base_path, base_path, self.counter))
439b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
440b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikclass EvalHTMLFormatter(formatter.HTMLFormatter):
441b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
442b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def __init__(self, base_path, counter, **kw):
443b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        super(EvalHTMLFormatter, self).__init__(**kw)
444b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.base_path = base_path
445b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        self.counter = counter
446b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
447b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def format_source_line(self, filename, frame):
448b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        line = formatter.HTMLFormatter.format_source_line(
449b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            self, filename, frame)
450b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return (line +
451b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                '  <a href="#" class="switch_source" '
452b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                'tbid="%s" onClick="return showFrame(this)">&nbsp; &nbsp; '
453b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                '<img src="%s/_debug/media/plus.jpg" border=0 width=9 '
454b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                'height=9> &nbsp; &nbsp;</a>'
455b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                % (frame.tbid, self.base_path))
456b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
457b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef make_table(items):
458b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if isinstance(items, dict):
459b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        items = items.items()
460b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        items.sort()
461b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    rows = []
462b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    i = 0
463b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for name, value in items:
464b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        i += 1
465b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        out = StringIO()
466b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        try:
467b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            pprint.pprint(value, out)
468b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        except Exception as e:
469b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            print('Error: %s' % e, file=out)
470b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        value = html_quote(out.getvalue())
471b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if len(value) > 100:
472b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # @@: This can actually break the HTML :(
473b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # should I truncate before quoting?
474b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            orig_value = value
475b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            value = value[:100]
476b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            value += '<a class="switch_source" style="background-color: #999" href="#" onclick="return expandLong(this)">...</a>'
477b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            value += '<span style="display: none">%s</span>' % orig_value[100:]
478b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        value = formatter.make_wrappable(value)
479b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if i % 2:
480b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            attr = ' class="even"'
481b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
482b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            attr = ' class="odd"'
483b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        rows.append('<tr%s style="vertical-align: top;"><td>'
484b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    '<b>%s</b></td><td style="overflow: auto">%s<td></tr>'
485b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    % (attr, html_quote(name),
486b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                       preserve_whitespace(value, quote=False)))
487b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return '<table>%s</table>' % (
488b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        '\n'.join(rows))
489b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
490b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef format_eval_html(exc_data, base_path, counter):
491b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    short_formatter = EvalHTMLFormatter(
492b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        base_path=base_path,
493b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        counter=counter,
494b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        include_reusable=False)
495b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    short_er = short_formatter.format_collected_data(exc_data)
496b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    long_formatter = EvalHTMLFormatter(
497b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        base_path=base_path,
498b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        counter=counter,
499b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        show_hidden_frames=True,
500b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        show_extra_data=False,
501b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        include_reusable=False)
502b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    long_er = long_formatter.format_collected_data(exc_data)
503b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    text_er = formatter.format_text(exc_data, show_hidden_frames=True)
504b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if short_formatter.filter_frames(exc_data.frames) != \
505b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        long_formatter.filter_frames(exc_data.frames):
506b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # Only display the full traceback when it differs from the
507b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # short version
508b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        full_traceback_html = """
509b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    <br>
510b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    <script type="text/javascript">
511b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    show_button('full_traceback', 'full traceback')
512b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    </script>
513b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    <div id="full_traceback" class="hidden-data">
514b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    %s
515b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    </div>
516b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """ % long_er
517b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    else:
518b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        full_traceback_html = ''
519b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
520b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return """
521b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    %s
522b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    %s
523b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    <br>
524b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    <script type="text/javascript">
525b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    show_button('text_version', 'text version')
526b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    </script>
527b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    <div id="text_version" class="hidden-data">
528b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    <textarea style="width: 100%%" rows=10 cols=60>%s</textarea>
529b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    </div>
530b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """ % (short_er, full_traceback_html, cgi.escape(text_er))
531b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
532b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef make_repost_button(environ):
533b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    url = request.construct_url(environ)
534b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if environ['REQUEST_METHOD'] == 'GET':
535b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return ('<button onclick="window.location.href=%r">'
536b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                'Re-GET Page</button><br>' % url)
537b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    else:
538b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # @@: I'd like to reconstruct this, but I can't because
539b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # the POST body is probably lost at this point, and
540b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # I can't get it back :(
541b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return None
542b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # @@: Use or lose the following code block
543b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
544b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    fields = []
545b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for name, value in wsgilib.parse_formvars(
546b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        environ, include_get_vars=False).items():
547b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if hasattr(value, 'filename'):
548b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # @@: Arg, we'll just submit the body, and leave out
549b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # the filename :(
550b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            value = value.value
551b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        fields.append(
552b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            '<input type="hidden" name="%s" value="%s">'
553b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            % (html_quote(name), html_quote(value)))
554b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return '''
555b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<form action="%s" method="POST">
556b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik%s
557b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<input type="submit" value="Re-POST Page">
558b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik</form>''' % (url, '\n'.join(fields))
559b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik"""
560b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
561b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
562b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef input_form(tbid, debug_info):
563b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return '''
564b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<form action="#" method="POST"
565b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik onsubmit="return submitInput($(\'submit_%(tbid)s\'), %(tbid)s)">
566b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<div id="exec-output-%(tbid)s" style="width: 95%%;
567b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik padding: 5px; margin: 5px; border: 2px solid #000;
568b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik display: none"></div>
569b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<input type="text" name="input" id="debug_input_%(tbid)s"
570b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik style="width: 100%%"
571b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik autocomplete="off" onkeypress="upArrow(this, event)"><br>
572b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<input type="submit" value="Execute" name="submitbutton"
573b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik onclick="return submitInput(this, %(tbid)s)"
574b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik id="submit_%(tbid)s"
575b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik input-from="debug_input_%(tbid)s"
576b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik output-to="exec-output-%(tbid)s">
577b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<input type="submit" value="Expand"
578b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik onclick="return expandInput(this)">
579b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik</form>
580b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik ''' % {'tbid': tbid}
581b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
582b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikerror_template = '''
583b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<html>
584b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<head>
585b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik <title>Server Error</title>
586b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik %(head_html)s
587b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik</head>
588b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<body>
589b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
590b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<div id="error-area" style="display: none; background-color: #600; color: #fff; border: 2px solid black">
591b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<div id="error-container"></div>
592b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik<button onclick="return clearError()">clear this</button>
593b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik</div>
594b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
595b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik%(repost_button)s
596b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
597b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik%(body)s
598b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
599b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik</body>
600b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik</html>
601b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik'''
602b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
603b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef make_eval_exception(app, global_conf, xmlhttp_key=None):
604b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
605b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Wraps the application in an interactive debugger.
606b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
607b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    This debugger is a major security hole, and should only be
608b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    used during development.
609b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
610b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    xmlhttp_key is a string that, if present in QUERY_STRING,
611b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    indicates that the request is an XMLHttp request, and the
612b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Javascript/interactive debugger should not be returned.  (If you
613b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    try to put the debugger somewhere with innerHTML, you will often
614b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    crash the browser)
615b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
616b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if xmlhttp_key is None:
617b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        xmlhttp_key = global_conf.get('xmlhttp_key', '_')
618b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return EvalException(app, xmlhttp_key=xmlhttp_key)
619