Commit 50a985b0 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Fixed #19099 -- Split broken link emails out of common middleware.

parent 83d0cc52
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -146,7 +146,7 @@ FILE_CHARSET = 'utf-8'
# Email address that error messages come from.
SERVER_EMAIL = 'root@localhost'

# Whether to send broken-link emails.
# Whether to send broken-link emails. Deprecated, must be removed in 1.8.
SEND_BROKEN_LINK_EMAILS = False

# Database connection info. If left empty, will default to the dummy backend.
@@ -245,7 +245,7 @@ ALLOWED_INCLUDE_ROOTS = ()
ADMIN_FOR = ()

# List of compiled regular expression objects representing URLs that need not
# be reported when SEND_BROKEN_LINK_EMAILS is True. Here are a few examples:
# be reported by BrokenLinkEmailsMiddleware. Here are a few examples:
#    import re
#    IGNORABLE_404_URLS = (
#        re.compile(r'^/apple-touch-icon.*\.png$'),
+46 −32
Original line number Diff line number Diff line
import hashlib
import logging
import re
import warnings

from django.conf import settings
from django import http
from django.core.mail import mail_managers
from django.core import urlresolvers
from django import http
from django.utils.http import urlquote
from django.utils import six
from django.core import urlresolvers


logger = logging.getLogger('django.request')
@@ -102,25 +103,15 @@ class CommonMiddleware(object):
        return http.HttpResponsePermanentRedirect(newurl)

    def process_response(self, request, response):
        "Send broken link emails and calculate the Etag, if needed."
        if response.status_code == 404:
            if settings.SEND_BROKEN_LINK_EMAILS and not settings.DEBUG:
                # If the referrer was from an internal link or a non-search-engine site,
                # send a note to the managers.
                domain = request.get_host()
                referer = request.META.get('HTTP_REFERER', None)
                is_internal = _is_internal_request(domain, referer)
                path = request.get_full_path()
                if referer and not _is_ignorable_404(path) and (is_internal or '?' not in referer):
                    ua = request.META.get('HTTP_USER_AGENT', '<none>')
                    ip = request.META.get('REMOTE_ADDR', '<none>')
                    mail_managers("Broken %slink on %s" % ((is_internal and 'INTERNAL ' or ''), domain),
                        "Referrer: %s\nRequested URL: %s\nUser agent: %s\nIP address: %s\n" \
                                  % (referer, request.get_full_path(), ua, ip),
                                  fail_silently=True)
                return response
        """
        Calculate the ETag, if needed.
        """
        if settings.SEND_BROKEN_LINK_EMAILS:
            warnings.warn("SEND_BROKEN_LINK_EMAILS is deprecated. "
                "Use BrokenLinkEmailsMiddleware instead.",
                PendingDeprecationWarning, stacklevel=2)
            BrokenLinkEmailsMiddleware().process_response(request, response)

        # Use ETags, if requested.
        if settings.USE_ETAGS:
            if response.has_header('ETag'):
                etag = response['ETag']
@@ -139,15 +130,38 @@ class CommonMiddleware(object):

        return response

def _is_ignorable_404(uri):

class BrokenLinkEmailsMiddleware(object):

    def process_response(self, request, response):
        """
    Returns True if a 404 at the given URL *shouldn't* notify the site managers.
        Send broken link emails for relevant 404 NOT FOUND responses.
        """
    return any(pattern.search(uri) for pattern in settings.IGNORABLE_404_URLS)
        if response.status_code == 404 and not settings.DEBUG:
            domain = request.get_host()
            path = request.get_full_path()
            referer = request.META.get('HTTP_REFERER', '')
            is_internal = self.is_internal_request(domain, referer)
            is_not_search_engine = '?' not in referer
            is_ignorable = self.is_ignorable_404(path)
            if referer and (is_internal or is_not_search_engine) and not is_ignorable:
                ua = request.META.get('HTTP_USER_AGENT', '<none>')
                ip = request.META.get('REMOTE_ADDR', '<none>')
                mail_managers(
                    "Broken %slink on %s" % (('INTERNAL ' if is_internal else ''), domain),
                    "Referrer: %s\nRequested URL: %s\nUser agent: %s\nIP address: %s\n" % (referer, path, ua, ip),
                    fail_silently=True)
        return response

