Ticket #10841: 10841.4.diff
File 10841.4.diff, 16.7 KB (added by , 13 years ago) |
---|
-
django/views/debug.py
diff --git a/django/views/debug.py b/django/views/debug.py
a b 59 59 the values returned from sys.exc_info() and friends. 60 60 """ 61 61 reporter = ExceptionReporter(request, exc_type, exc_value, tb) 62 html = reporter.get_traceback_html() 63 return HttpResponseServerError(html, mimetype='text/html') 62 if request.is_ajax(): 63 text = reporter.get_traceback_text() 64 return HttpResponseServerError(text, mimetype='text/plain') 65 else: 66 html = reporter.get_traceback_html() 67 return HttpResponseServerError(html, mimetype='text/html') 64 68 65 69 # Cache for the default exception reporter filter instance. 66 70 default_exception_reporter_filter = None … … 201 205 self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type) 202 206 self.exc_type = type(self.exc_value) 203 207 204 def get_traceback_ html(self):205 "Return HTML code for traceback."208 def get_traceback_data(self): 209 "Return a Context instance containing traceback information." 206 210 207 211 if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist): 208 212 from django.template.loader import template_source_loaders … … 240 244 unicode_str = self.exc_value.args[1] 241 245 unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace') 242 246 from django import get_version 243 t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') 244 c = Context({ 247 c = { 245 248 'is_email': self.is_email, 246 249 'unicode_hint': unicode_hint, 247 250 'frames': frames, … … 256 259 'template_info': self.template_info, 257 260 'template_does_not_exist': self.template_does_not_exist, 258 261 'loader_debug_info': self.loader_debug_info, 259 } )262 } 260 263 # Check whether exception info is available 261 264 if self.exc_type: 262 265 c['exception_type'] = self.exc_type.__name__ … … 264 267 c['exception_value'] = smart_unicode(self.exc_value, errors='replace') 265 268 if frames: 266 269 c['lastframe'] = frames[-1] 270 return c 271 272 def get_traceback_html(self): 273 "Return HTML version of debug 500 HTTP error page." 274 t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') 275 c = Context(self.get_traceback_data()) 276 return t.render(c) 277 278 def get_traceback_text(self): 279 "Return plain text version of debug 500 HTTP error page." 280 t = Template(TECHNICAL_500_TEXT_TEMPLATE, name='Technical 500 template') 281 c = Context(self.get_traceback_data(), autoescape=False) 267 282 return t.render(c) 268 283 269 284 def get_template_exception_info(self): … … 890 905 </html> 891 906 """ 892 907 908 TECHNICAL_500_TEXT_TEMPLATE = """{% firstof exception_type 'Report' %}{% if request %} at {{ request.path_info }}{% endif %} 909 {% firstof exception_value 'No exception supplied' %} 910 {% if request %} 911 Request Method: {{ request.META.REQUEST_METHOD }} 912 Request URL: {{ request.build_absolute_uri }}{% endif %} 913 Django Version: {{ django_version_info }} 914 Python Executable: {{ sys_executable }} 915 Python Version: {{ sys_version_info }} 916 Python Path: {{ sys_path }} 917 Server time: {{server_time|date:"r"}} 918 Installed Applications: 919 {{ settings.INSTALLED_APPS|pprint }} 920 Installed Middleware: 921 {{ settings.MIDDLEWARE_CLASSES|pprint }} 922 {% if template_does_not_exist %}Template loader Error: 923 {% if loader_debug_info %}Django tried loading these templates, in this order: 924 {% for loader in loader_debug_info %}Using loader {{ loader.loader }}: 925 {% for t in loader.templates %}{{ t.name }} (File {% if t.exists %}exists{% else %}does not exist{% endif %}) 926 {% endfor %}{% endfor %} 927 {% else %}Django couldn't find any templates because your TEMPLATE_LOADERS setting is empty! 928 {% endif %} 929 {% endif %}{% if template_info %} 930 Template error: 931 In template {{ template_info.name }}, error at line {{ template_info.line }} 932 {{ template_info.message }}{% for source_line in template_info.source_lines %}{% ifequal source_line.0 template_info.line %} 933 {{ source_line.0 }} : {{ template_info.before }} {{ template_info.during }} {{ template_info.after }} 934 {% else %} 935 {{ source_line.0 }} : {{ source_line.1 }} 936 {% endifequal %}{% endfor %}{% endif %}{% if frames %} 937 Traceback: 938 {% for frame in frames %}File "{{ frame.filename }}" in {{ frame.function }} 939 {% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line }}{% endif %} 940 {% endfor %} 941 {% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %} 942 {% if exception_value %}Exception Value: {{ exception_value }}{% endif %}{% endif %}{% endif %} 943 {% if request %}Request information: 944 GET:{% for k, v in request.GET.items %} 945 {{ k }} = {{ v|stringformat:"r" }}{% empty %} No GET data{% endfor %} 946 947 POST:{% for k, v in filtered_POST.items %} 948 {{ k }} = {{ v|stringformat:"r" }}{% empty %} No POST data{% endfor %} 949 950 FILES:{% for k, v in request.FILES.items %} 951 {{ k }} = {{ v|stringformat:"r" }}{% empty %} No FILES data{% endfor %} 952 953 COOKIES:{% for k, v in request.COOKIES.items %} 954 {{ k }} = {{ v|stringformat:"r" }}{% empty %} No cookie data{% endfor %} 955 956 META:{% for k, v in request.META.items|dictsort:"0" %} 957 {{ k }} = {{ v|stringformat:"r" }}{% endfor %} 958 {% else %}Request data not supplied 959 {% endif %} 960 Settings: 961 Using settings module {{ settings.SETTINGS_MODULE }}{% for k, v in settings.items|dictsort:"0" %} 962 {{ k }} = {{ v|stringformat:"r" }}{% endfor %} 963 964 You're seeing this error because you have DEBUG = True in your 965 Django settings file. Change that to False, and Django will 966 display a standard 500 page. 967 """ 968 893 969 TECHNICAL_404_TEMPLATE = """ 894 970 <!DOCTYPE html> 895 971 <html lang="en"> -
docs/releases/1.4.txt
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
a b 315 315 be able to retrieve a translation string without displaying it but setting 316 316 a template context variable instead. 317 317 318 * A new plaintext version of the HTTP 500 status code internal error page 319 served when :setting:`DEBUG` is ``True`` is now sent to the client when 320 Django detect that the request has originated in JavaScript code 321 (:meth:`~django.http.HttpRequest.is_ajax` is used for this). 322 323 Similarly to its HTML counterpart, it contains a collection of different 324 pieces of useful information about the state of the web application. 325 326 This should make it easier to read when debugging interaction with 327 client-side Javascript code. 328 318 329 .. _backwards-incompatible-changes-1.4: 319 330 320 331 Backwards incompatible changes in 1.4 -
tests/regressiontests/views/tests/debug.py
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py
a b 171 171 self.assertIn('<p>Request data not supplied</p>', html) 172 172 173 173 174 class ExceptionReporterFilterTests(TestCase): 175 """ 176 Ensure that sensitive information can be filtered out of error reports. 177 Refs #14614. 178 """ 174 class PlainTextReportTests(TestCase): 179 175 rf = RequestFactory() 176 177 def test_request_and_exception(self): 178 "A simple exception report can be generated" 179 try: 180 request = self.rf.get('/test_view/') 181 raise ValueError("Can't find my keys") 182 except ValueError: 183 exc_type, exc_value, tb = sys.exc_info() 184 reporter = ExceptionReporter(request, exc_type, exc_value, tb) 185 text = reporter.get_traceback_text() 186 self.assertIn('ValueError at /test_view/', text) 187 self.assertIn("Can't find my keys", text) 188 self.assertIn('Request Method:', text) 189 self.assertIn('Request URL:', text) 190 self.assertIn('Exception Type:', text) 191 self.assertIn('Exception Value:', text) 192 self.assertIn('Traceback:', text) 193 self.assertIn('Request information:', text) 194 self.assertNotIn('Request data not supplied', text) 195 196 def test_no_request(self): 197 "An exception report can be generated without request" 198 try: 199 raise ValueError("Can't find my keys") 200 except ValueError: 201 exc_type, exc_value, tb = sys.exc_info() 202 reporter = ExceptionReporter(None, exc_type, exc_value, tb) 203 text = reporter.get_traceback_text() 204 self.assertIn('ValueError', text) 205 self.assertIn("Can't find my keys", text) 206 self.assertNotIn('Request Method:', text) 207 self.assertNotIn('Request URL:', text) 208 self.assertIn('Exception Type:', text) 209 self.assertIn('Exception Value:', text) 210 self.assertIn('Traceback:', text) 211 self.assertIn('Request data not supplied', text) 212 213 def test_no_exception(self): 214 "An exception report can be generated for just a request" 215 request = self.rf.get('/test_view/') 216 reporter = ExceptionReporter(request, None, None, None) 217 text = reporter.get_traceback_text() 218 219 def test_request_and_message(self): 220 "A message can be provided in addition to a request" 221 request = self.rf.get('/test_view/') 222 reporter = ExceptionReporter(request, None, "I'm a little teapot", None) 223 text = reporter.get_traceback_text() 224 225 def test_message_only(self): 226 reporter = ExceptionReporter(None, None, "I'm a little teapot", None) 227 text = reporter.get_traceback_text() 228 229 230 class ExceptionReportTestMixin(object): 231 232 # Mixin used in the ExceptionReporterFilterTests and 233 # AjaxResponseExceptionReporterFilter tests below 234 180 235 breakfast_data = {'sausage-key': 'sausage-value', 181 236 'baked-beans-key': 'baked-beans-value', 182 237 'hash-brown-key': 'hash-brown-value', 183 238 'bacon-key': 'bacon-value',} 184 239 185 def verify_unsafe_response(self, view ):240 def verify_unsafe_response(self, view, check_for_vars=True): 186 241 """ 187 242 Asserts that potentially sensitive info are displayed in the response. 188 243 """ 189 244 request = self.rf.post('/some_url/', self.breakfast_data) 190 245 response = view(request) 191 # All variables are shown. 192 self.assertContains(response, 'cooked_eggs', status_code=500) 193 self.assertContains(response, 'scrambled', status_code=500) 194 self.assertContains(response, 'sauce', status_code=500) 195 self.assertContains(response, 'worcestershire', status_code=500) 246 if check_for_vars: 247 # All variables are shown. 248 self.assertContains(response, 'cooked_eggs', status_code=500) 249 self.assertContains(response, 'scrambled', status_code=500) 250 self.assertContains(response, 'sauce', status_code=500) 251 self.assertContains(response, 'worcestershire', status_code=500) 252 196 253 for k, v in self.breakfast_data.items(): 197 254 # All POST parameters are shown. 198 255 self.assertContains(response, k, status_code=500) 199 256 self.assertContains(response, v, status_code=500) 200 257 201 def verify_safe_response(self, view ):258 def verify_safe_response(self, view, check_for_vars=True): 202 259 """ 203 260 Asserts that certain sensitive info are not displayed in the response. 204 261 """ 205 262 request = self.rf.post('/some_url/', self.breakfast_data) 206 263 response = view(request) 207 # Non-sensitive variable's name and value are shown. 208 self.assertContains(response, 'cooked_eggs', status_code=500) 209 self.assertContains(response, 'scrambled', status_code=500) 210 # Sensitive variable's name is shown but not its value. 211 self.assertContains(response, 'sauce', status_code=500) 212 self.assertNotContains(response, 'worcestershire', status_code=500) 264 if check_for_vars: 265 # Non-sensitive variable's name and value are shown. 266 self.assertContains(response, 'cooked_eggs', status_code=500) 267 self.assertContains(response, 'scrambled', status_code=500) 268 # Sensitive variable's name is shown but not its value. 269 self.assertContains(response, 'sauce', status_code=500) 270 self.assertNotContains(response, 'worcestershire', status_code=500) 271 213 272 for k, v in self.breakfast_data.items(): 214 273 # All POST parameters' names are shown. 215 274 self.assertContains(response, k, status_code=500) … … 220 279 self.assertNotContains(response, 'sausage-value', status_code=500) 221 280 self.assertNotContains(response, 'bacon-value', status_code=500) 222 281 223 def verify_paranoid_response(self, view ):282 def verify_paranoid_response(self, view, check_for_vars=True): 224 283 """ 225 284 Asserts that no variables or POST parameters are displayed in the response. 226 285 """ 227 286 request = self.rf.post('/some_url/', self.breakfast_data) 228 287 response = view(request) 229 # Show variable names but not their values. 230 self.assertContains(response, 'cooked_eggs', status_code=500) 231 self.assertNotContains(response, 'scrambled', status_code=500) 232 self.assertContains(response, 'sauce', status_code=500) 233 self.assertNotContains(response, 'worcestershire', status_code=500) 288 if check_for_vars: 289 # Show variable names but not their values. 290 self.assertContains(response, 'cooked_eggs', status_code=500) 291 self.assertNotContains(response, 'scrambled', status_code=500) 292 self.assertContains(response, 'sauce', status_code=500) 293 self.assertNotContains(response, 'worcestershire', status_code=500) 294 234 295 for k, v in self.breakfast_data.items(): 235 296 # All POST parameters' names are shown. 236 297 self.assertContains(response, k, status_code=500) … … 303 364 # No POST parameters' values are shown. 304 365 self.assertNotIn(v, email.body) 305 366 367 368 class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin): 369 """ 370 Ensure that sensitive information can be filtered out of error reports. 371 Refs #14614. 372 """ 373 rf = RequestFactory() 374 306 375 def test_non_sensitive_request(self): 307 376 """ 308 377 Ensure that everything (request info and frame variables) can bee seen … … 354 423 with self.settings(DEBUG=False): 355 424 self.verify_unsafe_response(custom_exception_reporter_filter_view) 356 425 self.verify_unsafe_email(custom_exception_reporter_filter_view) 426 427 428 class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin): 429 """ 430 Ensure that sensitive information can be filtered out of error reports. 431 432 Here we specifically test the plain text 500 error page generated when it 433 has been detected the request was sent by JS code. We don't check 434 (non)existence of views variables in the traceback information section of 435 the response content because they aren't included in these error pages. 436 Refs #14614. 437 """ 438 rf = RequestFactory(HTTP_X_REQUESTED_WITH='XMLHttpRequest') 439 440 def test_non_sensitive_request(self): 441 """ 442 Ensure that request info can bee seen in the default error reports for 443 non-sensitive requests. 444 """ 445 with self.settings(DEBUG=True): 446 self.verify_unsafe_response(non_sensitive_view, check_for_vars=False) 447 448 with self.settings(DEBUG=False): 449 self.verify_unsafe_response(non_sensitive_view, check_for_vars=False) 450 451 def test_sensitive_request(self): 452 """ 453 Ensure that sensitive POST parameters cannot be seen in the default 454 error reports for sensitive requests. 455 """ 456 with self.settings(DEBUG=True): 457 self.verify_unsafe_response(sensitive_view, check_for_vars=False) 458 459 with self.settings(DEBUG=False): 460 self.verify_safe_response(sensitive_view, check_for_vars=False) 461 462 def test_paranoid_request(self): 463 """ 464 Ensure that no POST parameters can be seen in the default error reports 465 for "paranoid" requests. 466 """ 467 with self.settings(DEBUG=True): 468 self.verify_unsafe_response(paranoid_view, check_for_vars=False) 469 470 with self.settings(DEBUG=False): 471 self.verify_paranoid_response(paranoid_view, check_for_vars=False) 472 473 def test_custom_exception_reporter_filter(self): 474 """ 475 Ensure that it's possible to assign an exception reporter filter to 476 the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER. 477 """ 478 with self.settings(DEBUG=True): 479 self.verify_unsafe_response(custom_exception_reporter_filter_view, 480 check_for_vars=False) 481 482 with self.settings(DEBUG=False): 483 self.verify_unsafe_response(custom_exception_reporter_filter_view, 484 check_for_vars=False)