Index: django/middleware/common.py
===================================================================
--- django/middleware/common.py	(revision 7711)
+++ django/middleware/common.py	(working copy)
@@ -40,54 +40,18 @@
                 if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
                     return http.HttpResponseForbidden('<h1>Forbidden</h1>')
 
-        # Check for a redirect based on settings.APPEND_SLASH
-        # and settings.PREPEND_WWW
-        host = request.get_host()
-        old_url = [host, request.path]
-        new_url = old_url[:]
+        _response = _handle_redirects(request)
+        if _response:
+            return _response
 
-        if (settings.PREPEND_WWW and old_url[0] and
-                not old_url[0].startswith('www.')):
-            new_url[0] = 'www.' + old_url[0]
-
-        # Append a slash if APPEND_SLASH is set and the URL doesn't have a
-        # trailing slash and there is no pattern for the current path
-        if settings.APPEND_SLASH and (not old_url[1].endswith('/')):
-            try:
-                urlresolvers.resolve(request.path)
-            except urlresolvers.Resolver404:
-                new_url[1] = new_url[1] + '/'
-                if settings.DEBUG and request.method == 'POST':
-                    raise RuntimeError, (""
-                    "You called this URL via POST, but the URL doesn't end "
-                    "in a slash and you have APPEND_SLASH set. Django can't "
-                    "redirect to the slash URL while maintaining POST data. "
-                    "Change your form to point to %s%s (note the trailing "
-                    "slash), or set APPEND_SLASH=False in your Django "
-                    "settings.") % (new_url[0], new_url[1])
-
-        if new_url != old_url:
-            # Redirect if the target url exists
-            try:
-                urlresolvers.resolve(new_url[1])
-            except urlresolvers.Resolver404:
-                pass
-            else:
-                if new_url[0]:
-                    newurl = "%s://%s%s" % (
-                        request.is_secure() and 'https' or 'http',
-                        new_url[0], urlquote(new_url[1]))
-                else:
-                    newurl = urlquote(new_url[1])
-                if request.GET:
-                    newurl += '?' + request.GET.urlencode()
-                return http.HttpResponsePermanentRedirect(newurl)
-
         return None
 
     def process_response(self, request, response):
         "Check for a flat page (for 404s) and calculate the Etag, if needed."
         if response.status_code == 404:
+            _response = _handle_redirects(request, resolve=False)
+            if _response:
+                return _response
             if settings.SEND_BROKEN_LINK_EMAILS:
                 # If the referrer was from an internal link or a non-search-engine site,
                 # send a note to the managers.
@@ -118,6 +82,52 @@
 
         return response
 
+def _handle_redirects(request, resolve=True):
+    host = request.get_host()
+    old_url = [host, request.path]
+    new_url = old_url[:]
+
+    if (settings.PREPEND_WWW and old_url[0] and
+            not old_url[0].startswith('www.')):
+        new_url[0] = 'www.' + old_url[0]
+
+    # Append a slash if APPEND_SLASH is set and the URL doesn't have a
+    # trailing slash and there is no pattern for the current path
+    if settings.APPEND_SLASH and (not old_url[1].endswith('/')):
+        if resolve:
+            try:
+                urlresolvers.resolve(request.path)
+            except urlresolvers.Resolver404:
+                new_url[1] = new_url[1] + '/'
+        else:
+            new_url[1] = new_url[1] + '/'
+
+    if new_url != old_url:
+        # Redirect if the target url exists
+        try:
+            if resolve and new_url[0] == old_url[0] and new_url[1] != old_url[1]:
+                urlresolvers.resolve(new_url[1])
+        except urlresolvers.Resolver404:
+            pass
+        else:
+            if new_url[0]:
+                newurl = "%s://%s%s" % (
+                    request.is_secure() and 'https' or 'http',
+                    new_url[0], urlquote(new_url[1]))
+            else:
+                newurl = urlquote(new_url[1])
+            if request.GET:
+                newurl += '?' + request.GET.urlencode()
+            if settings.DEBUG and request.method == 'POST':
+                raise RuntimeError, (""
+                "You called this URL via POST, but the URL doesn't end "
+                "in a slash and you have APPEND_SLASH set. Django can't "
+                "redirect to the slash URL while maintaining POST data. "
+                "Change your form to point to %s%s (note the trailing "
+                "slash), or set APPEND_SLASH=False in your Django "
+                "settings.") % (new_url[0], new_url[1])
+            return http.HttpResponsePermanentRedirect(newurl)
+
 def _is_ignorable_404(uri):
     "Returns True if a 404 at the given URL *shouldn't* notify the site managers"
     for start in settings.IGNORABLE_404_STARTS:
