Commit 9128762f authored by Bas Peschier's avatar Bas Peschier Committed by Tim Graham
Browse files

Fixed #19910 -- Added slash to i18n redirect if APPEND_SLASH is set.

This introduces a force_append_slash argument for request.get_full_path()
which is used by RedirectFallbackMiddleware and CommonMiddleware when
handling redirects for settings.APPEND_SLASH.
parent 3e64f3d0
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -34,12 +34,12 @@ class RedirectFallbackMiddleware(object):
            r = Redirect.objects.get(site=current_site, old_path=full_path)
        except Redirect.DoesNotExist:
            pass
        if settings.APPEND_SLASH and not request.path.endswith('/'):
            # Try appending a trailing slash.
            path_len = len(request.path)
            full_path = full_path[:path_len] + '/' + full_path[path_len:]
        if r is None and settings.APPEND_SLASH and not request.path.endswith('/'):
            try:
                r = Redirect.objects.get(site=current_site, old_path=full_path)
                r = Redirect.objects.get(
                    site=current_site,
                    old_path=request.get_full_path(force_append_slash=True),
                )
            except Redirect.DoesNotExist:
                pass
        if r is not None:
+3 −2
Original line number Diff line number Diff line
@@ -99,11 +99,12 @@ class HttpRequest(object):
                msg += " The domain name provided is not valid according to RFC 1034/1035."
            raise DisallowedHost(msg)

    def get_full_path(self):
    def get_full_path(self, force_append_slash=False):
        # RFC 3986 requires query string arguments to be in the ASCII range.
        # Rather than crash if this doesn't happen, we encode defensively.
        return '%s%s' % (
        return '%s%s%s' % (
            escape_uri_path(self.path),
            '/' if force_append_slash and not self.path.endswith('/') else '',
            ('?' + iri_to_uri(self.META.get('QUERY_STRING', ''))) if self.META.get('QUERY_STRING', '') else ''
        )

+4 −18
Original line number Diff line number Diff line
@@ -6,9 +6,7 @@ from django import http
from django.conf import settings
from django.core import urlresolvers
from django.core.mail import mail_managers
from django.utils import six
from django.utils.encoding import force_text
from django.utils.http import urlquote

logger = logging.getLogger('django.request')

@@ -60,7 +58,7 @@ class CommonMiddleware(object):
        # Check for a redirect based on settings.APPEND_SLASH
        # and settings.PREPEND_WWW
        host = request.get_host()
        old_url = [host, request.path]
        old_url = [host, request.get_full_path()]
        new_url = old_url[:]

        if (settings.PREPEND_WWW and old_url[0] and
@@ -73,7 +71,7 @@ class CommonMiddleware(object):
            urlconf = getattr(request, 'urlconf', None)
            if (not urlresolvers.is_valid_path(request.path_info, urlconf) and
                    urlresolvers.is_valid_path("%s/" % request.path_info, urlconf)):
                new_url[1] = new_url[1] + '/'
                new_url[1] = request.get_full_path(force_append_slash=True)
                if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'):
                    raise RuntimeError((""
                    "You called this URL via %(method)s, but the URL doesn't end "
@@ -89,21 +87,9 @@ class CommonMiddleware(object):
        if new_url[0] != old_url[0]:
            newurl = "%s://%s%s" % (
                request.scheme,
                new_url[0], urlquote(new_url[1]))
                new_url[0], new_url[1])
        else:
            newurl = urlquote(new_url[1])
        if request.META.get('QUERY_STRING', ''):
            if six.PY3:
                newurl += '?' + request.META['QUERY_STRING']
            else:
                # `query_string` is a bytestring. Appending it to the unicode
                # string `newurl` will fail if it isn't ASCII-only. This isn't
                # allowed; only broken software generates such query strings.
                # Better drop the invalid query string than crash (#15152).
                try:
                    newurl += '?' + request.META['QUERY_STRING'].decode()
                except UnicodeDecodeError:
                    pass
            newurl = new_url[1]
        return self.response_redirect_class(newurl)

    def process_response(self, request, response):
+8 −5
Original line number Diff line number Diff line
@@ -34,15 +34,18 @@ class LocaleMiddleware(object):
            urlconf = getattr(request, 'urlconf', None)
            language_path = '/%s%s' % (language, request.path_info)
            path_valid = is_valid_path(language_path, urlconf)
            if (not path_valid and settings.APPEND_SLASH
                    and not language_path.endswith('/')):
                path_valid = is_valid_path("%s/" % language_path, urlconf)
            path_needs_slash = (
                not path_valid and (
                    settings.APPEND_SLASH and not language_path.endswith('/')
                    and is_valid_path('%s/' % language_path, urlconf)
                )
            )

            if path_valid:
            if path_valid or path_needs_slash:
                script_prefix = get_script_prefix()
                # Insert language after the script prefix and before the
                # rest of the URL
                language_url = request.get_full_path().replace(
                language_url = request.get_full_path(force_append_slash=path_needs_slash).replace(
                    script_prefix,
                    '%s%s/' % (script_prefix, language),
                    1
+2 −2
Original line number Diff line number Diff line
@@ -247,8 +247,8 @@ class URLRedirectWithoutTrailingSlashTests(URLTestCaseBase):

    def test_en_redirect(self):
        response = self.client.get('/account/register', HTTP_ACCEPT_LANGUAGE='en', follow=True)
        # target status code of 301 because of CommonMiddleware redirecting
        self.assertIn(('/en/account/register/', 301), response.redirect_chain)
        # We only want one redirect, bypassing CommonMiddleware
        self.assertListEqual(response.redirect_chain, [('/en/account/register/', 302)])
        self.assertRedirects(response, '/en/account/register/', 302)

        response = self.client.get('/prefixed.xml', HTTP_ACCEPT_LANGUAGE='en', follow=True)