mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #25099 -- Cleaned up HttpRequest representations in error reporting.
This commit is contained in:
parent
6bdd3840be
commit
8f8c54f70b
@ -1,7 +1,6 @@
|
||||
from django.http.cookie import SimpleCookie, parse_cookie
|
||||
from django.http.request import (
|
||||
HttpRequest, QueryDict, RawPostDataException, UnreadablePostError,
|
||||
build_request_repr,
|
||||
)
|
||||
from django.http.response import (
|
||||
BadHeaderError, FileResponse, Http404, HttpResponse,
|
||||
@ -14,7 +13,7 @@ from django.http.utils import conditional_content_removal
|
||||
|
||||
__all__ = [
|
||||
'SimpleCookie', 'parse_cookie', 'HttpRequest', 'QueryDict',
|
||||
'RawPostDataException', 'UnreadablePostError', 'build_request_repr',
|
||||
'RawPostDataException', 'UnreadablePostError',
|
||||
'HttpResponse', 'StreamingHttpResponse', 'HttpResponseRedirect',
|
||||
'HttpResponsePermanentRedirect', 'HttpResponseNotModified',
|
||||
'HttpResponseBadRequest', 'HttpResponseForbidden', 'HttpResponseNotFound',
|
||||
|
@ -5,7 +5,6 @@ import re
|
||||
import sys
|
||||
from io import BytesIO
|
||||
from itertools import chain
|
||||
from pprint import pformat
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signing
|
||||
@ -465,52 +464,6 @@ class QueryDict(MultiValueDict):
|
||||
return '&'.join(output)
|
||||
|
||||
|
||||
def build_request_repr(request, path_override=None, GET_override=None,
|
||||
POST_override=None, COOKIES_override=None,
|
||||
META_override=None):
|
||||
"""
|
||||
Builds and returns the request's representation string. The request's
|
||||
attributes may be overridden by pre-processed values.
|
||||
"""
|
||||
# Since this is called as part of error handling, we need to be very
|
||||
# robust against potentially malformed input.
|
||||
try:
|
||||
get = (pformat(GET_override)
|
||||
if GET_override is not None
|
||||
else pformat(request.GET))
|
||||
except Exception:
|
||||
get = '<could not parse>'
|
||||
if request._post_parse_error:
|
||||
post = '<could not parse>'
|
||||
else:
|
||||
try:
|
||||
post = (pformat(POST_override)
|
||||
if POST_override is not None
|
||||
else pformat(request.POST))
|
||||
except Exception:
|
||||
post = '<could not parse>'
|
||||
try:
|
||||
cookies = (pformat(COOKIES_override)
|
||||
if COOKIES_override is not None
|
||||
else pformat(request.COOKIES))
|
||||
except Exception:
|
||||
cookies = '<could not parse>'
|
||||
try:
|
||||
meta = (pformat(META_override)
|
||||
if META_override is not None
|
||||
else pformat(request.META))
|
||||
except Exception:
|
||||
meta = '<could not parse>'
|
||||
path = path_override if path_override is not None else request.path
|
||||
return force_str('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' %
|
||||
(request.__class__.__name__,
|
||||
path,
|
||||
six.text_type(get),
|
||||
six.text_type(post),
|
||||
six.text_type(cookies),
|
||||
six.text_type(meta)))
|
||||
|
||||
|
||||
# It's neither necessary nor appropriate to use
|
||||
# django.utils.encoding.smart_text for parsing URLs and form inputs. Thus,
|
||||
# this slightly more restricted function, used by QueryDict.
|
||||
|
@ -4,14 +4,14 @@ import logging
|
||||
import logging.config # needed when logging_config doesn't start with logging.config
|
||||
import sys
|
||||
import warnings
|
||||
from copy import copy
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from django.core.mail import get_connection
|
||||
from django.utils.deprecation import RemovedInNextVersionWarning
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.module_loading import import_string
|
||||
from django.views.debug import ExceptionReporter, get_exception_reporter_filter
|
||||
from django.views.debug import ExceptionReporter
|
||||
|
||||
# Default logging for Django. This sends an email to the site admins on every
|
||||
# HTTP 500 error. Depending on DEBUG, all other log records are either sent to
|
||||
@ -90,24 +90,27 @@ class AdminEmailHandler(logging.Handler):
|
||||
else 'EXTERNAL'),
|
||||
record.getMessage()
|
||||
)
|
||||
filter = get_exception_reporter_filter(request)
|
||||
request_repr = '\n{}'.format(force_text(filter.get_request_repr(request)))
|
||||
except Exception:
|
||||
subject = '%s: %s' % (
|
||||
record.levelname,
|
||||
record.getMessage()
|
||||
)
|
||||
request = None
|
||||
request_repr = "unavailable"
|
||||
subject = self.format_subject(subject)
|
||||
|
||||
# Since we add a nicely formatted traceback on our own, create a copy
|
||||
# of the log record without the exception data.
|
||||
no_exc_record = copy(record)
|
||||
no_exc_record.exc_info = None
|
||||
no_exc_record.exc_text = None
|
||||
|
||||
if record.exc_info:
|
||||
exc_info = record.exc_info
|
||||
else:
|
||||
exc_info = (None, record.getMessage(), None)
|
||||
|
||||
message = "%s\n\nRequest repr(): %s" % (self.format(record), request_repr)
|
||||
reporter = ExceptionReporter(request, is_email=True, *exc_info)
|
||||
message = "%s\n\n%s" % (self.format(no_exc_record), reporter.get_traceback_text())
|
||||
html_message = reporter.get_traceback_html() if self.include_html else None
|
||||
self.send_mail(subject, message, fail_silently=True, html_message=html_message)
|
||||
|
||||
|
@ -6,9 +6,7 @@ import types
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import Resolver404, resolve
|
||||
from django.http import (
|
||||
HttpRequest, HttpResponse, HttpResponseNotFound, build_request_repr,
|
||||
)
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from django.template import Context, Engine, TemplateDoesNotExist
|
||||
from django.template.defaultfilters import force_escape, pprint
|
||||
from django.utils import lru_cache, six, timezone
|
||||
@ -113,12 +111,6 @@ class ExceptionReporterFilter(object):
|
||||
contain lenient default behaviors.
|
||||
"""
|
||||
|
||||
def get_request_repr(self, request):
|
||||
if request is None:
|
||||
return repr(None)
|
||||
else:
|
||||
return build_request_repr(request, POST_override=self.get_post_parameters(request))
|
||||
|
||||
def get_post_parameters(self, request):
|
||||
if request is None:
|
||||
return {}
|
||||
@ -186,16 +178,13 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
|
||||
def cleanse_special_types(self, request, value):
|
||||
try:
|
||||
# If value is lazy or a complex object of another kind, this check
|
||||
# might raise an exception. isinstance checks that lazy HttpRequests
|
||||
# or MultiValueDicts will have a return value.
|
||||
is_request = isinstance(value, HttpRequest)
|
||||
# might raise an exception. isinstance checks that lazy
|
||||
# MultiValueDicts will have a return value.
|
||||
is_multivalue_dict = isinstance(value, MultiValueDict)
|
||||
except Exception as e:
|
||||
return '{!r} while evaluating {!r}'.format(e, value)
|
||||
|
||||
if is_request:
|
||||
# Cleanse the request's POST parameters.
|
||||
value = self.get_request_repr(value)
|
||||
elif isinstance(value, MultiValueDict):
|
||||
if is_multivalue_dict:
|
||||
# Cleanse MultiValueDicts (request.POST is the one we usually care about)
|
||||
value = self.get_cleansed_multivaluedict(request, value)
|
||||
return value
|
||||
@ -1126,9 +1115,11 @@ Settings:
|
||||
Using settings module {{ settings.SETTINGS_MODULE }}{% for k, v in settings.items|dictsort:"0" %}
|
||||
{{ k }} = {{ v|stringformat:"r" }}{% endfor %}
|
||||
|
||||
{% if not is_email %}
|
||||
You're seeing this error because you have DEBUG = True in your
|
||||
Django settings file. Change that to False, and Django will
|
||||
display a standard page generated by the handler for this status code.
|
||||
{% endif %}
|
||||
""")
|
||||
|
||||
TECHNICAL_404_TEMPLATE = """
|
||||
|
@ -255,13 +255,6 @@ following methods:
|
||||
Returns ``True`` to activate the filtering operated in the other methods.
|
||||
By default the filter is active if :setting:`DEBUG` is ``False``.
|
||||
|
||||
.. method:: SafeExceptionReporterFilter.get_request_repr(request)
|
||||
|
||||
Returns the representation string of the request object, that is, the
|
||||
value that would be returned by ``repr(request)``, except it uses the
|
||||
filtered dictionary of POST parameters as determined by
|
||||
:meth:`SafeExceptionReporterFilter.get_post_parameters`.
|
||||
|
||||
.. method:: SafeExceptionReporterFilter.get_post_parameters(request)
|
||||
|
||||
Returns the filtered dictionary of POST parameters. By default it replaces
|
||||
|
@ -683,6 +683,20 @@ console, for example.
|
||||
If you are overriding Django's default logging, you should check to see how
|
||||
your configuration merges with the new defaults.
|
||||
|
||||
``HttpRequest`` details in error reporting
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It was redundant to display the full details of the
|
||||
:class:`~django.http.HttpRequest` each time it appeared as a stack frame
|
||||
variable in the HTML version of the debug page and error email. Thus, the HTTP
|
||||
request will now display the same standard representation as other variables
|
||||
(``repr(request)``). As a result, the method
|
||||
``ExceptionReporterFilter.get_request_repr()`` was removed.
|
||||
|
||||
The contents of the text version of the email were modified to provide a
|
||||
traceback of the same structure as in the case of AJAX requests. The traceback
|
||||
details are rendered by the ``ExceptionReporter.get_traceback_text()`` method.
|
||||
|
||||
Removal of time zone aware global adapters and converters for datetimes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -334,7 +334,7 @@ class AdminEmailHandlerTest(SimpleTestCase):
|
||||
msg = mail.outbox[0]
|
||||
self.assertEqual(msg.to, ['admin@example.com'])
|
||||
self.assertEqual(msg.subject, "[Django] ERROR (EXTERNAL IP): message")
|
||||
self.assertIn("path:%s" % url_path, msg.body)
|
||||
self.assertIn("Report at %s" % url_path, msg.body)
|
||||
|
||||
@override_settings(
|
||||
MANAGERS=[('manager', 'manager@example.com')],
|
||||
@ -419,7 +419,7 @@ class SecurityLoggerTest(SimpleTestCase):
|
||||
def test_suspicious_email_admins(self):
|
||||
self.client.get('/suspicious/')
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertIn('path:/suspicious/,', mail.outbox[0].body)
|
||||
self.assertIn('Report at /suspicious/', mail.outbox[0].body)
|
||||
|
||||
|
||||
class SettingsCustomLoggingTest(AdminScriptTestCase):
|
||||
|
@ -10,7 +10,7 @@ from django.core.exceptions import SuspiciousOperation
|
||||
from django.core.handlers.wsgi import LimitedStream, WSGIRequest
|
||||
from django.http import (
|
||||
HttpRequest, HttpResponse, RawPostDataException, UnreadablePostError,
|
||||
build_request_repr, parse_cookie,
|
||||
parse_cookie,
|
||||
)
|
||||
from django.test import RequestFactory, SimpleTestCase, override_settings
|
||||
from django.test.client import FakePayload
|
||||
@ -60,9 +60,6 @@ class RequestsTests(SimpleTestCase):
|
||||
request.COOKIES = {'post-key': 'post-value'}
|
||||
request.META = {'post-key': 'post-value'}
|
||||
self.assertEqual(repr(request), str_prefix("<HttpRequest: GET '/somepath/'>"))
|
||||
self.assertEqual(build_request_repr(request), str_prefix("<HttpRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>"))
|
||||
self.assertEqual(build_request_repr(request, path_override='/otherpath/', GET_override={'a': 'b'}, POST_override={'c': 'd'}, COOKIES_override={'e': 'f'}, META_override={'g': 'h'}),
|
||||
str_prefix("<HttpRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>"))
|
||||
|
||||
def test_httprequest_repr_invalid_method_and_path(self):
|
||||
request = HttpRequest()
|
||||
@ -74,22 +71,6 @@ class RequestsTests(SimpleTestCase):
|
||||
request.path = ""
|
||||
self.assertEqual(repr(request), str_prefix("<HttpRequest>"))
|
||||
|
||||
def test_bad_httprequest_repr(self):
|
||||
"""
|
||||
If an exception occurs when parsing GET, POST, COOKIES, or META, the
|
||||
repr of the request should show it.
|
||||
"""
|
||||
class Bomb(object):
|
||||
"""An object that raises an exception when printed out."""
|
||||
def __repr__(self):
|
||||
raise Exception('boom!')
|
||||
|
||||
bomb = Bomb()
|
||||
for attr in ['GET', 'POST', 'COOKIES', 'META']:
|
||||
request = HttpRequest()
|
||||
setattr(request, attr, {'bomb': bomb})
|
||||
self.assertIn('%s:<could not parse>' % attr, build_request_repr(request))
|
||||
|
||||
def test_wsgirequest(self):
|
||||
request = WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus', 'wsgi.input': BytesIO(b'')})
|
||||
self.assertEqual(list(request.GET.keys()), [])
|
||||
@ -147,9 +128,6 @@ class RequestsTests(SimpleTestCase):
|
||||
request.COOKIES = {'post-key': 'post-value'}
|
||||
request.META = {'post-key': 'post-value'}
|
||||
self.assertEqual(repr(request), str_prefix("<WSGIRequest: GET '/somepath/'>"))
|
||||
self.assertEqual(build_request_repr(request), str_prefix("<WSGIRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>"))
|
||||
self.assertEqual(build_request_repr(request, path_override='/otherpath/', GET_override={'a': 'b'}, POST_override={'c': 'd'}, COOKIES_override={'e': 'f'}, META_override={'g': 'h'}),
|
||||
str_prefix("<WSGIRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>"))
|
||||
|
||||
def test_wsgirequest_path_info(self):
|
||||
def wsgi_str(path_info):
|
||||
|
Loading…
Reference in New Issue
Block a user