import datetime import os import re import sys import types from django.conf import settings from django.http import HttpResponse, HttpResponseServerError, HttpResponseNotFound from django.template import (Template, Context, TemplateDoesNotExist, TemplateSyntaxError) from django.template.defaultfilters import force_escape, pprint from django.utils.html import escape from django.utils.importlib import import_module from django.utils.encoding import smart_unicode, smart_str HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST|SIGNATURE') def linebreak_iter(template_source): yield 0 p = template_source.find('\n') while p >= 0: yield p+1 p = template_source.find('\n', p+1) yield len(template_source) + 1 def cleanse_setting(key, value): """Cleanse an individual setting key/value of sensitive content. If the value is a dictionary, recursively cleanse the keys in that dictionary. """ try: if HIDDEN_SETTINGS.search(key): cleansed = '********************' else: if isinstance(value, dict): cleansed = dict((k, cleanse_setting(k, v)) for k,v in value.items()) else: cleansed = value except TypeError: # If the key isn't regex-able, just return as-is. cleansed = value return cleansed def get_safe_settings(): "Returns a dictionary of the settings module, with sensitive settings blurred out." settings_dict = {} for k in dir(settings): if k.isupper(): settings_dict[k] = cleanse_setting(k, getattr(settings, k)) return settings_dict def technical_500_response(request, exc_type, exc_value, tb): """ Create a technical server error response. The last three arguments are the values returned from sys.exc_info() and friends. """ reporter = ExceptionReporter(request, exc_type, exc_value, tb) html = reporter.get_traceback_html() return HttpResponseServerError(html, mimetype='text/html') class ExceptionReporter(object): """ A class to organize and coordinate reporting on exceptions. """ def __init__(self, request, exc_type, exc_value, tb, is_email=False): self.request = request self.exc_type = exc_type self.exc_value = exc_value self.tb = tb self.is_email = is_email self.template_info = None self.template_does_not_exist = False self.loader_debug_info = None # Handle deprecated string exceptions if isinstance(self.exc_type, basestring): self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type) self.exc_type = type(self.exc_value) def get_traceback_html(self): "Return HTML code for traceback." if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist): from django.template.loader import template_source_loaders self.template_does_not_exist = True self.loader_debug_info = [] for loader in template_source_loaders: try: module = import_module(loader.__module__) if hasattr(loader, '__class__'): source_list_func = loader.get_template_sources else: # NOTE: Remember to remove this branch when we deprecate old template loaders in 1.4 source_list_func = module.get_template_sources # NOTE: This assumes exc_value is the name of the template that # the loader attempted to load. template_list = [{'name': t, 'exists': os.path.exists(t)} \ for t in source_list_func(str(self.exc_value))] except (ImportError, AttributeError): template_list = [] if hasattr(loader, '__class__'): loader_name = loader.__module__ + '.' + loader.__class__.__name__ else: # NOTE: Remember to remove this branch when we deprecate old template loaders in 1.4 loader_name = loader.__module__ + '.' + loader.__name__ self.loader_debug_info.append({ 'loader': loader_name, 'templates': template_list, }) if (settings.TEMPLATE_DEBUG and hasattr(self.exc_value, 'source') and isinstance(self.exc_value, TemplateSyntaxError)): self.get_template_exception_info() frames = self.get_traceback_frames() for i, frame in enumerate(frames): if 'vars' in frame: frame['vars'] = [(k, force_escape(pprint(v))) for k, v in frame['vars']] frames[i] = frame 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) if start is not None and end is not None: unicode_str = self.exc_value.args[1] unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace') from django import get_version t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') c = Context({ 'is_email': self.is_email, 'unicode_hint': unicode_hint, 'frames': frames, 'request': self.request, 'settings': get_safe_settings(), 'sys_executable': sys.executable, 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], 'server_time': datetime.datetime.now(), 'django_version_info': get_version(), 'sys_path' : sys.path, 'template_info': self.template_info, 'template_does_not_exist': self.template_does_not_exist, 'loader_debug_info': self.loader_debug_info, }) # Check whether exception info is available if self.exc_type: c['exception_type'] = self.exc_type.__name__ if self.exc_value: c['exception_value'] = smart_unicode(self.exc_value, errors='replace') if frames: c['lastframe'] = frames[-1] return t.render(c) def get_template_exception_info(self): origin, (start, end) = self.exc_value.source template_source = origin.reload() context_lines = 10 line = 0 upto = 0 source_lines = [] before = during = after = "" for num, next in enumerate(linebreak_iter(template_source)): if start >= upto and end <= next: line = num before = escape(template_source[upto:start]) during = escape(template_source[start:end]) after = escape(template_source[end:next]) source_lines.append( (num, escape(template_source[upto:next])) ) upto = next total = len(source_lines) top = max(1, line - context_lines) bottom = min(total, line + 1 + context_lines) self.template_info = { 'message': self.exc_value.args[0], 'source_lines': source_lines[top:bottom], 'before': before, 'during': during, 'after': after, 'top': top, 'bottom': bottom, 'total': total, 'line': line, 'name': origin.name, } def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, module_name=None): """ Returns context_lines before and after lineno from file. Returns (pre_context_lineno, pre_context, context_line, post_context). """ source = None if loader is not None and hasattr(loader, "get_source"): source = loader.get_source(module_name) if source is not None: source = source.splitlines() if source is None: try: f = open(filename) try: source = f.readlines() finally: f.close() except (OSError, IOError): pass if source is None: return None, [], None, [] encoding = 'ascii' for line in source[:2]: # File coding may be specified. Match pattern from PEP-263 # (http://www.python.org/dev/peps/pep-0263/) match = re.search(r'coding[:=]\s*([-\w.]+)', line) if match: encoding = match.group(1) break source = [unicode(sline, encoding, 'replace') for sline in source] lower_bound = max(0, lineno - context_lines) upper_bound = lineno + context_lines pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] context_line = source[lineno].strip('\n') post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] return lower_bound, pre_context, context_line, post_context def get_traceback_frames(self): frames = [] tb = self.tb 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__'): 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__') 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 not None: frames.append({ 'tb': tb, 'filename': filename, 'function': function, 'lineno': lineno + 1, 'vars': tb.tb_frame.f_locals.items(), '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 return frames def format_exception(self): """ Return the same data as from traceback.format_exception. """ import traceback frames = self.get_traceback_frames() tb = [ (f['filename'], f['lineno'], f['function'], f['context_line']) for f in frames ] list = ['Traceback (most recent call last):\n'] list += traceback.format_list(tb) list += traceback.format_exception_only(self.exc_type, self.exc_value) return list def technical_404_response(request, exception): "Create a technical 404 error response. The exception should be the Http404." try: tried = exception.args[0]['tried'] except (IndexError, TypeError, KeyError): tried = [] else: if not tried: # tried exists but is an empty list. The URLconf must've been empty. return empty_urlconf(request) urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF) if isinstance(urlconf, types.ModuleType): urlconf = urlconf.__name__ t = Template(TECHNICAL_404_TEMPLATE, name='Technical 404 template') c = Context({ 'urlconf': urlconf, 'root_urlconf': settings.ROOT_URLCONF, 'request_path': request.path_info[1:], # Trim leading slash 'urlpatterns': tried, 'reason': smart_str(exception, errors='replace'), 'request': request, 'settings': get_safe_settings(), }) return HttpResponseNotFound(t.render(c), mimetype='text/html') def empty_urlconf(request): "Create an empty URLconf 404 error response." t = Template(EMPTY_URLCONF_TEMPLATE, name='Empty URLConf template') c = Context({ 'project_name': settings.SETTINGS_MODULE.split('.')[0] }) return HttpResponse(t.render(c), mimetype='text/html') # # Templates are embedded in the file so that we know the error handler will # always work even if the template loader is broken. # TECHNICAL_500_TEMPLATE = """ {% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}{% if request %} at {{ request.path_info|escape }}{% endif %} {% if not is_email %} {% endif %}

