diff options
Diffstat (limited to 'django/views/debug.py')
| -rw-r--r-- | django/views/debug.py | 296 |
1 files changed, 167 insertions, 129 deletions
diff --git a/django/views/debug.py b/django/views/debug.py index 34bd57b33d..e30fd65095 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -23,7 +23,7 @@ from django.utils.version import get_docs_version # works even if the template loader is broken. DEBUG_ENGINE = Engine( debug=True, - libraries={'i18n': 'django.templatetags.i18n'}, + libraries={"i18n": "django.templatetags.i18n"}, ) @@ -34,7 +34,7 @@ def builtin_template_path(name): Avoid calling this function at the module level or in a class-definition because __file__ may not exist, e.g. in frozen environments. """ - return Path(__file__).parent / 'templates' / name + return Path(__file__).parent / "templates" / name class ExceptionCycleWarning(UserWarning): @@ -48,6 +48,7 @@ class CallableSettingWrapper: * Not to break the debug page if the callable forbidding to set attributes (#23070). """ + def __init__(self, callable_setting): self._wrapped = callable_setting @@ -61,12 +62,14 @@ def technical_500_response(request, exc_type, exc_value, tb, status_code=500): the values returned from sys.exc_info() and friends. """ reporter = get_exception_reporter_class(request)(request, exc_type, exc_value, tb) - if request.accepts('text/html'): + if request.accepts("text/html"): html = reporter.get_traceback_html() - return HttpResponse(html, status=status_code, content_type='text/html') + return HttpResponse(html, status=status_code, content_type="text/html") else: text = reporter.get_traceback_text() - return HttpResponse(text, status=status_code, content_type='text/plain; charset=utf-8') + return HttpResponse( + text, status=status_code, content_type="text/plain; charset=utf-8" + ) @functools.lru_cache @@ -77,12 +80,16 @@ def get_default_exception_reporter_filter(): def get_exception_reporter_filter(request): default_filter = get_default_exception_reporter_filter() - return getattr(request, 'exception_reporter_filter', default_filter) + return getattr(request, "exception_reporter_filter", default_filter) def get_exception_reporter_class(request): - default_exception_reporter_class = import_string(settings.DEFAULT_EXCEPTION_REPORTER) - return getattr(request, 'exception_reporter_class', default_exception_reporter_class) + default_exception_reporter_class = import_string( + settings.DEFAULT_EXCEPTION_REPORTER + ) + return getattr( + request, "exception_reporter_class", default_exception_reporter_class + ) def get_caller(request): @@ -92,7 +99,7 @@ def get_caller(request): resolver_match = resolve(request.path) except Http404: pass - return '' if resolver_match is None else resolver_match._func_path + return "" if resolver_match is None else resolver_match._func_path class SafeExceptionReporterFilter: @@ -100,8 +107,11 @@ class SafeExceptionReporterFilter: Use annotations made by the sensitive_post_parameters and sensitive_variables decorators to filter out sensitive information. """ - cleansed_substitute = '********************' - hidden_settings = _lazy_re_compile('API|TOKEN|KEY|SECRET|PASS|SIGNATURE', flags=re.I) + + cleansed_substitute = "********************" + hidden_settings = _lazy_re_compile( + "API|TOKEN|KEY|SECRET|PASS|SIGNATURE", flags=re.I + ) def cleanse_setting(self, key, value): """ @@ -118,9 +128,9 @@ class SafeExceptionReporterFilter: elif isinstance(value, dict): cleansed = {k: self.cleanse_setting(k, v) for k, v in value.items()} elif isinstance(value, list): - cleansed = [self.cleanse_setting('', v) for v in value] + cleansed = [self.cleanse_setting("", v) for v in value] elif isinstance(value, tuple): - cleansed = tuple([self.cleanse_setting('', v) for v in value]) + cleansed = tuple([self.cleanse_setting("", v) for v in value]) else: cleansed = value @@ -144,7 +154,7 @@ class SafeExceptionReporterFilter: """ Return a dictionary of request.META with sensitive values redacted. """ - if not hasattr(request, 'META'): + if not hasattr(request, "META"): return {} return {k: self.cleanse_setting(k, v) for k, v in request.META.items()} @@ -163,7 +173,7 @@ class SafeExceptionReporterFilter: This mitigates leaking sensitive POST parameters if something like request.POST['nonexistent_key'] throws an exception (#21098). """ - sensitive_post_parameters = getattr(request, 'sensitive_post_parameters', []) + sensitive_post_parameters = getattr(request, "sensitive_post_parameters", []) if self.is_active(request) and sensitive_post_parameters: multivaluedict = multivaluedict.copy() for param in sensitive_post_parameters: @@ -179,10 +189,12 @@ class SafeExceptionReporterFilter: if request is None: return {} else: - sensitive_post_parameters = getattr(request, 'sensitive_post_parameters', []) + sensitive_post_parameters = getattr( + request, "sensitive_post_parameters", [] + ) if self.is_active(request) and sensitive_post_parameters: cleansed = request.POST.copy() - if sensitive_post_parameters == '__ALL__': + if sensitive_post_parameters == "__ALL__": # Cleanse all parameters. for k in cleansed: cleansed[k] = self.cleansed_substitute @@ -203,7 +215,7 @@ class SafeExceptionReporterFilter: # MultiValueDicts will have a return value. is_multivalue_dict = isinstance(value, MultiValueDict) except Exception as e: - return '{!r} while evaluating {!r}'.format(e, value) + return "{!r} while evaluating {!r}".format(e, value) if is_multivalue_dict: # Cleanse MultiValueDicts (request.POST is the one we usually care about) @@ -220,18 +232,20 @@ class SafeExceptionReporterFilter: current_frame = tb_frame.f_back sensitive_variables = None while current_frame is not None: - if (current_frame.f_code.co_name == 'sensitive_variables_wrapper' and - 'sensitive_variables_wrapper' in current_frame.f_locals): + if ( + current_frame.f_code.co_name == "sensitive_variables_wrapper" + and "sensitive_variables_wrapper" in current_frame.f_locals + ): # The sensitive_variables decorator was used, so we take note # of the sensitive variables' names. - wrapper = current_frame.f_locals['sensitive_variables_wrapper'] - sensitive_variables = getattr(wrapper, 'sensitive_variables', None) + wrapper = current_frame.f_locals["sensitive_variables_wrapper"] + sensitive_variables = getattr(wrapper, "sensitive_variables", None) break current_frame = current_frame.f_back cleansed = {} if self.is_active(request) and sensitive_variables: - if sensitive_variables == '__ALL__': + if sensitive_variables == "__ALL__": # Cleanse all variables for name in tb_frame.f_locals: cleansed[name] = self.cleansed_substitute @@ -249,14 +263,16 @@ class SafeExceptionReporterFilter: for name, value in tb_frame.f_locals.items(): cleansed[name] = self.cleanse_special_types(request, value) - if (tb_frame.f_code.co_name == 'sensitive_variables_wrapper' and - 'sensitive_variables_wrapper' in tb_frame.f_locals): + if ( + tb_frame.f_code.co_name == "sensitive_variables_wrapper" + and "sensitive_variables_wrapper" in tb_frame.f_locals + ): # For good measure, obfuscate the decorated function's arguments in # the sensitive_variables decorator's frame, in case the variables # associated with those arguments were meant to be obfuscated from # the decorated function's frame. - cleansed['func_args'] = self.cleansed_substitute - cleansed['func_kwargs'] = self.cleansed_substitute + cleansed["func_args"] = self.cleansed_substitute + cleansed["func_kwargs"] = self.cleansed_substitute return cleansed.items() @@ -266,11 +282,11 @@ class ExceptionReporter: @property def html_template_path(self): - return builtin_template_path('technical_500.html') + return builtin_template_path("technical_500.html") @property def text_template_path(self): - return builtin_template_path('technical_500.txt') + return builtin_template_path("technical_500.txt") def __init__(self, request, exc_type, exc_value, tb, is_email=False): self.request = request @@ -280,7 +296,7 @@ class ExceptionReporter: self.tb = tb self.is_email = is_email - self.template_info = getattr(self.exc_value, 'template_debug', None) + self.template_info = getattr(self.exc_value, "template_debug", None) self.template_does_not_exist = False self.postmortem = None @@ -289,7 +305,7 @@ class ExceptionReporter: Return an absolute URI from variables available in this request. Skip allowed hosts protection, so may return insecure URI. """ - return '{scheme}://{host}{path}'.format( + return "{scheme}://{host}{path}".format( scheme=self.request.scheme, host=self.request._get_raw_host(), path=self.request.get_full_path(), @@ -303,26 +319,27 @@ class ExceptionReporter: frames = self.get_traceback_frames() for i, frame in enumerate(frames): - if 'vars' in frame: + if "vars" in frame: frame_vars = [] - for k, v in frame['vars']: + for k, v in frame["vars"]: v = pprint(v) # Trim large blobs of data if len(v) > 4096: - v = '%s… <trimmed %d bytes string>' % (v[0:4096], len(v)) + v = "%s… <trimmed %d bytes string>" % (v[0:4096], len(v)) frame_vars.append((k, v)) - frame['vars'] = frame_vars + frame["vars"] = frame_vars frames[i] = frame - unicode_hint = '' + unicode_hint = "" if self.exc_type and issubclass(self.exc_type, UnicodeError): - start = getattr(self.exc_value, 'start', None) - end = getattr(self.exc_value, 'end', None) + start = getattr(self.exc_value, "start", None) + end = getattr(self.exc_value, "end", None) if start is not None and end is not None: unicode_str = self.exc_value.args[1] unicode_hint = force_str( - unicode_str[max(start - 5, 0):min(end + 5, len(unicode_str))], - 'ascii', errors='replace' + unicode_str[max(start - 5, 0) : min(end + 5, len(unicode_str))], + "ascii", + errors="replace", ) from django import get_version @@ -334,59 +351,61 @@ class ExceptionReporter: except Exception: # request.user may raise OperationalError if the database is # unavailable, for example. - user_str = '[unable to retrieve the current user]' + user_str = "[unable to retrieve the current user]" c = { - 'is_email': self.is_email, - 'unicode_hint': unicode_hint, - 'frames': frames, - 'request': self.request, - 'request_meta': self.filter.get_safe_request_meta(self.request), - 'user_str': user_str, - 'filtered_POST_items': list(self.filter.get_post_parameters(self.request).items()), - 'settings': self.filter.get_safe_settings(), - 'sys_executable': sys.executable, - 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], - 'server_time': timezone.now(), - 'django_version_info': get_version(), - 'sys_path': sys.path, - 'template_info': self.template_info, - 'template_does_not_exist': self.template_does_not_exist, - 'postmortem': self.postmortem, + "is_email": self.is_email, + "unicode_hint": unicode_hint, + "frames": frames, + "request": self.request, + "request_meta": self.filter.get_safe_request_meta(self.request), + "user_str": user_str, + "filtered_POST_items": list( + self.filter.get_post_parameters(self.request).items() + ), + "settings": self.filter.get_safe_settings(), + "sys_executable": sys.executable, + "sys_version_info": "%d.%d.%d" % sys.version_info[0:3], + "server_time": timezone.now(), + "django_version_info": get_version(), + "sys_path": sys.path, + "template_info": self.template_info, + "template_does_not_exist": self.template_does_not_exist, + "postmortem": self.postmortem, } if self.request is not None: - c['request_GET_items'] = self.request.GET.items() - c['request_FILES_items'] = self.request.FILES.items() - c['request_COOKIES_items'] = self.request.COOKIES.items() - c['request_insecure_uri'] = self._get_raw_insecure_uri() - c['raising_view_name'] = get_caller(self.request) + c["request_GET_items"] = self.request.GET.items() + c["request_FILES_items"] = self.request.FILES.items() + c["request_COOKIES_items"] = self.request.COOKIES.items() + c["request_insecure_uri"] = self._get_raw_insecure_uri() + c["raising_view_name"] = get_caller(self.request) # Check whether exception info is available if self.exc_type: - c['exception_type'] = self.exc_type.__name__ + c["exception_type"] = self.exc_type.__name__ if self.exc_value: - c['exception_value'] = str(self.exc_value) + c["exception_value"] = str(self.exc_value) if frames: - c['lastframe'] = frames[-1] + c["lastframe"] = frames[-1] return c def get_traceback_html(self): """Return HTML version of debug 500 HTTP error page.""" - with self.html_template_path.open(encoding='utf-8') as fh: + with self.html_template_path.open(encoding="utf-8") as fh: t = DEBUG_ENGINE.from_string(fh.read()) c = Context(self.get_traceback_data(), use_l10n=False) return t.render(c) def get_traceback_text(self): """Return plain text version of debug 500 HTTP error page.""" - with self.text_template_path.open(encoding='utf-8') as fh: + with self.text_template_path.open(encoding="utf-8") as fh: t = DEBUG_ENGINE.from_string(fh.read()) c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False) return t.render(c) def _get_source(self, filename, loader, module_name): source = None - if hasattr(loader, 'get_source'): + if hasattr(loader, "get_source"): try: source = loader.get_source(module_name) except ImportError: @@ -395,13 +414,15 @@ class ExceptionReporter: source = source.splitlines() if source is None: try: - with open(filename, 'rb') as fp: + with open(filename, "rb") as fp: source = fp.read().splitlines() except OSError: pass return source - def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, module_name=None): + def _get_lines_from_file( + self, filename, lineno, context_lines, loader=None, module_name=None + ): """ Return context_lines before and after lineno from file. Return (pre_context_lineno, pre_context, context_line, post_context). @@ -414,15 +435,15 @@ class ExceptionReporter: # apply tokenize.detect_encoding to decode the source into a # string, then we should do that ourselves. if isinstance(source[0], bytes): - encoding = 'ascii' + encoding = "ascii" for line in source[:2]: # File coding may be specified. Match pattern from PEP-263 # (https://www.python.org/dev/peps/pep-0263/) - match = re.search(br'coding[:=]\s*([-\w.]+)', line) + match = re.search(rb"coding[:=]\s*([-\w.]+)", line) if match: - encoding = match[1].decode('ascii') + encoding = match[1].decode("ascii") break - source = [str(sline, encoding, 'replace') for sline in source] + source = [str(sline, encoding, "replace") for sline in source] lower_bound = max(0, lineno - context_lines) upper_bound = lineno + context_lines @@ -430,15 +451,15 @@ class ExceptionReporter: try: pre_context = source[lower_bound:lineno] context_line = source[lineno] - post_context = source[lineno + 1:upper_bound] + post_context = source[lineno + 1 : upper_bound] except IndexError: return None, [], None, [] return lower_bound, pre_context, context_line, post_context def _get_explicit_or_implicit_cause(self, exc_value): - explicit = getattr(exc_value, '__cause__', None) - suppress_context = getattr(exc_value, '__suppress_context__', None) - implicit = getattr(exc_value, '__context__', None) + explicit = getattr(exc_value, "__cause__", None) + suppress_context = getattr(exc_value, "__suppress_context__", None) + implicit = getattr(exc_value, "__context__", None) return explicit or (None if suppress_context else implicit) def get_traceback_frames(self): @@ -476,47 +497,58 @@ class ExceptionReporter: def get_exception_traceback_frames(self, exc_value, tb): exc_cause = self._get_explicit_or_implicit_cause(exc_value) - exc_cause_explicit = getattr(exc_value, '__cause__', True) + exc_cause_explicit = getattr(exc_value, "__cause__", True) if tb is None: yield { - 'exc_cause': exc_cause, - 'exc_cause_explicit': exc_cause_explicit, - 'tb': None, - 'type': 'user', + "exc_cause": exc_cause, + "exc_cause_explicit": exc_cause_explicit, + "tb": None, + "type": "user", } while tb is not None: # Support for __traceback_hide__ which is used by a few libraries # to hide internal frames. - if tb.tb_frame.f_locals.get('__traceback_hide__'): + if tb.tb_frame.f_locals.get("__traceback_hide__"): tb = tb.tb_next continue filename = tb.tb_frame.f_code.co_filename function = tb.tb_frame.f_code.co_name lineno = tb.tb_lineno - 1 - loader = tb.tb_frame.f_globals.get('__loader__') - module_name = tb.tb_frame.f_globals.get('__name__') or '' - pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file( - filename, lineno, 7, loader, module_name, + loader = tb.tb_frame.f_globals.get("__loader__") + module_name = tb.tb_frame.f_globals.get("__name__") or "" + ( + pre_context_lineno, + pre_context, + context_line, + post_context, + ) = self._get_lines_from_file( + filename, + lineno, + 7, + loader, + module_name, ) if pre_context_lineno is None: pre_context_lineno = lineno pre_context = [] - context_line = '<source code not available>' + context_line = "<source code not available>" post_context = [] yield { - 'exc_cause': exc_cause, - 'exc_cause_explicit': exc_cause_explicit, - 'tb': tb, - 'type': 'django' if module_name.startswith('django.') else 'user', - 'filename': filename, - 'function': function, - 'lineno': lineno + 1, - 'vars': self.filter.get_traceback_frame_variables(self.request, tb.tb_frame), - 'id': id(tb), - 'pre_context': pre_context, - 'context_line': context_line, - 'post_context': post_context, - 'pre_context_lineno': pre_context_lineno + 1, + "exc_cause": exc_cause, + "exc_cause_explicit": exc_cause_explicit, + "tb": tb, + "type": "django" if module_name.startswith("django.") else "user", + "filename": filename, + "function": function, + "lineno": lineno + 1, + "vars": self.filter.get_traceback_frame_variables( + self.request, tb.tb_frame + ), + "id": id(tb), + "pre_context": pre_context, + "context_line": context_line, + "post_context": post_context, + "pre_context_lineno": pre_context_lineno + 1, } tb = tb.tb_next @@ -524,52 +556,58 @@ class ExceptionReporter: def technical_404_response(request, exception): """Create a technical 404 error response. `exception` is the Http404.""" try: - error_url = exception.args[0]['path'] + error_url = exception.args[0]["path"] except (IndexError, TypeError, KeyError): error_url = request.path_info[1:] # Trim leading slash try: - tried = exception.args[0]['tried'] + tried = exception.args[0]["tried"] except (IndexError, TypeError, KeyError): resolved = True tried = request.resolver_match.tried if request.resolver_match else None else: resolved = False - if (not tried or ( # empty URLconf - request.path == '/' and - len(tried) == 1 and # default URLconf - len(tried[0]) == 1 and - getattr(tried[0][0], 'app_name', '') == getattr(tried[0][0], 'namespace', '') == 'admin' - )): + if not tried or ( # empty URLconf + request.path == "/" + and len(tried) == 1 + and len(tried[0]) == 1 # default URLconf + and getattr(tried[0][0], "app_name", "") + == getattr(tried[0][0], "namespace", "") + == "admin" + ): return default_urlconf(request) - urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF) + urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) if isinstance(urlconf, types.ModuleType): urlconf = urlconf.__name__ - with builtin_template_path('technical_404.html').open(encoding='utf-8') as fh: + with builtin_template_path("technical_404.html").open(encoding="utf-8") as fh: t = DEBUG_ENGINE.from_string(fh.read()) reporter_filter = get_default_exception_reporter_filter() - c = Context({ - 'urlconf': urlconf, - 'root_urlconf': settings.ROOT_URLCONF, - 'request_path': error_url, - 'urlpatterns': tried, - 'resolved': resolved, - 'reason': str(exception), - 'request': request, - 'settings': reporter_filter.get_safe_settings(), - 'raising_view_name': get_caller(request), - }) - return HttpResponseNotFound(t.render(c), content_type='text/html') + c = Context( + { + "urlconf": urlconf, + "root_urlconf": settings.ROOT_URLCONF, + "request_path": error_url, + "urlpatterns": tried, + "resolved": resolved, + "reason": str(exception), + "request": request, + "settings": reporter_filter.get_safe_settings(), + "raising_view_name": get_caller(request), + } + ) + return HttpResponseNotFound(t.render(c), content_type="text/html") def default_urlconf(request): """Create an empty URLconf 404 error response.""" - with builtin_template_path('default_urlconf.html').open(encoding='utf-8') as fh: + with builtin_template_path("default_urlconf.html").open(encoding="utf-8") as fh: t = DEBUG_ENGINE.from_string(fh.read()) - c = Context({ - 'version': get_docs_version(), - }) + c = Context( + { + "version": get_docs_version(), + } + ) - return HttpResponse(t.render(c), content_type='text/html') + return HttpResponse(t.render(c), content_type="text/html") |
