diff options
Diffstat (limited to 'src/werkzeug/debug')
| -rw-r--r-- | src/werkzeug/debug/__init__.py | 265 | ||||
| -rw-r--r-- | src/werkzeug/debug/console.py | 61 | ||||
| -rw-r--r-- | src/werkzeug/debug/repr.py | 153 | ||||
| -rw-r--r-- | src/werkzeug/debug/tbtools.py | 265 |
4 files changed, 391 insertions, 353 deletions
diff --git a/src/werkzeug/debug/__init__.py b/src/werkzeug/debug/__init__.py index cb984b03..beb7729e 100644 --- a/src/werkzeug/debug/__init__.py +++ b/src/werkzeug/debug/__init__.py @@ -8,33 +8,35 @@ :copyright: 2007 Pallets :license: BSD-3-Clause """ +import getpass +import hashlib +import json +import mimetypes import os import pkgutil import re import sys -import uuid -import json import time -import getpass -import hashlib -import mimetypes +import uuid from itertools import chain -from os.path import join, basename -from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response -from werkzeug.http import parse_cookie -from werkzeug.debug.tbtools import get_current_traceback, render_console_html -from werkzeug.debug.console import Console -from werkzeug.security import gen_salt -from werkzeug._internal import _log -from werkzeug._compat import text_type +from os.path import basename +from os.path import join - -# DEPRECATED -from werkzeug.debug.repr import debug_repr as _debug_repr +from .._compat import text_type +from .._internal import _log +from ..http import parse_cookie +from ..security import gen_salt +from ..wrappers import BaseRequest as Request +from ..wrappers import BaseResponse as Response +from .console import Console +from .repr import debug_repr as _debug_repr +from .tbtools import get_current_traceback +from .tbtools import render_console_html def debug_repr(*args, **kwargs): import warnings + warnings.warn( "'debug_repr' has moved to 'werkzeug.debug.repr.debug_repr'" " as of version 0.7. This old import will be removed in version" @@ -51,8 +53,8 @@ PIN_TIME = 60 * 60 * 24 * 7 def hash_pin(pin): if isinstance(pin, text_type): - pin = pin.encode('utf-8', 'replace') - return hashlib.md5(pin + b'shittysalt').hexdigest()[:12] + pin = pin.encode("utf-8", "replace") + return hashlib.md5(pin + b"shittysalt").hexdigest()[:12] _machine_id = None @@ -67,9 +69,9 @@ def get_machine_id(): def _generate(): # Potential sources of secret information on linux. The machine-id # is stable across boots, the boot id is not - for filename in '/etc/machine-id', '/proc/sys/kernel/random/boot_id': + for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id": try: - with open(filename, 'rb') as f: + with open(filename, "rb") as f: return f.readline().strip() except IOError: continue @@ -81,8 +83,10 @@ def get_machine_id(): # Google App Engine # See https://github.com/pallets/werkzeug/issues/925 from subprocess import Popen, PIPE - dump = Popen(['ioreg', '-c', 'IOPlatformExpertDevice', '-d', '2'], - stdout=PIPE).communicate()[0] + + dump = Popen( + ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE + ).communicate()[0] match = re.search(b'"serial-number" = <([^>]+)', dump) if match is not None: return match.group(1) @@ -100,12 +104,15 @@ def get_machine_id(): pass if wr is not None: try: - with wr.OpenKey(wr.HKEY_LOCAL_MACHINE, - 'SOFTWARE\\Microsoft\\Cryptography', 0, - wr.KEY_READ | wr.KEY_WOW64_64KEY) as rk: - machineGuid, wrType = wr.QueryValueEx(rk, 'MachineGuid') + with wr.OpenKey( + wr.HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Cryptography", + 0, + wr.KEY_READ | wr.KEY_WOW64_64KEY, + ) as rk: + machineGuid, wrType = wr.QueryValueEx(rk, "MachineGuid") if wrType == wr.REG_SZ: - return machineGuid.encode('utf-8') + return machineGuid.encode("utf-8") else: return machineGuid except WindowsError: @@ -116,7 +123,6 @@ def get_machine_id(): class _ConsoleFrame(object): - """Helper class so that we can reuse the frame console code for the standalone console. """ @@ -134,24 +140,23 @@ def get_pin_and_cookie_name(app): Second item in the resulting tuple is the cookie name for remembering. """ - pin = os.environ.get('WERKZEUG_DEBUG_PIN') + pin = os.environ.get("WERKZEUG_DEBUG_PIN") rv = None num = None # Pin was explicitly disabled - if pin == 'off': + if pin == "off": return None, None # Pin was provided explicitly - if pin is not None and pin.replace('-', '').isdigit(): + if pin is not None and pin.replace("-", "").isdigit(): # If there are separators in the pin, return it directly - if '-' in pin: + if "-" in pin: rv = pin else: num = pin - modname = getattr(app, '__module__', - getattr(app.__class__, '__module__')) + modname = getattr(app, "__module__", getattr(app.__class__, "__module__")) try: # `getpass.getuser()` imports the `pwd` module, @@ -167,42 +172,41 @@ def get_pin_and_cookie_name(app): probably_public_bits = [ username, modname, - getattr(app, '__name__', getattr(app.__class__, '__name__')), - getattr(mod, '__file__', None), + getattr(app, "__name__", getattr(app.__class__, "__name__")), + getattr(mod, "__file__", None), ] # This information is here to make it harder for an attacker to # guess the cookie name. They are unlikely to be contained anywhere # within the unauthenticated debug page. - private_bits = [ - str(uuid.getnode()), - get_machine_id(), - ] + private_bits = [str(uuid.getnode()), get_machine_id()] h = hashlib.md5() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance(bit, text_type): - bit = bit.encode('utf-8') + bit = bit.encode("utf-8") h.update(bit) - h.update(b'cookiesalt') + h.update(b"cookiesalt") - cookie_name = '__wzd' + h.hexdigest()[:20] + cookie_name = "__wzd" + h.hexdigest()[:20] # If we need to generate a pin we salt it a bit more so that we don't # end up with the same value and generate out 9 digits if num is None: - h.update(b'pinsalt') - num = ('%09d' % int(h.hexdigest(), 16))[:9] + h.update(b"pinsalt") + num = ("%09d" % int(h.hexdigest(), 16))[:9] # Format the pincode in groups of digits for easier remembering if # we don't have a result yet. if rv is None: for group_size in 5, 4, 3: if len(num) % group_size == 0: - rv = '-'.join(num[x:x + group_size].rjust(group_size, '0') - for x in range(0, len(num), group_size)) + rv = "-".join( + num[x : x + group_size].rjust(group_size, "0") + for x in range(0, len(num), group_size) + ) break else: rv = num @@ -240,12 +244,21 @@ class DebuggedApplication(object): :param pin_logging: enables the logging of the pin system. """ - def __init__(self, app, evalex=False, request_key='werkzeug.request', - console_path='/console', console_init_func=None, - show_hidden_frames=False, lodgeit_url=None, - pin_security=True, pin_logging=True): + def __init__( + self, + app, + evalex=False, + request_key="werkzeug.request", + console_path="/console", + console_init_func=None, + show_hidden_frames=False, + lodgeit_url=None, + pin_security=True, + pin_logging=True, + ): if lodgeit_url is not None: from warnings import warn + warn( "'lodgeit_url' is no longer used as of version 0.9 and" " will be removed in version 1.0. Werkzeug uses" @@ -269,19 +282,17 @@ class DebuggedApplication(object): self.pin_logging = pin_logging if pin_security: # Print out the pin for the debugger on standard out. - if os.environ.get('WERKZEUG_RUN_MAIN') == 'true' and \ - pin_logging: - _log('warning', ' * Debugger is active!') + if os.environ.get("WERKZEUG_RUN_MAIN") == "true" and pin_logging: + _log("warning", " * Debugger is active!") if self.pin is None: - _log('warning', ' * Debugger PIN disabled. ' - 'DEBUGGER UNSECURED!') + _log("warning", " * Debugger PIN disabled. DEBUGGER UNSECURED!") else: - _log('info', ' * Debugger PIN: %s' % self.pin) + _log("info", " * Debugger PIN: %s" % self.pin) else: self.pin = None def _get_pin(self): - if not hasattr(self, '_pin'): + if not hasattr(self, "_pin"): self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app) return self._pin @@ -294,7 +305,7 @@ class DebuggedApplication(object): @property def pin_cookie_name(self): """The name of the pin cookie.""" - if not hasattr(self, '_pin_cookie'): + if not hasattr(self, "_pin_cookie"): self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app) return self._pin_cookie @@ -305,46 +316,51 @@ class DebuggedApplication(object): app_iter = self.app(environ, start_response) for item in app_iter: yield item - if hasattr(app_iter, 'close'): + if hasattr(app_iter, "close"): app_iter.close() except Exception: - if hasattr(app_iter, 'close'): + if hasattr(app_iter, "close"): app_iter.close() traceback = get_current_traceback( - skip=1, show_hidden_frames=self.show_hidden_frames, - ignore_system_exceptions=True) + skip=1, + show_hidden_frames=self.show_hidden_frames, + ignore_system_exceptions=True, + ) for frame in traceback.frames: self.frames[frame.id] = frame self.tracebacks[traceback.id] = traceback try: - start_response('500 INTERNAL SERVER ERROR', [ - ('Content-Type', 'text/html; charset=utf-8'), - # Disable Chrome's XSS protection, the debug - # output can cause false-positives. - ('X-XSS-Protection', '0'), - ]) + start_response( + "500 INTERNAL SERVER ERROR", + [ + ("Content-Type", "text/html; charset=utf-8"), + # Disable Chrome's XSS protection, the debug + # output can cause false-positives. + ("X-XSS-Protection", "0"), + ], + ) except Exception: # if we end up here there has been output but an error # occurred. in that situation we can do nothing fancy any # more, better log something into the error log and fall # back gracefully. - environ['wsgi.errors'].write( - 'Debugging middleware caught exception in streamed ' - 'response at a point where response headers were already ' - 'sent.\n') + environ["wsgi.errors"].write( + "Debugging middleware caught exception in streamed " + "response at a point where response headers were already " + "sent.\n" + ) else: is_trusted = bool(self.check_pin_trust(environ)) - yield traceback.render_full(evalex=self.evalex, - evalex_trusted=is_trusted, - secret=self.secret) \ - .encode('utf-8', 'replace') + yield traceback.render_full( + evalex=self.evalex, evalex_trusted=is_trusted, secret=self.secret + ).encode("utf-8", "replace") - traceback.log(environ['wsgi.errors']) + traceback.log(environ["wsgi.errors"]) def execute_command(self, request, command, frame): """Execute a command in a console.""" - return Response(frame.console.eval(command), mimetype='text/html') + return Response(frame.console.eval(command), mimetype="text/html") def display_console(self, request): """Display a standalone shell.""" @@ -353,30 +369,30 @@ class DebuggedApplication(object): ns = {} else: ns = dict(self.console_init_func()) - ns.setdefault('app', self.app) + ns.setdefault("app", self.app) self.frames[0] = _ConsoleFrame(ns) is_trusted = bool(self.check_pin_trust(request.environ)) - return Response(render_console_html(secret=self.secret, - evalex_trusted=is_trusted), - mimetype='text/html') + return Response( + render_console_html(secret=self.secret, evalex_trusted=is_trusted), + mimetype="text/html", + ) def paste_traceback(self, request, traceback): """Paste the traceback and return a JSON response.""" rv = traceback.paste() - return Response(json.dumps(rv), mimetype='application/json') + return Response(json.dumps(rv), mimetype="application/json") def get_resource(self, request, filename): """Return a static resource from the shared folder.""" - filename = join('shared', basename(filename)) + filename = join("shared", basename(filename)) try: data = pkgutil.get_data(__package__, filename) except OSError: data = None if data is not None: - mimetype = mimetypes.guess_type(filename)[0] \ - or 'application/octet-stream' + mimetype = mimetypes.guess_type(filename)[0] or "application/octet-stream" return Response(data, mimetype=mimetype) - return Response('Not Found', status=404) + return Response("Not Found", status=404) def check_pin_trust(self, environ): """Checks if the request passed the pin test. This returns `True` if the @@ -387,9 +403,9 @@ class DebuggedApplication(object): if self.pin is None: return True val = parse_cookie(environ).get(self.pin_cookie_name) - if not val or '|' not in val: + if not val or "|" not in val: return False - ts, pin_hash = val.split('|', 1) + ts, pin_hash = val.split("|", 1) if not ts.isdigit(): return False if pin_hash != hash_pin(self.pin): @@ -426,23 +442,23 @@ class DebuggedApplication(object): # Otherwise go through pin based authentication else: - entered_pin = request.args.get('pin') - if entered_pin.strip().replace('-', '') == \ - self.pin.replace('-', ''): + entered_pin = request.args.get("pin") + if entered_pin.strip().replace("-", "") == self.pin.replace("-", ""): self._failed_pin_auth = 0 auth = True else: self._fail_pin_auth() - rv = Response(json.dumps({ - 'auth': auth, - 'exhausted': exhausted, - }), mimetype='application/json') + rv = Response( + json.dumps({"auth": auth, "exhausted": exhausted}), + mimetype="application/json", + ) if auth: - rv.set_cookie(self.pin_cookie_name, '%s|%s' % ( - int(time.time()), - hash_pin(self.pin) - ), httponly=True) + rv.set_cookie( + self.pin_cookie_name, + "%s|%s" % (int(time.time()), hash_pin(self.pin)), + httponly=True, + ) elif bad_cookie: rv.delete_cookie(self.pin_cookie_name) return rv @@ -450,10 +466,11 @@ class DebuggedApplication(object): def log_pin_request(self): """Log the pin if needed.""" if self.pin_logging and self.pin is not None: - _log('info', ' * To enable the debugger you need to ' - 'enter the security pin:') - _log('info', ' * Debugger pin code: %s' % self.pin) - return Response('') + _log( + "info", " * To enable the debugger you need to enter the security pin:" + ) + _log("info", " * Debugger pin code: %s" % self.pin) + return Response("") def __call__(self, environ, start_response): """Dispatch the requests.""" @@ -462,26 +479,32 @@ class DebuggedApplication(object): # any more! request = Request(environ) response = self.debug_application - if request.args.get('__debugger__') == 'yes': - cmd = request.args.get('cmd') - arg = request.args.get('f') - secret = request.args.get('s') - traceback = self.tracebacks.get(request.args.get('tb', type=int)) - frame = self.frames.get(request.args.get('frm', type=int)) - if cmd == 'resource' and arg: + if request.args.get("__debugger__") == "yes": + cmd = request.args.get("cmd") + arg = request.args.get("f") + secret = request.args.get("s") + traceback = self.tracebacks.get(request.args.get("tb", type=int)) + frame = self.frames.get(request.args.get("frm", type=int)) + if cmd == "resource" and arg: response = self.get_resource(request, arg) - elif cmd == 'paste' and traceback is not None and \ - secret == self.secret: + elif cmd == "paste" and traceback is not None and secret == self.secret: response = self.paste_traceback(request, traceback) - elif cmd == 'pinauth' and secret == self.secret: + elif cmd == "pinauth" and secret == self.secret: response = self.pin_auth(request) - elif cmd == 'printpin' and secret == self.secret: + elif cmd == "printpin" and secret == self.secret: response = self.log_pin_request() - elif self.evalex and cmd is not None and frame is not None \ - and self.secret == secret and \ - self.check_pin_trust(environ): + elif ( + self.evalex + and cmd is not None + and frame is not None + and self.secret == secret + and self.check_pin_trust(environ) + ): response = self.execute_command(request, cmd, frame) - elif self.evalex and self.console_path is not None and \ - request.path == self.console_path: + elif ( + self.evalex + and self.console_path is not None + and request.path == self.console_path + ): response = self.display_console(request) return response(environ, start_response) diff --git a/src/werkzeug/debug/console.py b/src/werkzeug/debug/console.py index 2915aea1..adbd170b 100644 --- a/src/werkzeug/debug/console.py +++ b/src/werkzeug/debug/console.py @@ -8,20 +8,21 @@ :copyright: 2007 Pallets :license: BSD-3-Clause """ -import sys import code +import sys from types import CodeType -from werkzeug.utils import escape -from werkzeug.local import Local -from werkzeug.debug.repr import debug_repr, dump, helper +from ..local import Local +from ..utils import escape +from .repr import debug_repr +from .repr import dump +from .repr import helper _local = Local() class HTMLStringO(object): - """A StringO version that HTML escapes on write.""" def __init__(self): @@ -41,46 +42,46 @@ class HTMLStringO(object): def readline(self): if len(self._buffer) == 0: - return '' + return "" ret = self._buffer[0] del self._buffer[0] return ret def reset(self): - val = ''.join(self._buffer) + val = "".join(self._buffer) del self._buffer[:] return val def _write(self, x): if isinstance(x, bytes): - x = x.decode('utf-8', 'replace') + x = x.decode("utf-8", "replace") self._buffer.append(x) def write(self, x): self._write(escape(x)) def writelines(self, x): - self._write(escape(''.join(x))) + self._write(escape("".join(x))) class ThreadedStream(object): - """Thread-local wrapper for sys.stdout for the interactive console.""" + @staticmethod def push(): if not isinstance(sys.stdout, ThreadedStream): sys.stdout = ThreadedStream() _local.stream = HTMLStringO() - push = staticmethod(push) + @staticmethod def fetch(): try: stream = _local.stream except AttributeError: - return '' + return "" return stream.reset() - fetch = staticmethod(fetch) + @staticmethod def displayhook(obj): try: stream = _local.stream @@ -89,18 +90,17 @@ class ThreadedStream(object): # stream._write bypasses escaping as debug_repr is # already generating HTML for us. if obj is not None: - _local._current_ipy.locals['_'] = obj + _local._current_ipy.locals["_"] = obj stream._write(debug_repr(obj)) - displayhook = staticmethod(displayhook) def __setattr__(self, name, value): - raise AttributeError('read only attribute %s' % name) + raise AttributeError("read only attribute %s" % name) def __dir__(self): return dir(sys.__stdout__) def __getattribute__(self, name): - if name == '__members__': + if name == "__members__": return dir(sys.__stdout__) try: stream = _local.stream @@ -118,7 +118,6 @@ sys.displayhook = ThreadedStream.displayhook class _ConsoleLoader(object): - def __init__(self): self._storage = {} @@ -143,29 +142,30 @@ def _wrap_compiler(console): code = compile(source, filename, symbol) console.loader.register(code, source) return code + console.compile = func class _InteractiveConsole(code.InteractiveInterpreter): - def __init__(self, globals, locals): code.InteractiveInterpreter.__init__(self, locals) self.globals = dict(globals) - self.globals['dump'] = dump - self.globals['help'] = helper - self.globals['__loader__'] = self.loader = _ConsoleLoader() + self.globals["dump"] = dump + self.globals["help"] = helper + self.globals["__loader__"] = self.loader = _ConsoleLoader() self.more = False self.buffer = [] _wrap_compiler(self) def runsource(self, source): - source = source.rstrip() + '\n' + source = source.rstrip() + "\n" ThreadedStream.push() - prompt = '... ' if self.more else '>>> ' + prompt = "... " if self.more else ">>> " try: - source_to_eval = ''.join(self.buffer + [source]) - if code.InteractiveInterpreter.runsource(self, - source_to_eval, '<debugger>', 'single'): + source_to_eval = "".join(self.buffer + [source]) + if code.InteractiveInterpreter.runsource( + self, source_to_eval, "<debugger>", "single" + ): self.more = True self.buffer.append(source) else: @@ -182,12 +182,14 @@ class _InteractiveConsole(code.InteractiveInterpreter): self.showtraceback() def showtraceback(self): - from werkzeug.debug.tbtools import get_current_traceback + from .tbtools import get_current_traceback + tb = get_current_traceback(skip=1) sys.stdout._write(tb.render_summary()) def showsyntaxerror(self, filename=None): - from werkzeug.debug.tbtools import get_current_traceback + from .tbtools import get_current_traceback + tb = get_current_traceback(skip=4) sys.stdout._write(tb.render_summary()) @@ -196,7 +198,6 @@ class _InteractiveConsole(code.InteractiveInterpreter): class Console(object): - """An interactive console.""" def __init__(self, globals=None, locals=None): diff --git a/src/werkzeug/debug/repr.py b/src/werkzeug/debug/repr.py index e82d87c4..d7a7285c 100644 --- a/src/werkzeug/debug/repr.py +++ b/src/werkzeug/debug/repr.py @@ -13,37 +13,38 @@ :copyright: 2007 Pallets :license: BSD-3-Clause """ -import sys -import re import codecs +import re +import sys +from collections import deque from traceback import format_exception_only -try: - from collections import deque -except ImportError: # pragma: no cover - deque = None -from werkzeug.utils import escape -from werkzeug._compat import iteritems, PY2, text_type, integer_types, \ - string_types + +from .._compat import integer_types +from .._compat import iteritems +from .._compat import PY2 +from .._compat import string_types +from .._compat import text_type +from ..utils import escape missing = object() -_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}') +_paragraph_re = re.compile(r"(?:\r\n|\r|\n){2,}") RegexType = type(_paragraph_re) -HELP_HTML = '''\ +HELP_HTML = """\ <div class=box> <h3>%(title)s</h3> <pre class=help>%(text)s</pre> </div>\ -''' -OBJECT_DUMP_HTML = '''\ +""" +OBJECT_DUMP_HTML = """\ <div class=box> <h3>%(title)s</h3> %(repr)s <table>%(items)s</table> </div>\ -''' +""" def debug_repr(obj): @@ -64,31 +65,31 @@ def dump(obj=missing): class _Helper(object): - """Displays an HTML version of the normal help, for the interactive debugger only because it requires a patched sys.stdout. """ def __repr__(self): - return 'Type help(object) for help about object.' + return "Type help(object) for help about object." def __call__(self, topic=None): if topic is None: - sys.stdout._write('<span class=help>%s</span>' % repr(self)) + sys.stdout._write("<span class=help>%s</span>" % repr(self)) return import pydoc + pydoc.help(topic) rv = sys.stdout.reset() if isinstance(rv, bytes): - rv = rv.decode('utf-8', 'ignore') + rv = rv.decode("utf-8", "ignore") paragraphs = _paragraph_re.split(rv) if len(paragraphs) > 1: title = paragraphs[0] - text = '\n\n'.join(paragraphs[1:]) + text = "\n\n".join(paragraphs[1:]) else: # pragma: no cover - title = 'Help' + title = "Help" text = paragraphs[0] - sys.stdout._write(HELP_HTML % {'title': title, 'text': text}) + sys.stdout._write(HELP_HTML % {"title": title, "text": text}) helper = _Helper() @@ -101,55 +102,55 @@ def _add_subclass_info(inner, obj, base): return inner elif type(obj) is base: return inner - module = '' - if obj.__class__.__module__ not in ('__builtin__', 'exceptions'): + module = "" + if obj.__class__.__module__ not in ("__builtin__", "exceptions"): module = '<span class="module">%s.</span>' % obj.__class__.__module__ - return '%s%s(%s)' % (module, obj.__class__.__name__, inner) + return "%s%s(%s)" % (module, obj.__class__.__name__, inner) class DebugReprGenerator(object): - def __init__(self): self._stack = [] - def _sequence_repr_maker(left, right, base=object(), limit=8): + def _sequence_repr_maker(left, right, base=object(), limit=8): # noqa: B008, B902 def proxy(self, obj, recursive): if recursive: - return _add_subclass_info(left + '...' + right, obj, base) + return _add_subclass_info(left + "..." + right, obj, base) buf = [left] have_extended_section = False for idx, item in enumerate(obj): if idx: - buf.append(', ') + buf.append(", ") if idx == limit: buf.append('<span class="extended">') have_extended_section = True buf.append(self.repr(item)) if have_extended_section: - buf.append('</span>') + buf.append("</span>") buf.append(right) - return _add_subclass_info(u''.join(buf), obj, base) + return _add_subclass_info(u"".join(buf), obj, base) + return proxy - list_repr = _sequence_repr_maker('[', ']', list) - tuple_repr = _sequence_repr_maker('(', ')', tuple) - set_repr = _sequence_repr_maker('set([', '])', set) - frozenset_repr = _sequence_repr_maker('frozenset([', '])', frozenset) - if deque is not None: - deque_repr = _sequence_repr_maker('<span class="module">collections.' - '</span>deque([', '])', deque) + list_repr = _sequence_repr_maker("[", "]", list) + tuple_repr = _sequence_repr_maker("(", ")", tuple) + set_repr = _sequence_repr_maker("set([", "])", set) + frozenset_repr = _sequence_repr_maker("frozenset([", "])", frozenset) + deque_repr = _sequence_repr_maker( + '<span class="module">collections.' "</span>deque([", "])", deque + ) del _sequence_repr_maker def regex_repr(self, obj): pattern = repr(obj.pattern) if PY2: - pattern = pattern.decode('string-escape', 'ignore') + pattern = pattern.decode("string-escape", "ignore") else: - pattern = codecs.decode(pattern, 'unicode-escape', 'ignore') - if pattern[:1] == 'u': - pattern = 'ur' + pattern[1:] + pattern = codecs.decode(pattern, "unicode-escape", "ignore") + if pattern[:1] == "u": + pattern = "ur" + pattern[1:] else: - pattern = 'r' + pattern + pattern = "r" + pattern return u're.compile(<span class="string regex">%s</span>)' % pattern def string_repr(self, obj, limit=70): @@ -158,14 +159,18 @@ class DebugReprGenerator(object): # shorten the repr when the hidden part would be at least 3 chars if len(r) - limit > 2: - buf.extend(( - escape(r[:limit]), - '<span class="extended">', escape(r[limit:]), '</span>', - )) + buf.extend( + ( + escape(r[:limit]), + '<span class="extended">', + escape(r[limit:]), + "</span>", + ) + ) else: buf.append(escape(r)) - buf.append('</span>') + buf.append("</span>") out = u"".join(buf) # if the repr looks like a standard string, add subclass info if needed @@ -177,27 +182,29 @@ class DebugReprGenerator(object): def dict_repr(self, d, recursive, limit=5): if recursive: - return _add_subclass_info(u'{...}', d, dict) - buf = ['{'] + return _add_subclass_info(u"{...}", d, dict) + buf = ["{"] have_extended_section = False for idx, (key, value) in enumerate(iteritems(d)): if idx: - buf.append(', ') + buf.append(", ") if idx == limit - 1: buf.append('<span class="extended">') have_extended_section = True - buf.append('<span class="pair"><span class="key">%s</span>: ' - '<span class="value">%s</span></span>' % - (self.repr(key), self.repr(value))) + buf.append( + '<span class="pair"><span class="key">%s</span>: ' + '<span class="value">%s</span></span>' + % (self.repr(key), self.repr(value)) + ) if have_extended_section: - buf.append('</span>') - buf.append('}') - return _add_subclass_info(u''.join(buf), d, dict) + buf.append("</span>") + buf.append("}") + return _add_subclass_info(u"".join(buf), d, dict) def object_repr(self, obj): r = repr(obj) if PY2: - r = r.decode('utf-8', 'replace') + r = r.decode("utf-8", "replace") return u'<span class="object">%s</span>' % escape(r) def dispatch_repr(self, obj, recursive): @@ -225,13 +232,14 @@ class DebugReprGenerator(object): def fallback_repr(self): try: - info = ''.join(format_exception_only(*sys.exc_info()[:2])) + info = "".join(format_exception_only(*sys.exc_info()[:2])) except Exception: # pragma: no cover - info = '?' + info = "?" if PY2: - info = info.decode('utf-8', 'ignore') - return u'<span class="brokenrepr"><broken repr (%s)>' \ - u'</span>' % escape(info.strip()) + info = info.decode("utf-8", "ignore") + return u'<span class="brokenrepr"><broken repr (%s)>' u"</span>" % escape( + info.strip() + ) def repr(self, obj): recursive = False @@ -251,7 +259,7 @@ class DebugReprGenerator(object): def dump_object(self, obj): repr = items = None if isinstance(obj, dict): - title = 'Contents of' + title = "Contents of" items = [] for key, value in iteritems(obj): if not isinstance(key, string_types): @@ -266,23 +274,24 @@ class DebugReprGenerator(object): items.append((key, self.repr(getattr(obj, key)))) except Exception: pass - title = 'Details for' - title += ' ' + object.__repr__(obj)[1:-1] + title = "Details for" + title += " " + object.__repr__(obj)[1:-1] return self.render_object_dump(items, title, repr) def dump_locals(self, d): items = [(key, self.repr(value)) for key, value in d.items()] - return self.render_object_dump(items, 'Local variables in frame') + return self.render_object_dump(items, "Local variables in frame") def render_object_dump(self, items, title, repr=None): html_items = [] for key, value in items: - html_items.append('<tr><th>%s<td><pre class=repr>%s</pre>' % - (escape(key), value)) + html_items.append( + "<tr><th>%s<td><pre class=repr>%s</pre>" % (escape(key), value) + ) if not html_items: - html_items.append('<tr><td><em>Nothing</em>') + html_items.append("<tr><td><em>Nothing</em>") return OBJECT_DUMP_HTML % { - 'title': escape(title), - 'repr': '<pre class=repr>%s</pre>' % repr if repr else '', - 'items': '\n'.join(html_items) + "title": escape(title), + "repr": "<pre class=repr>%s</pre>" % repr if repr else "", + "items": "\n".join(html_items), } diff --git a/src/werkzeug/debug/tbtools.py b/src/werkzeug/debug/tbtools.py index 9422052f..f6af4e30 100644 --- a/src/werkzeug/debug/tbtools.py +++ b/src/werkzeug/debug/tbtools.py @@ -8,30 +8,33 @@ :copyright: 2007 Pallets :license: BSD-3-Clause """ -import re - +import codecs +import inspect +import json import os +import re import sys -import json -import inspect import sysconfig import traceback -import codecs from tokenize import TokenError -from werkzeug.utils import cached_property, escape -from werkzeug.debug.console import Console -from werkzeug._compat import ( - range_type, PY2, text_type, string_types, - to_native, to_unicode, reraise, -) -from werkzeug.filesystem import get_filesystem_encoding +from .._compat import PY2 +from .._compat import range_type +from .._compat import reraise +from .._compat import string_types +from .._compat import text_type +from .._compat import to_native +from .._compat import to_unicode +from ..filesystem import get_filesystem_encoding +from ..utils import cached_property +from ..utils import escape +from .console import Console -_coding_re = re.compile(br'coding[:=]\s*([-\w.]+)') -_line_re = re.compile(br'^(.*?)$', re.MULTILINE) -_funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') -UTF8_COOKIE = b'\xef\xbb\xbf' +_coding_re = re.compile(br"coding[:=]\s*([-\w.]+)") +_line_re = re.compile(br"^(.*?)$", re.MULTILINE) +_funcdef_re = re.compile(r"^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)") +UTF8_COOKIE = b"\xef\xbb\xbf" system_exceptions = (SystemExit, KeyboardInterrupt) try: @@ -40,7 +43,7 @@ except NameError: pass -HEADER = u'''\ +HEADER = u"""\ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> @@ -65,8 +68,8 @@ HEADER = u'''\ </head> <body style="background-color: #fff"> <div class="debugger"> -''' -FOOTER = u'''\ +""" +FOOTER = u"""\ <div class="footer"> Brought to you by <strong class="arthur">DON'T PANIC</strong>, your friendly Werkzeug powered traceback interpreter. @@ -89,9 +92,11 @@ FOOTER = u'''\ </div> </body> </html> -''' +""" -PAGE_HTML = HEADER + u'''\ +PAGE_HTML = ( + HEADER + + u"""\ <h1>%(exception_type)s</h1> <div class="detail"> <p class="errormsg">%(exception)s</p> @@ -117,61 +122,69 @@ PAGE_HTML = HEADER + u'''\ execution (if the evalex feature is enabled), automatic pasting of the exceptions and much more.</span> </div> -''' + FOOTER + ''' +""" + + FOOTER + + """ <!-- %(plaintext_cs)s --> -''' +""" +) -CONSOLE_HTML = HEADER + u'''\ +CONSOLE_HTML = ( + HEADER + + u"""\ <h1>Interactive Console</h1> <div class="explanation"> In this console you can execute Python expressions in the context of the application. The initial namespace was created by the debugger automatically. </div> <div class="console"><div class="inner">The Console requires JavaScript.</div></div> -''' + FOOTER +""" + + FOOTER +) -SUMMARY_HTML = u'''\ +SUMMARY_HTML = u"""\ <div class="%(classes)s"> %(title)s <ul>%(frames)s</ul> %(description)s </div> -''' +""" -FRAME_HTML = u'''\ +FRAME_HTML = u"""\ <div class="frame" id="frame-%(id)d"> <h4>File <cite class="filename">"%(filename)s"</cite>, line <em class="line">%(lineno)s</em>, in <code class="function">%(function_name)s</code></h4> <div class="source %(library)s">%(lines)s</div> </div> -''' +""" -SOURCE_LINE_HTML = u'''\ +SOURCE_LINE_HTML = u"""\ <tr class="%(classes)s"> <td class=lineno>%(lineno)s</td> <td>%(code)s</td> </tr> -''' +""" def render_console_html(secret, evalex_trusted=True): return CONSOLE_HTML % { - 'evalex': 'true', - 'evalex_trusted': 'true' if evalex_trusted else 'false', - 'console': 'true', - 'title': 'Console', - 'secret': secret, - 'traceback_id': -1 + "evalex": "true", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "true", + "title": "Console", + "secret": secret, + "traceback_id": -1, } -def get_current_traceback(ignore_system_exceptions=False, - show_hidden_frames=False, skip=0): +def get_current_traceback( + ignore_system_exceptions=False, show_hidden_frames=False, skip=0 +): """Get the current exception info as `Traceback` object. Per default calling this method will reraise system exceptions such as generator exit, system exit or others. This behavior can be disabled by passing `False` @@ -192,7 +205,8 @@ def get_current_traceback(ignore_system_exceptions=False, class Line(object): """Helper for the source renderer.""" - __slots__ = ('lineno', 'code', 'in_frame', 'current') + + __slots__ = ("lineno", "code", "in_frame", "current") def __init__(self, lineno, code): self.lineno = lineno @@ -202,18 +216,18 @@ class Line(object): @property def classes(self): - rv = ['line'] + rv = ["line"] if self.in_frame: - rv.append('in-frame') + rv.append("in-frame") if self.current: - rv.append('current') + rv.append("current") return rv def render(self): return SOURCE_LINE_HTML % { - 'classes': u' '.join(self.classes), - 'lineno': self.lineno, - 'code': escape(self.code) + "classes": u" ".join(self.classes), + "lineno": self.lineno, + "code": escape(self.code), } @@ -227,7 +241,7 @@ class Traceback(object): exception_type = exc_type.__name__ if exc_type.__module__ not in {"builtins", "__builtin__", "exceptions"}: - exception_type = exc_type.__module__ + '.' + exception_type + exception_type = exc_type.__module__ + "." + exception_type self.exception_type = exception_type self.groups = [] @@ -264,38 +278,33 @@ class Traceback(object): """Log the ASCII traceback into a file object.""" if logfile is None: logfile = sys.stderr - tb = self.plaintext.rstrip() + u'\n' + tb = self.plaintext.rstrip() + u"\n" logfile.write(to_native(tb, "utf-8", "replace")) def paste(self): """Create a paste and return the paste id.""" - data = json.dumps({ - 'description': 'Werkzeug Internal Server Error', - 'public': False, - 'files': { - 'traceback.txt': { - 'content': self.plaintext - } + data = json.dumps( + { + "description": "Werkzeug Internal Server Error", + "public": False, + "files": {"traceback.txt": {"content": self.plaintext}}, } - }).encode('utf-8') + ).encode("utf-8") try: from urllib2 import urlopen except ImportError: from urllib.request import urlopen - rv = urlopen('https://api.github.com/gists', data=data) - resp = json.loads(rv.read().decode('utf-8')) + rv = urlopen("https://api.github.com/gists", data=data) + resp = json.loads(rv.read().decode("utf-8")) rv.close() - return { - 'url': resp['html_url'], - 'id': resp['id'] - } + return {"url": resp["html_url"], "id": resp["id"]} def render_summary(self, include_title=True): """Render the traceback for the interactive console.""" - title = '' - classes = ['traceback'] + title = "" + classes = ["traceback"] if not self.frames: - classes.append('noframe-traceback') + classes.append("noframe-traceback") frames = [] else: library_frames = sum(frame.is_library for frame in self.frames) @@ -304,38 +313,37 @@ class Traceback(object): if include_title: if self.is_syntax_error: - title = u'Syntax Error' + title = u"Syntax Error" else: - title = u'Traceback <em>(most recent call last)</em>:' + title = u"Traceback <em>(most recent call last)</em>:" if self.is_syntax_error: - description_wrapper = u'<pre class=syntaxerror>%s</pre>' + description_wrapper = u"<pre class=syntaxerror>%s</pre>" else: - description_wrapper = u'<blockquote>%s</blockquote>' + description_wrapper = u"<blockquote>%s</blockquote>" return SUMMARY_HTML % { - 'classes': u' '.join(classes), - 'title': u'<h3>%s</h3>' % title if title else u'', - 'frames': u'\n'.join(frames), - 'description': description_wrapper % escape(self.exception) + "classes": u" ".join(classes), + "title": u"<h3>%s</h3>" % title if title else u"", + "frames": u"\n".join(frames), + "description": description_wrapper % escape(self.exception), } - def render_full(self, evalex=False, secret=None, - evalex_trusted=True): + def render_full(self, evalex=False, secret=None, evalex_trusted=True): """Render the Full HTML page with the traceback info.""" exc = escape(self.exception) return PAGE_HTML % { - 'evalex': 'true' if evalex else 'false', - 'evalex_trusted': 'true' if evalex_trusted else 'false', - 'console': 'false', - 'title': exc, - 'exception': exc, - 'exception_type': escape(self.exception_type), - 'summary': self.render_summary(include_title=False), - 'plaintext': escape(self.plaintext), - 'plaintext_cs': re.sub('-{2,}', '-', self.plaintext), - 'traceback_id': self.id, - 'secret': secret + "evalex": "true" if evalex else "false", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "false", + "title": exc, + "exception": exc, + "exception_type": escape(self.exception_type), + "summary": self.render_summary(include_title=False), + "plaintext": escape(self.plaintext), + "plaintext_cs": re.sub("-{2,}", "-", self.plaintext), + "traceback_id": self.id, + "secret": secret, } @cached_property @@ -418,10 +426,13 @@ class Group(object): if self.info is not None: out.append(u'<li><div class="exc-divider">%s:</div>' % self.info) for frame in self.frames: - out.append(u"<li%s>%s" % ( - u' title="%s"' % escape(frame.info) if frame.info else u"", - frame.render(mark_lib=mark_lib) - )) + out.append( + u"<li%s>%s" + % ( + u' title="%s"' % escape(frame.info) if frame.info else u"", + frame.render(mark_lib=mark_lib), + ) + ) return u"\n".join(out) def render_text(self): @@ -436,7 +447,6 @@ class Group(object): class Frame(object): - """A single frame in a traceback.""" def __init__(self, exc_type, exc_value, tb): @@ -446,19 +456,19 @@ class Frame(object): self.globals = tb.tb_frame.f_globals fn = inspect.getsourcefile(tb) or inspect.getfile(tb) - if fn[-4:] in ('.pyo', '.pyc'): + if fn[-4:] in (".pyo", ".pyc"): fn = fn[:-1] # if it's a file on the file system resolve the real filename. if os.path.isfile(fn): fn = os.path.realpath(fn) self.filename = to_unicode(fn, get_filesystem_encoding()) - self.module = self.globals.get('__name__') - self.loader = self.globals.get('__loader__') + self.module = self.globals.get("__name__") + self.loader = self.globals.get("__loader__") self.code = tb.tb_frame.f_code # support for paste's traceback extensions - self.hide = self.locals.get('__traceback_hide__', False) - info = self.locals.get('__traceback_info__') + self.hide = self.locals.get("__traceback_hide__", False) + info = self.locals.get("__traceback_info__") if info is not None: info = to_unicode(info, "utf-8", "replace") self.info = info @@ -466,11 +476,11 @@ class Frame(object): def render(self, mark_lib=True): """Render a single frame in a traceback.""" return FRAME_HTML % { - 'id': self.id, - 'filename': escape(self.filename), - 'lineno': self.lineno, - 'function_name': escape(self.function_name), - 'lines': self.render_line_context(), + "id": self.id, + "filename": escape(self.filename), + "lineno": self.lineno, + "function_name": escape(self.function_name), + "lines": self.render_line_context(), "library": "library" if mark_lib and self.is_library else "", } @@ -485,7 +495,7 @@ class Frame(object): self.filename, self.lineno, self.function_name, - self.current_line.strip() + self.current_line.strip(), ) def render_line_context(self): @@ -497,34 +507,34 @@ class Frame(object): stripped_line = line.strip() prefix = len(line) - len(stripped_line) rv.append( - '<pre class="line %s"><span class="ws">%s</span>%s</pre>' % ( - cls, ' ' * prefix, escape(stripped_line) or ' ')) + '<pre class="line %s"><span class="ws">%s</span>%s</pre>' + % (cls, " " * prefix, escape(stripped_line) or " ") + ) for line in before: - render_line(line, 'before') - render_line(current, 'current') + render_line(line, "before") + render_line(current, "current") for line in after: - render_line(line, 'after') + render_line(line, "after") - return '\n'.join(rv) + return "\n".join(rv) def get_annotated_lines(self): """Helper function that returns lines with extra information.""" lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)] # find function definition and mark lines - if hasattr(self.code, 'co_firstlineno'): + if hasattr(self.code, "co_firstlineno"): lineno = self.code.co_firstlineno - 1 while lineno > 0: if _funcdef_re.match(lines[lineno].code): break lineno -= 1 try: - offset = len(inspect.getblock([x.code + '\n' for x - in lines[lineno:]])) + offset = len(inspect.getblock([x.code + "\n" for x in lines[lineno:]])) except TokenError: offset = 0 - for line in lines[lineno:lineno + offset]: + for line in lines[lineno : lineno + offset]: line.in_frame = True # mark current line @@ -535,12 +545,12 @@ class Frame(object): return lines - def eval(self, code, mode='single'): + def eval(self, code, mode="single"): """Evaluate code in the context of the frame.""" if isinstance(code, string_types): if PY2 and isinstance(code, text_type): # noqa - code = UTF8_COOKIE + code.encode('utf-8') - code = compile(code, '<interactive>', mode) + code = UTF8_COOKIE + code.encode("utf-8") + code = compile(code, "<interactive>", mode) return eval(code, self.globals, self.locals) @cached_property @@ -550,9 +560,9 @@ class Frame(object): source = None if self.loader is not None: try: - if hasattr(self.loader, 'get_source'): + if hasattr(self.loader, "get_source"): source = self.loader.get_source(self.module) - elif hasattr(self.loader, 'get_source_by_code'): + elif hasattr(self.loader, "get_source_by_code"): source = self.loader.get_source_by_code(self.code) except Exception: # we munch the exception so that we don't cause troubles @@ -561,8 +571,7 @@ class Frame(object): if source is None: try: - f = open(to_native(self.filename, get_filesystem_encoding()), - mode='rb') + f = open(to_native(self.filename, get_filesystem_encoding()), mode="rb") except IOError: return [] try: @@ -576,7 +585,7 @@ class Frame(object): # yes. it should be ascii, but we don't want to reject too many # characters in the debugger if something breaks - charset = 'utf-8' + charset = "utf-8" if source.startswith(UTF8_COOKIE): source = source[3:] else: @@ -593,25 +602,21 @@ class Frame(object): try: codecs.lookup(charset) except LookupError: - charset = 'utf-8' + charset = "utf-8" - return source.decode(charset, 'replace').splitlines() + return source.decode(charset, "replace").splitlines() def get_context_lines(self, context=5): - before = self.sourcelines[self.lineno - context - 1:self.lineno - 1] - past = self.sourcelines[self.lineno:self.lineno + context] - return ( - before, - self.current_line, - past, - ) + before = self.sourcelines[self.lineno - context - 1 : self.lineno - 1] + past = self.sourcelines[self.lineno : self.lineno + context] + return (before, self.current_line, past) @property def current_line(self): try: return self.sourcelines[self.lineno - 1] except IndexError: - return u'' + return u"" @cached_property def console(self): |