{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}{% if request %} at {{ request.path_info|escape }}{% endif %}

{% if exception_value %}{{ exception_value|force_escape }}{% else %}No exception supplied{% endif %}
{% if request %} {% endif %} {% if exception_type %} {% endif %} {% if exception_type and exception_value %} {% endif %} {% if lastframe %} {% endif %}
Request Method: {{ request.META.REQUEST_METHOD }}
Request URL: {{ request.build_absolute_uri|escape }}
Django Version: {{ django_version_info }}
Exception Type: {{ exception_type }}
Exception Value:
{{ exception_value|force_escape }}
Exception Location: {{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}
Python Executable: {{ sys_executable|escape }}
Python Version: {{ sys_version_info }}
Python Path:
{{ sys_path|pprint }}
Server time: {{server_time|date:"r"}}
{% if unicode_hint %}

Unicode error hint

The string that could not be encoded/decoded was: {{ unicode_hint|force_escape }}

{% endif %} {% if template_does_not_exist %}

Template-loader postmortem

{% if loader_debug_info %}

Django tried loading these templates, in this order:

{% else %}

Django couldn't find any templates because your TEMPLATE_LOADERS setting is empty!

{% endif %}
{% endif %} {% if template_info %}

Template error

In template {{ template_info.name }}, error at line {{ template_info.line }}

{{ template_info.message }}

{% for source_line in template_info.source_lines %} {% ifequal source_line.0 template_info.line %} {% else %} {% endifequal %} {% endfor %}
{{ source_line.0 }} {{ template_info.before }}{{ template_info.during }}{{ template_info.after }}
{{ source_line.0 }} {{ source_line.1 }}
{% endif %} {% if frames %}

Traceback {% if not is_email %}Switch to copy-and-paste view{% endif %}

{% autoescape off %}
{% endautoescape %}
{% if not is_email %}


{% endif %} {% endif %}

Request information

{% if request %}

GET

{% if request.GET %} {% for var in request.GET.items %} {% endfor %}
Variable Value
{{ var.0 }}
{{ var.1|pprint }}
{% else %}

No GET data

{% endif %}

POST

{% if request.POST %} {% for var in request.POST.items %} {% endfor %}
Variable Value
{{ var.0 }}
{{ var.1|pprint }}
{% else %}

No POST data

{% endif %}

FILES

{% if request.FILES %} {% for var in request.FILES.items %} {% endfor %}
Variable Value
{{ var.0 }}
{{ var.1|pprint }}
{% else %}

No FILES data

{% endif %} {% if request.COOKIES %} {% for var in request.COOKIES.items %} {% endfor %}
Variable Value
{{ var.0 }}
{{ var.1|pprint }}
{% else %}

No cookie data

{% endif %}

META

{% for var in request.META.items|dictsort:"0" %} {% endfor %}
Variable Value
{{ var.0 }}
{{ var.1|pprint }}
{% else %}

Request data not supplied

{% endif %}

Settings

Using settings module {{ settings.SETTINGS_MODULE }}

{% for var in settings.items|dictsort:"0" %} {% endfor %}
Setting Value
{{ var.0 }}
{{ var.1|pprint }}
{% 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 500 page.

{% endif %} """ TECHNICAL_404_TEMPLATE = """ Page not found at {{ request.path_info|escape }}

Page not found (404)

Request Method: {{ request.META.REQUEST_METHOD }}
Request URL: {{ request.build_absolute_uri|escape }}
{% if urlpatterns %}

Using the URLconf defined in {{ urlconf }}, Django tried these URL patterns, in this order:

    {% for pattern in urlpatterns %}
  1. {% for pat in pattern %} {{ pat.regex.pattern }} {% if forloop.last and pat.name %}[name='{{ pat.name }}']{% endif %} {% endfor %}
  2. {% endfor %}

The current URL, {{ request_path|escape }}, didn't match any of these.

{% else %}

{{ reason }}

{% endif %}

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 404 page.

""" EMPTY_URLCONF_TEMPLATE = """ Welcome to Django

It worked!

Congratulations on your first Django-powered page.

Of course, you haven't actually done any work yet. Here's what to do next:

You're seeing this message because you have DEBUG = True in your Django settings file and you haven't configured any URLs. Get to work!

"""