Index: tests/regressiontests/middleware/tests.py
===================================================================
--- tests/regressiontests/middleware/tests.py	(revision 7711)
+++ tests/regressiontests/middleware/tests.py	(working copy)
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from django.test import TestCase
-from django.http import HttpRequest
+from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
 from django.middleware.common import CommonMiddleware
 from django.conf import settings
 
@@ -89,3 +89,72 @@
         self.assertEquals(
             r['Location'],
             'http://testserver/middleware/needsquoting%23/')
+
+    def test_append_slash_enabled_custom_middleware_slash(self):
+        """
+        Tests that if APPEND_SLASH is True and some intermediate middleware
+        (e.g. FlatpageFallbackMiddleware) has processed the request and
+        returned a 404 response, then this is redirected.
+        """
+        settings.APPEND_SLASH = True
+        request = self._get_request('custom/slash')
+        common_middleware = CommonMiddleware()
+        custom_middleware = CustomMiddleware()
+        response = common_middleware.process_request(request)
+        self.assertEquals(response, None)
+        response = custom_middleware.process_request(request)
+        self.assertEquals(response.status_code, 404)
+        response = custom_middleware.process_response(request, response)
+        self.assertEquals(response.status_code, 404)
+        response = common_middleware.process_response(request, response)
+        self.assertEquals(response.status_code, 301)
+        self.assertEquals(
+            response['Location'],
+            'http://testserver/middleware/custom/slash/')
+
+    def test_append_slash_disabled_custom_middleware_slash(self):
+        """
+        Tests that if APPEND_SLASH is False and some intermediate middleware
+        (e.g. FlatpageFallbackMiddleware) has processed the request and
+        returned a 404 response, then this is not redirected.
+        """
+        settings.APPEND_SLASH = False
+        request = self._get_request('custom/slash')
+        common_middleware = CommonMiddleware()
+        custom_middleware = CustomMiddleware()
+        response = common_middleware.process_request(request)
+        self.assertEquals(response, None)
+        response = custom_middleware.process_request(request)
+        self.assertEquals(response.status_code, 404)
+        response = custom_middleware.process_response(request, response)
+        self.assertEquals(response.status_code, 404)
+        response = common_middleware.process_response(request, response)
+        self.assertEquals(response.status_code, 404)
+
+    def test_append_slash_custom_middleware_slashless(self):
+        """
+        Tests that whether APPEND_SLASH is True or not, and some intermediate
+        middleware (e.g. FlatpageFallbackMiddleware) has processed the request
+        and returned a response, this is not redirected.
+        """
+        for append_slash in (True, False):
+            settings.APPEND_SLASH = append_slash
+            request = self._get_request('custom/noslash')
+            common_middleware = CommonMiddleware()
+            custom_middleware = CustomMiddleware()
+            response = common_middleware.process_request(request)
+            self.assertEquals(response, None)
+            response = custom_middleware.process_request(request)
+            self.assertContains(response, 'Custom middleware response')
+            response = custom_middleware.process_response(request, response)
+            self.assertContains(response, 'Custom middleware response')
+            response = common_middleware.process_response(request, response)
+            self.assertContains(response, 'Custom middleware response')
+
+class CustomMiddleware(object):
+    def process_request(self, request):
+        if request.path in ('/middleware/custom/slash/', '/middleware/custom/noslash'):
+            return HttpResponse('Custom middleware response')
+        return HttpResponseNotFound()
+    def process_response(self, request, response):
+        return response
