Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/views/debug.py: 14%
328 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import functools
2import re
3import sys
4import types
5import warnings
6from pathlib import Path
8from django.conf import settings
9from django.http import Http404, HttpResponse, HttpResponseNotFound
10from django.template import Context, Engine, TemplateDoesNotExist
11from django.template.defaultfilters import pprint
12from django.urls import resolve
13from django.utils import timezone
14from django.utils.datastructures import MultiValueDict
15from django.utils.encoding import force_str
16from django.utils.module_loading import import_string
17from django.utils.regex_helper import _lazy_re_compile
18from django.utils.version import get_docs_version
20# Minimal Django templates engine to render the error templates
21# regardless of the project's TEMPLATES setting. Templates are
22# read directly from the filesystem so that the error handler
23# works even if the template loader is broken.
24DEBUG_ENGINE = Engine(
25 debug=True,
26 libraries={"i18n": "django.templatetags.i18n"},
27)
30def builtin_template_path(name):
31 """
32 Return a path to a builtin template.
34 Avoid calling this function at the module level or in a class-definition
35 because __file__ may not exist, e.g. in frozen environments.
36 """
37 return Path(__file__).parent / "templates" / name
40class ExceptionCycleWarning(UserWarning):
41 pass
44class CallableSettingWrapper:
45 """
46 Object to wrap callable appearing in settings.
47 * Not to call in the debug page (#21345).
48 * Not to break the debug page if the callable forbidding to set attributes
49 (#23070).
50 """
52 def __init__(self, callable_setting):
53 self._wrapped = callable_setting
55 def __repr__(self):
56 return repr(self._wrapped)
59def technical_500_response(request, exc_type, exc_value, tb, status_code=500):
60 """
61 Create a technical server error response. The last three arguments are
62 the values returned from sys.exc_info() and friends.
63 """
64 reporter = get_exception_reporter_class(request)(request, exc_type, exc_value, tb)
65 if request.accepts("text/html"):
66 html = reporter.get_traceback_html()
67 return HttpResponse(html, status=status_code, content_type="text/html")
68 else:
69 text = reporter.get_traceback_text()
70 return HttpResponse(
71 text, status=status_code, content_type="text/plain; charset=utf-8"
72 )
75@functools.lru_cache()
76def get_default_exception_reporter_filter():
77 # Instantiate the default filter for the first time and cache it.
78 return import_string(settings.DEFAULT_EXCEPTION_REPORTER_FILTER)()
81def get_exception_reporter_filter(request):
82 default_filter = get_default_exception_reporter_filter()
83 return getattr(request, "exception_reporter_filter", default_filter)
86def get_exception_reporter_class(request):
87 default_exception_reporter_class = import_string(
88 settings.DEFAULT_EXCEPTION_REPORTER
89 )
90 return getattr(
91 request, "exception_reporter_class", default_exception_reporter_class
92 )
95class SafeExceptionReporterFilter:
96 """
97 Use annotations made by the sensitive_post_parameters and
98 sensitive_variables decorators to filter out sensitive information.
99 """
101 cleansed_substitute = "********************"
102 hidden_settings = _lazy_re_compile(
103 "API|TOKEN|KEY|SECRET|PASS|SIGNATURE", flags=re.I
104 )
106 def cleanse_setting(self, key, value):
107 """
108 Cleanse an individual setting key/value of sensitive content. If the
109 value is a dictionary, recursively cleanse the keys in that dictionary.
110 """
111 try:
112 is_sensitive = self.hidden_settings.search(key)
113 except TypeError:
114 is_sensitive = False
116 if is_sensitive:
117 cleansed = self.cleansed_substitute
118 elif isinstance(value, dict):
119 cleansed = {k: self.cleanse_setting(k, v) for k, v in value.items()}
120 elif isinstance(value, list):
121 cleansed = [self.cleanse_setting("", v) for v in value]
122 elif isinstance(value, tuple):
123 cleansed = tuple([self.cleanse_setting("", v) for v in value])
124 else:
125 cleansed = value
127 if callable(cleansed):
128 cleansed = CallableSettingWrapper(cleansed)
130 return cleansed
132 def get_safe_settings(self):
133 """
134 Return a dictionary of the settings module with values of sensitive
135 settings replaced with stars (*********).
136 """
137 settings_dict = {}
138 for k in dir(settings):
139 if k.isupper():
140 settings_dict[k] = self.cleanse_setting(k, getattr(settings, k))
141 return settings_dict
143 def get_safe_request_meta(self, request):
144 """
145 Return a dictionary of request.META with sensitive values redacted.
146 """
147 if not hasattr(request, "META"):
148 return {}
149 return {k: self.cleanse_setting(k, v) for k, v in request.META.items()}
151 def is_active(self, request):
152 """
153 This filter is to add safety in production environments (i.e. DEBUG
154 is False). If DEBUG is True then your site is not safe anyway.
155 This hook is provided as a convenience to easily activate or
156 deactivate the filter on a per request basis.
157 """
158 return settings.DEBUG is False
160 def get_cleansed_multivaluedict(self, request, multivaluedict):
161 """
162 Replace the keys in a MultiValueDict marked as sensitive with stars.
163 This mitigates leaking sensitive POST parameters if something like
164 request.POST['nonexistent_key'] throws an exception (#21098).
165 """
166 sensitive_post_parameters = getattr(request, "sensitive_post_parameters", [])
167 if self.is_active(request) and sensitive_post_parameters:
168 multivaluedict = multivaluedict.copy()
169 for param in sensitive_post_parameters:
170 if param in multivaluedict:
171 multivaluedict[param] = self.cleansed_substitute
172 return multivaluedict
174 def get_post_parameters(self, request):
175 """
176 Replace the values of POST parameters marked as sensitive with
177 stars (*********).
178 """
179 if request is None:
180 return {}
181 else:
182 sensitive_post_parameters = getattr(
183 request, "sensitive_post_parameters", []
184 )
185 if self.is_active(request) and sensitive_post_parameters:
186 cleansed = request.POST.copy()
187 if sensitive_post_parameters == "__ALL__":
188 # Cleanse all parameters.
189 for k in cleansed:
190 cleansed[k] = self.cleansed_substitute
191 return cleansed
192 else:
193 # Cleanse only the specified parameters.
194 for param in sensitive_post_parameters:
195 if param in cleansed:
196 cleansed[param] = self.cleansed_substitute
197 return cleansed
198 else:
199 return request.POST
201 def cleanse_special_types(self, request, value):
202 try:
203 # If value is lazy or a complex object of another kind, this check
204 # might raise an exception. isinstance checks that lazy
205 # MultiValueDicts will have a return value.
206 is_multivalue_dict = isinstance(value, MultiValueDict)
207 except Exception as e:
208 return "{!r} while evaluating {!r}".format(e, value)
210 if is_multivalue_dict:
211 # Cleanse MultiValueDicts (request.POST is the one we usually care about)
212 value = self.get_cleansed_multivaluedict(request, value)
213 return value
215 def get_traceback_frame_variables(self, request, tb_frame):
216 """
217 Replace the values of variables marked as sensitive with
218 stars (*********).
219 """
220 # Loop through the frame's callers to see if the sensitive_variables
221 # decorator was used.
222 current_frame = tb_frame.f_back
223 sensitive_variables = None
224 while current_frame is not None:
225 if (
226 current_frame.f_code.co_name == "sensitive_variables_wrapper"
227 and "sensitive_variables_wrapper" in current_frame.f_locals
228 ):
229 # The sensitive_variables decorator was used, so we take note
230 # of the sensitive variables' names.
231 wrapper = current_frame.f_locals["sensitive_variables_wrapper"]
232 sensitive_variables = getattr(wrapper, "sensitive_variables", None)
233 break
234 current_frame = current_frame.f_back
236 cleansed = {}
237 if self.is_active(request) and sensitive_variables:
238 if sensitive_variables == "__ALL__":
239 # Cleanse all variables
240 for name in tb_frame.f_locals:
241 cleansed[name] = self.cleansed_substitute
242 else:
243 # Cleanse specified variables
244 for name, value in tb_frame.f_locals.items():
245 if name in sensitive_variables:
246 value = self.cleansed_substitute
247 else:
248 value = self.cleanse_special_types(request, value)
249 cleansed[name] = value
250 else:
251 # Potentially cleanse the request and any MultiValueDicts if they
252 # are one of the frame variables.
253 for name, value in tb_frame.f_locals.items():
254 cleansed[name] = self.cleanse_special_types(request, value)
256 if (
257 tb_frame.f_code.co_name == "sensitive_variables_wrapper"
258 and "sensitive_variables_wrapper" in tb_frame.f_locals
259 ):
260 # For good measure, obfuscate the decorated function's arguments in
261 # the sensitive_variables decorator's frame, in case the variables
262 # associated with those arguments were meant to be obfuscated from
263 # the decorated function's frame.
264 cleansed["func_args"] = self.cleansed_substitute
265 cleansed["func_kwargs"] = self.cleansed_substitute
267 return cleansed.items()
270class ExceptionReporter:
271 """Organize and coordinate reporting on exceptions."""
273 @property
274 def html_template_path(self):
275 return builtin_template_path("technical_500.html")
277 @property
278 def text_template_path(self):
279 return builtin_template_path("technical_500.txt")
281 def __init__(self, request, exc_type, exc_value, tb, is_email=False):
282 self.request = request
283 self.filter = get_exception_reporter_filter(self.request)
284 self.exc_type = exc_type
285 self.exc_value = exc_value
286 self.tb = tb
287 self.is_email = is_email
289 self.template_info = getattr(self.exc_value, "template_debug", None)
290 self.template_does_not_exist = False
291 self.postmortem = None
293 def _get_raw_insecure_uri(self):
294 """
295 Return an absolute URI from variables available in this request. Skip
296 allowed hosts protection, so may return insecure URI.
297 """
298 return "{scheme}://{host}{path}".format(
299 scheme=self.request.scheme,
300 host=self.request._get_raw_host(),
301 path=self.request.get_full_path(),
302 )
304 def get_traceback_data(self):
305 """Return a dictionary containing traceback information."""
306 if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
307 self.template_does_not_exist = True
308 self.postmortem = self.exc_value.chain or [self.exc_value]
310 frames = self.get_traceback_frames()
311 for i, frame in enumerate(frames):
312 if "vars" in frame:
313 frame_vars = []
314 for k, v in frame["vars"]:
315 v = pprint(v)
316 # Trim large blobs of data
317 if len(v) > 4096:
318 v = "%s… <trimmed %d bytes string>" % (v[0:4096], len(v))
319 frame_vars.append((k, v))
320 frame["vars"] = frame_vars
321 frames[i] = frame
323 unicode_hint = ""
324 if self.exc_type and issubclass(self.exc_type, UnicodeError):
325 start = getattr(self.exc_value, "start", None)
326 end = getattr(self.exc_value, "end", None)
327 if start is not None and end is not None:
328 unicode_str = self.exc_value.args[1]
329 unicode_hint = force_str(
330 unicode_str[max(start - 5, 0) : min(end + 5, len(unicode_str))],
331 "ascii",
332 errors="replace",
333 )
334 from django import get_version
336 if self.request is None:
337 user_str = None
338 else:
339 try:
340 user_str = str(self.request.user)
341 except Exception:
342 # request.user may raise OperationalError if the database is
343 # unavailable, for example.
344 user_str = "[unable to retrieve the current user]"
346 c = {
347 "is_email": self.is_email,
348 "unicode_hint": unicode_hint,
349 "frames": frames,
350 "request": self.request,
351 "request_meta": self.filter.get_safe_request_meta(self.request),
352 "user_str": user_str,
353 "filtered_POST_items": list(
354 self.filter.get_post_parameters(self.request).items()
355 ),
356 "settings": self.filter.get_safe_settings(),
357 "sys_executable": sys.executable,
358 "sys_version_info": "%d.%d.%d" % sys.version_info[0:3],
359 "server_time": timezone.now(),
360 "django_version_info": get_version(),
361 "sys_path": sys.path,
362 "template_info": self.template_info,
363 "template_does_not_exist": self.template_does_not_exist,
364 "postmortem": self.postmortem,
365 }
366 if self.request is not None:
367 c["request_GET_items"] = self.request.GET.items()
368 c["request_FILES_items"] = self.request.FILES.items()
369 c["request_COOKIES_items"] = self.request.COOKIES.items()
370 c["request_insecure_uri"] = self._get_raw_insecure_uri()
372 # Check whether exception info is available
373 if self.exc_type:
374 c["exception_type"] = self.exc_type.__name__
375 if self.exc_value:
376 c["exception_value"] = str(self.exc_value)
377 if frames:
378 c["lastframe"] = frames[-1]
379 return c
381 def get_traceback_html(self):
382 """Return HTML version of debug 500 HTTP error page."""
383 with self.html_template_path.open(encoding="utf-8") as fh:
384 t = DEBUG_ENGINE.from_string(fh.read())
385 c = Context(self.get_traceback_data(), use_l10n=False)
386 return t.render(c)
388 def get_traceback_text(self):
389 """Return plain text version of debug 500 HTTP error page."""
390 with self.text_template_path.open(encoding="utf-8") as fh:
391 t = DEBUG_ENGINE.from_string(fh.read())
392 c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False)
393 return t.render(c)
395 def _get_source(self, filename, loader, module_name):
396 source = None
397 if hasattr(loader, "get_source"):
398 try:
399 source = loader.get_source(module_name)
400 except ImportError:
401 pass
402 if source is not None:
403 source = source.splitlines()
404 if source is None:
405 try:
406 with open(filename, "rb") as fp:
407 source = fp.read().splitlines()
408 except OSError:
409 pass
410 return source
412 def _get_lines_from_file(
413 self, filename, lineno, context_lines, loader=None, module_name=None
414 ):
415 """
416 Return context_lines before and after lineno from file.
417 Return (pre_context_lineno, pre_context, context_line, post_context).
418 """
419 source = self._get_source(filename, loader, module_name)
420 if source is None:
421 return None, [], None, []
423 # If we just read the source from a file, or if the loader did not
424 # apply tokenize.detect_encoding to decode the source into a
425 # string, then we should do that ourselves.
426 if isinstance(source[0], bytes):
427 encoding = "ascii"
428 for line in source[:2]:
429 # File coding may be specified. Match pattern from PEP-263
430 # (https://www.python.org/dev/peps/pep-0263/)
431 match = re.search(rb"coding[:=]\s*([-\w.]+)", line)
432 if match:
433 encoding = match[1].decode("ascii")
434 break
435 source = [str(sline, encoding, "replace") for sline in source]
437 lower_bound = max(0, lineno - context_lines)
438 upper_bound = lineno + context_lines
440 try:
441 pre_context = source[lower_bound:lineno]
442 context_line = source[lineno]
443 post_context = source[lineno + 1 : upper_bound]
444 except IndexError:
445 return None, [], None, []
446 return lower_bound, pre_context, context_line, post_context
448 def _get_explicit_or_implicit_cause(self, exc_value):
449 explicit = getattr(exc_value, "__cause__", None)
450 suppress_context = getattr(exc_value, "__suppress_context__", None)
451 implicit = getattr(exc_value, "__context__", None)
452 return explicit or (None if suppress_context else implicit)
454 def get_traceback_frames(self):
455 # Get the exception and all its causes
456 exceptions = []
457 exc_value = self.exc_value
458 while exc_value:
459 exceptions.append(exc_value)
460 exc_value = self._get_explicit_or_implicit_cause(exc_value)
461 if exc_value in exceptions:
462 warnings.warn(
463 "Cycle in the exception chain detected: exception '%s' "
464 "encountered again." % exc_value,
465 ExceptionCycleWarning,
466 )
467 # Avoid infinite loop if there's a cyclic reference (#29393).
468 break
470 frames = []
471 # No exceptions were supplied to ExceptionReporter
472 if not exceptions:
473 return frames
475 # In case there's just one exception, take the traceback from self.tb
476 exc_value = exceptions.pop()
477 tb = self.tb if not exceptions else exc_value.__traceback__
478 while True:
479 frames.extend(self.get_exception_traceback_frames(exc_value, tb))
480 try:
481 exc_value = exceptions.pop()
482 except IndexError:
483 break
484 tb = exc_value.__traceback__
485 return frames
487 def get_exception_traceback_frames(self, exc_value, tb):
488 exc_cause = self._get_explicit_or_implicit_cause(exc_value)
489 exc_cause_explicit = getattr(exc_value, "__cause__", True)
490 if tb is None:
491 yield {
492 "exc_cause": exc_cause,
493 "exc_cause_explicit": exc_cause_explicit,
494 "tb": None,
495 "type": "user",
496 }
497 while tb is not None:
498 # Support for __traceback_hide__ which is used by a few libraries
499 # to hide internal frames.
500 if tb.tb_frame.f_locals.get("__traceback_hide__"):
501 tb = tb.tb_next
502 continue
503 filename = tb.tb_frame.f_code.co_filename
504 function = tb.tb_frame.f_code.co_name
505 lineno = tb.tb_lineno - 1
506 loader = tb.tb_frame.f_globals.get("__loader__")
507 module_name = tb.tb_frame.f_globals.get("__name__") or ""
508 (
509 pre_context_lineno,
510 pre_context,
511 context_line,
512 post_context,
513 ) = self._get_lines_from_file(
514 filename,
515 lineno,
516 7,
517 loader,
518 module_name,
519 )
520 if pre_context_lineno is None:
521 pre_context_lineno = lineno
522 pre_context = []
523 context_line = "<source code not available>"
524 post_context = []
525 yield {
526 "exc_cause": exc_cause,
527 "exc_cause_explicit": exc_cause_explicit,
528 "tb": tb,
529 "type": "django" if module_name.startswith("django.") else "user",
530 "filename": filename,
531 "function": function,
532 "lineno": lineno + 1,
533 "vars": self.filter.get_traceback_frame_variables(
534 self.request, tb.tb_frame
535 ),
536 "id": id(tb),
537 "pre_context": pre_context,
538 "context_line": context_line,
539 "post_context": post_context,
540 "pre_context_lineno": pre_context_lineno + 1,
541 }
542 tb = tb.tb_next
545def technical_404_response(request, exception):
546 """Create a technical 404 error response. `exception` is the Http404."""
547 try:
548 error_url = exception.args[0]["path"]
549 except (IndexError, TypeError, KeyError):
550 error_url = request.path_info[1:] # Trim leading slash
552 try:
553 tried = exception.args[0]["tried"]
554 except (IndexError, TypeError, KeyError):
555 resolved = True
556 tried = request.resolver_match.tried if request.resolver_match else None
557 else:
558 resolved = False
559 if not tried or ( # empty URLconf
560 request.path == "/"
561 and len(tried) == 1
562 and len(tried[0]) == 1 # default URLconf
563 and getattr(tried[0][0], "app_name", "")
564 == getattr(tried[0][0], "namespace", "")
565 == "admin"
566 ):
567 return default_urlconf(request)
569 urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
570 if isinstance(urlconf, types.ModuleType):
571 urlconf = urlconf.__name__
573 caller = ""
574 try:
575 resolver_match = resolve(request.path)
576 except Http404:
577 pass
578 else:
579 obj = resolver_match.func
581 if hasattr(obj, "view_class"):
582 obj = obj.view_class
584 if hasattr(obj, "__name__"):
585 caller = obj.__name__
586 elif hasattr(obj, "__class__") and hasattr(obj.__class__, "__name__"):
587 caller = obj.__class__.__name__
589 if hasattr(obj, "__module__"):
590 module = obj.__module__
591 caller = "%s.%s" % (module, caller)
593 with builtin_template_path("technical_404.html").open(encoding="utf-8") as fh:
594 t = DEBUG_ENGINE.from_string(fh.read())
595 reporter_filter = get_default_exception_reporter_filter()
596 c = Context(
597 {
598 "urlconf": urlconf,
599 "root_urlconf": settings.ROOT_URLCONF,
600 "request_path": error_url,
601 "urlpatterns": tried,
602 "resolved": resolved,
603 "reason": str(exception),
604 "request": request,
605 "settings": reporter_filter.get_safe_settings(),
606 "raising_view_name": caller,
607 }
608 )
609 return HttpResponseNotFound(t.render(c), content_type="text/html")
612def default_urlconf(request):
613 """Create an empty URLconf 404 error response."""
614 with builtin_template_path("default_urlconf.html").open(encoding="utf-8") as fh:
615 t = DEBUG_ENGINE.from_string(fh.read())
616 c = Context(
617 {
618 "version": get_docs_version(),
619 }
620 )
622 return HttpResponse(t.render(c), content_type="text/html")