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

1import functools 

2import re 

3import sys 

4import types 

5import warnings 

6from pathlib import Path 

7 

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 

19 

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) 

28 

29 

30def builtin_template_path(name): 

31 """ 

32 Return a path to a builtin template. 

33 

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 

38 

39 

40class ExceptionCycleWarning(UserWarning): 

41 pass 

42 

43 

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 """ 

51 

52 def __init__(self, callable_setting): 

53 self._wrapped = callable_setting 

54 

55 def __repr__(self): 

56 return repr(self._wrapped) 

57 

58 

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 ) 

73 

74 

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)() 

79 

80 

81def get_exception_reporter_filter(request): 

82 default_filter = get_default_exception_reporter_filter() 

83 return getattr(request, "exception_reporter_filter", default_filter) 

84 

85 

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 ) 

93 

94 

95class SafeExceptionReporterFilter: 

96 """ 

97 Use annotations made by the sensitive_post_parameters and 

98 sensitive_variables decorators to filter out sensitive information. 

99 """ 

100 

101 cleansed_substitute = "********************" 

102 hidden_settings = _lazy_re_compile( 

103 "API|TOKEN|KEY|SECRET|PASS|SIGNATURE", flags=re.I 

104 ) 

105 

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 

115 

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 

126 

127 if callable(cleansed): 

128 cleansed = CallableSettingWrapper(cleansed) 

129 

130 return cleansed 

131 

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 

142 

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()} 

150 

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 

159 

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 

173 

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 

200 

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) 

209 

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 

214 

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 

235 

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) 

255 

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 

266 

267 return cleansed.items() 

268 

269 

270class ExceptionReporter: 

271 """Organize and coordinate reporting on exceptions.""" 

272 

273 @property 

274 def html_template_path(self): 

275 return builtin_template_path("technical_500.html") 

276 

277 @property 

278 def text_template_path(self): 

279 return builtin_template_path("technical_500.txt") 

280 

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 

288 

289 self.template_info = getattr(self.exc_value, "template_debug", None) 

290 self.template_does_not_exist = False 

291 self.postmortem = None 

292 

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 ) 

303 

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] 

309 

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 

322 

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 

335 

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]" 

345 

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() 

371 

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 

380 

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) 

387 

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) 

394 

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 

411 

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, [] 

422 

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] 

436 

437 lower_bound = max(0, lineno - context_lines) 

438 upper_bound = lineno + context_lines 

439 

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 

447 

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) 

453 

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 

469 

470 frames = [] 

471 # No exceptions were supplied to ExceptionReporter 

472 if not exceptions: 

473 return frames 

474 

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 

486 

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 

543 

544 

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 

551 

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) 

568 

569 urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) 

570 if isinstance(urlconf, types.ModuleType): 

571 urlconf = urlconf.__name__ 

572 

573 caller = "" 

574 try: 

575 resolver_match = resolve(request.path) 

576 except Http404: 

577 pass 

578 else: 

579 obj = resolver_match.func 

580 

581 if hasattr(obj, "view_class"): 

582 obj = obj.view_class 

583 

584 if hasattr(obj, "__name__"): 

585 caller = obj.__name__ 

586 elif hasattr(obj, "__class__") and hasattr(obj.__class__, "__name__"): 

587 caller = obj.__class__.__name__ 

588 

589 if hasattr(obj, "__module__"): 

590 module = obj.__module__ 

591 caller = "%s.%s" % (module, caller) 

592 

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") 

610 

611 

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 ) 

621 

622 return HttpResponse(t.render(c), content_type="text/html")