def _is_internal_request(domain, referer):
    def is_internal_request(self, domain, referer):
        """
    Returns true if the referring URL is the same domain as the current request.
        Returns True if the referring URL is the same domain as the current request.
        """
        # Different subdomains are treated as different domains.
    return referer is not None and re.match("^https?://%s/" % re.escape(domain), referer)
        return re.match("^https?://%s/" % re.escape(domain), referer)

    def is_ignorable_404(self, uri):
        """
        Returns True if a 404 at the given URL *shouldn't* notify the site managers.
        """
        return any(pattern.search(uri) for pattern in settings.IGNORABLE_404_URLS)
+11 −8
Original line number Diff line number Diff line
@@ -54,18 +54,24 @@ setting.
Django can also be configured to email errors about broken links (404 "page
not found" errors). Django sends emails about 404 errors when:

* :setting:`DEBUG` is ``False``
* :setting:`DEBUG` is ``False``;

* :setting:`SEND_BROKEN_LINK_EMAILS` is ``True``

* Your :setting:`MIDDLEWARE_CLASSES` setting includes ``CommonMiddleware``
  (which it does by default).
* Your :setting:`MIDDLEWARE_CLASSES` setting includes
  :class:`django.middleware.common.BrokenLinkEmailsMiddleware`.

If those conditions are met, Django will email the users listed in the
:setting:`MANAGERS` setting whenever your code raises a 404 and the request has
a referer. (It doesn't bother to email for 404s that don't have a referer --
those are usually just people typing in broken URLs or broken Web 'bots).

.. note::

    :class:`~django.middleware.common.BrokenLinkEmailsMiddleware` must appear
    before other middleware that intercepts 404 errors, such as
    :class:`~django.middleware.locale.LocaleMiddleware` or
    :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`.
    Put it towards the top of your :setting:`MIDDLEWARE_CLASSES` setting.

You can tell Django to stop reporting particular 404s by tweaking the
:setting:`IGNORABLE_404_URLS` setting. It should be a tuple of compiled
regular expression objects. For example::
@@ -92,9 +98,6 @@ crawlers often request::
(Note that these are regular expressions, so we put a backslash in front of
periods to escape them.)

The best way to disable this behavior is to set
:setting:`SEND_BROKEN_LINK_EMAILS` to ``False``.

.. seealso::

   404 errors are logged using the logging framework. By default, these log
+7 −0
Original line number Diff line number Diff line
@@ -308,6 +308,13 @@ these changes.
* The ``depth`` keyword argument will be removed from
  :meth:`~django.db.models.query.QuerySet.select_related`.

1.8
---

* The ``SEND_BROKEN_LINK_EMAILS`` setting will be removed. Add the
  :class:`django.middleware.common.BrokenLinkEmailsMiddleware` middleware to
  your :setting:`MIDDLEWARE_CLASSES` setting instead.

2.0
---

+5 −3
Original line number Diff line number Diff line
@@ -61,14 +61,16 @@ Adds a few conveniences for perfectionists:
  indexer would treat them as separate URLs -- so it's best practice to
  normalize URLs.

* Sends broken link notification emails to :setting:`MANAGERS` if
  :setting:`SEND_BROKEN_LINK_EMAILS` is set to ``True``.

* Handles ETags based on the :setting:`USE_ETAGS` setting. If
  :setting:`USE_ETAGS` is set to ``True``, Django will calculate an ETag
  for each request by MD5-hashing the page content, and it'll take care of
  sending ``Not Modified`` responses, if appropriate.

.. class:: BrokenLinkEmailsMiddleware

* Sends broken link notification emails to :setting:`MANAGERS` (see
  :doc:`/howto/error-reporting`).

View metadata middleware
------------------------

Loading