Django

Code

Ticket #6213: common_middleware.2.py.diff

File common_middleware.2.py.diff, 8.9 kB (added by esaj, 7 months ago)

Updated patch with unit tests.

  • django/middleware/common.py

    old new  
    4040                if user_agent_regex.search(request.META['HTTP_USER_AGENT']): 
    4141                    return http.HttpResponseForbidden('<h1>Forbidden</h1>') 
    4242 
    43         # Check for a redirect based on settings.APPEND_SLASH 
    44         # and settings.PREPEND_WWW 
    45         host = request.get_host() 
    46         old_url = [host, request.path] 
    47         new_url = old_url[:] 
     43        _response = _handle_redirects(request) 
     44        if _response: 
     45            return _response 
    4846 
    49         if (settings.PREPEND_WWW and old_url[0] and 
    50                 not old_url[0].startswith('www.')): 
    51             new_url[0] = 'www.' + old_url[0] 
    52  
    53         # Append a slash if APPEND_SLASH is set and the URL doesn't have a 
    54         # trailing slash and there is no pattern for the current path 
    55         if settings.APPEND_SLASH and (not old_url[1].endswith('/')): 
    56             try: 
    57                 urlresolvers.resolve(request.path) 
    58             except urlresolvers.Resolver404: 
    59                 new_url[1] = new_url[1] + '/' 
    60                 if settings.DEBUG and request.method == 'POST': 
    61                     raise RuntimeError, ("" 
    62                     "You called this URL via POST, but the URL doesn't end " 
    63                     "in a slash and you have APPEND_SLASH set. Django can't " 
    64                     "redirect to the slash URL while maintaining POST data. " 
    65                     "Change your form to point to %s%s (note the trailing " 
    66                     "slash), or set APPEND_SLASH=False in your Django " 
    67                     "settings.") % (new_url[0], new_url[1]) 
    68  
    69         if new_url != old_url: 
    70             # Redirect if the target url exists 
    71             try: 
    72                 urlresolvers.resolve(new_url[1]) 
    73             except urlresolvers.Resolver404: 
    74                 pass 
    75             else: 
    76                 if new_url[0]: 
    77                     newurl = "%s://%s%s" % ( 
    78                         request.is_secure() and 'https' or 'http', 
    79                         new_url[0], urlquote(new_url[1])) 
    80                 else: 
    81                     newurl = urlquote(new_url[1]) 
    82                 if request.GET: 
    83                     newurl += '?' + request.GET.urlencode() 
    84                 return http.HttpResponsePermanentRedirect(newurl) 
    85  
    8647        return None 
    8748 
    8849    def process_response(self, request, response): 
    8950        "Check for a flat page (for 404s) and calculate the Etag, if needed." 
    9051        if response.status_code == 404: 
     52            _response = _handle_redirects(request, resolve=False) 
     53            if _response: 
     54                return _response 
    9155            if settings.SEND_BROKEN_LINK_EMAILS: 
    9256                # If the referrer was from an internal link or a non-search-engine site, 
    9357                # send a note to the managers. 
     
    11882 
    11983        return response 
    12084 
     85def _handle_redirects(request, resolve=True): 
     86    host = request.get_host() 
     87    old_url = [host, request.path] 
     88    new_url = old_url[:] 
     89 
     90    if (settings.PREPEND_WWW and old_url[0] and 
     91            not old_url[0].startswith('www.')): 
     92        new_url[0] = 'www.' + old_url[0] 
     93 
     94    # Append a slash if APPEND_SLASH is set and the URL doesn't have a 
     95    # trailing slash and there is no pattern for the current path 
     96    if settings.APPEND_SLASH and (not old_url[1].endswith('/')): 
     97        if resolve: 
     98            try: 
     99                urlresolvers.resolve(request.path) 
     100            except urlresolvers.Resolver404: 
     101                new_url[1] = new_url[1] + '/' 
     102        else: 
     103            new_url[1] = new_url[1] + '/' 
     104 
     105    if new_url != old_url: 
     106        # Redirect if the target url exists 
     107        try: 
     108            if resolve and new_url[0] == new_url[0] and new_url[1] != old_url[1]: 
     109                urlresolvers.resolve(new_url[1]) 
     110        except urlresolvers.Resolver404: 
     111            pass 
     112        else: 
     113            if new_url[0]: 
     114                newurl = "%s://%s%s" % ( 
     115                    request.is_secure() and 'https' or 'http', 
     116                    new_url[0], urlquote(new_url[1])) 
     117            else: 
     118                newurl = urlquote(new_url[1]) 
     119            if request.GET: 
     120                newurl += '?' + request.GET.urlencode() 
     121            if settings.DEBUG and request.method == 'POST': 
     122                raise RuntimeError, ("" 
     123                "You called this URL via POST, but the URL doesn't end " 
     124                "in a slash and you have APPEND_SLASH set. Django can't " 
     125                "redirect to the slash URL while maintaining POST data. " 
     126                "Change your form to point to %s%s (note the trailing " 
     127                "slash), or set APPEND_SLASH=False in your Django " 
     128                "settings.") % (new_url[0], new_url[1]) 
     129            return http.HttpResponsePermanentRedirect(newurl) 
     130 
    121131def _is_ignorable_404(uri): 
    122132    "Returns True if a 404 at the given URL *shouldn't* notify the site managers" 
    123133    for start in settings.IGNORABLE_404_STARTS: 
  • tests/regressiontests/middleware/tests.py

    old new  
    11# -*- coding: utf-8 -*- 
    22 
    33from django.test import TestCase 
    4 from django.http import HttpRequest 
     4from django.http import HttpRequest, HttpResponse, HttpResponseNotFound 
    55from django.middleware.common import CommonMiddleware 
    66from django.conf import settings 
    77 
     
    8989        self.assertEquals( 
    9090            r['Location'], 
    9191            'http://testserver/middleware/needsquoting%23/') 
     92 
     93    def test_append_slash_enabled_custom_middleware_slash(self): 
     94        """ 
     95        Tests that if some intermediate middleware (e.g. 
     96        FlatpageFallbackMiddleware) has processed the request and returned a 
     97        response, this is not redirected. 
     98        """ 
     99        settings.APPEND_SLASH = True 
     100        request = self._get_request('custom/slash') 
     101        common_middleware = CommonMiddleware() 
     102        custom_middleware = CustomMiddleware() 
     103        response = common_middleware.process_request(request) 
     104        if response is None: 
     105            response = custom_middleware.process_request(request) 
     106            response = custom_middleware.process_response(request, response) 
     107        response = common_middleware.process_response(request, response) 
     108        self.assertEquals(response.status_code, 301) 
     109        self.assertEquals( 
     110            response['Location'], 
     111            'http://testserver/middleware/custom/slash/') 
     112 
     113    def test_append_slash_disabled_custom_middleware_slash(self): 
     114        """ 
     115        Tests that if some intermediate middleware (e.g. 
     116        FlatpageFallbackMiddleware) has processed the request and returned a 
     117        response, this is not redirected. 
     118        """ 
     119        settings.APPEND_SLASH = False 
     120        request = self._get_request('custom/slash') 
     121        common_middleware = CommonMiddleware() 
     122        custom_middleware = CustomMiddleware() 
     123        response = common_middleware.process_request(request) 
     124        self.assertEquals(response, None) 
     125        response = custom_middleware.process_request(request) 
     126        self.assertEquals(response.status_code, 404) 
     127        response = custom_middleware.process_response(request, response) 
     128        self.assertEquals(response.status_code, 404) 
     129        response = common_middleware.process_response(request, response) 
     130        self.assertEquals(response.status_code, 404) 
     131 
     132    def test_append_slash_custom_middleware_slashless(self): 
     133        """ 
     134        Tests that if some intermediate middleware (e.g. 
     135        FlatpageFallbackMiddleware) has processed the request and returned a 
     136        response, this is not redirected. 
     137        """ 
     138        for append_slash in (True, False): 
     139            settings.APPEND_SLASH = append_slash 
     140            request = self._get_request('custom/noslash') 
     141            common_middleware = CommonMiddleware() 
     142            custom_middleware = CustomMiddleware() 
     143            response = common_middleware.process_request(request) 
     144            self.assertEquals(response, None) 
     145            response = custom_middleware.process_request(request) 
     146            self.assertContains(response, 'Custom middleware response') 
     147            response = custom_middleware.process_response(request, response) 
     148            self.assertContains(response, 'Custom middleware response') 
     149            response = common_middleware.process_response(request, response) 
     150            self.assertContains(response, 'Custom middleware response') 
     151 
     152class CustomMiddleware(object): 
     153    def process_request(self, request): 
     154        if request.path in ('/middleware/custom/slash/', '/middleware/custom/noslash'): 
     155            return HttpResponse('Custom middleware response') 
     156        return HttpResponseNotFound() 
     157    def process_response(self, request, response): 
     158        return response