mirror of https://github.com/django/django.git
Fixed #21446 -- Allowed not performing redirect in set_language view
Thanks Claude Paroz and Tim Graham for polishing the patch.
This commit is contained in:
parent
12ba20d83c
commit
940b7fd5cb
|
@ -34,17 +34,18 @@ def set_language(request):
|
||||||
any state.
|
any state.
|
||||||
"""
|
"""
|
||||||
next = request.POST.get('next', request.GET.get('next'))
|
next = request.POST.get('next', request.GET.get('next'))
|
||||||
if not is_safe_url(url=next, host=request.get_host()):
|
if (next or not request.is_ajax()) and not is_safe_url(url=next, host=request.get_host()):
|
||||||
next = request.META.get('HTTP_REFERER')
|
next = request.META.get('HTTP_REFERER')
|
||||||
if not is_safe_url(url=next, host=request.get_host()):
|
if not is_safe_url(url=next, host=request.get_host()):
|
||||||
next = '/'
|
next = '/'
|
||||||
response = http.HttpResponseRedirect(next)
|
response = http.HttpResponseRedirect(next) if next else http.HttpResponse(status=204)
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
lang_code = request.POST.get(LANGUAGE_QUERY_PARAMETER)
|
lang_code = request.POST.get(LANGUAGE_QUERY_PARAMETER)
|
||||||
if lang_code and check_for_language(lang_code):
|
if lang_code and check_for_language(lang_code):
|
||||||
next_trans = translate_url(next, lang_code)
|
if next:
|
||||||
if next_trans != next:
|
next_trans = translate_url(next, lang_code)
|
||||||
response = http.HttpResponseRedirect(next_trans)
|
if next_trans != next:
|
||||||
|
response = http.HttpResponseRedirect(next_trans)
|
||||||
if hasattr(request, 'session'):
|
if hasattr(request, 'session'):
|
||||||
request.session[LANGUAGE_SESSION_KEY] = lang_code
|
request.session[LANGUAGE_SESSION_KEY] = lang_code
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -267,6 +267,10 @@ Internationalization
|
||||||
:func:`~django.conf.urls.i18n.i18n_patterns` to ``False``, you can allow
|
:func:`~django.conf.urls.i18n.i18n_patterns` to ``False``, you can allow
|
||||||
accessing the default language without a URL prefix.
|
accessing the default language without a URL prefix.
|
||||||
|
|
||||||
|
* :func:`~django.views.i18n.set_language` now returns a 204 status code (No
|
||||||
|
Content) for AJAX requests when there is no ``next`` parameter in ``POST`` or
|
||||||
|
``GET``.
|
||||||
|
|
||||||
Management Commands
|
Management Commands
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -695,6 +699,9 @@ Miscellaneous
|
||||||
:meth:`~django.test.Client.login()` method no longer always rejects inactive
|
:meth:`~django.test.Client.login()` method no longer always rejects inactive
|
||||||
users but instead delegates this decision to the authentication backend.
|
users but instead delegates this decision to the authentication backend.
|
||||||
|
|
||||||
|
* :func:`django.views.i18n.set_language` may now return a 204 status code for
|
||||||
|
AJAX requests.
|
||||||
|
|
||||||
.. _deprecated-features-1.10:
|
.. _deprecated-features-1.10:
|
||||||
|
|
||||||
Features deprecated in 1.10
|
Features deprecated in 1.10
|
||||||
|
|
|
@ -1788,14 +1788,21 @@ saves the language choice in the user's session. Otherwise, it saves the
|
||||||
language choice in a cookie that is by default named ``django_language``.
|
language choice in a cookie that is by default named ``django_language``.
|
||||||
(The name can be changed through the :setting:`LANGUAGE_COOKIE_NAME` setting.)
|
(The name can be changed through the :setting:`LANGUAGE_COOKIE_NAME` setting.)
|
||||||
|
|
||||||
After setting the language choice, Django redirects the user, following this
|
After setting the language choice, Django looks for a ``next`` parameter in the
|
||||||
algorithm:
|
``POST`` or ``GET`` data. If that is found and Django considers it to be a safe
|
||||||
|
URL (i.e. it doesn't point to a different host and uses a safe scheme), a
|
||||||
|
redirect to that URL will be performed. Otherwise, Django may fall back to
|
||||||
|
redirecting the user to the URL from the ``Referer`` header or, if it is not
|
||||||
|
set, to ``/``, depending on the nature of the request:
|
||||||
|
|
||||||
* Django looks for a ``next`` parameter in the ``POST`` data.
|
* For AJAX requests, the fallback will be performed only if the ``next``
|
||||||
* If that doesn't exist, or is empty, Django tries the URL in the
|
parameter was set. Otherwise a 204 status code (No Content) will be returned.
|
||||||
``Referrer`` header.
|
* For non-AJAX requests, the fallback will always be performed.
|
||||||
* If that's empty -- say, if a user's browser suppresses that header --
|
|
||||||
then the user will be redirected to ``/`` (the site root) as a fallback.
|
.. versionchanged:: 1.10
|
||||||
|
|
||||||
|
Returning a 204 status code for AJAX requests when no redirect is specified
|
||||||
|
is new.
|
||||||
|
|
||||||
Here's example HTML template code:
|
Here's example HTML template code:
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,9 @@ from django.test.selenium import SeleniumTestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils._os import upath
|
from django.utils._os import upath
|
||||||
from django.utils.translation import LANGUAGE_SESSION_KEY, override
|
from django.utils.translation import (
|
||||||
|
LANGUAGE_SESSION_KEY, get_language, override,
|
||||||
|
)
|
||||||
|
|
||||||
from ..urls import locale_dir
|
from ..urls import locale_dir
|
||||||
|
|
||||||
|
@ -22,29 +24,98 @@ from ..urls import locale_dir
|
||||||
class I18NTests(TestCase):
|
class I18NTests(TestCase):
|
||||||
""" Tests django views in django/views/i18n.py """
|
""" Tests django views in django/views/i18n.py """
|
||||||
|
|
||||||
|
def _get_inactive_language_code(self):
|
||||||
|
"""Return language code for a language which is not activated."""
|
||||||
|
current_language = get_language()
|
||||||
|
return [code for code, name in settings.LANGUAGES if not code == current_language][0]
|
||||||
|
|
||||||
def test_setlang(self):
|
def test_setlang(self):
|
||||||
"""
|
"""
|
||||||
The set_language view can be used to change the session language.
|
The set_language view can be used to change the session language.
|
||||||
|
|
||||||
The user is redirected to the 'next' argument if provided.
|
The user is redirected to the 'next' argument if provided.
|
||||||
"""
|
"""
|
||||||
for lang_code, lang_name in settings.LANGUAGES:
|
lang_code = self._get_inactive_language_code()
|
||||||
post_data = dict(language=lang_code, next='/')
|
post_data = dict(language=lang_code, next='/')
|
||||||
response = self.client.post('/i18n/setlang/', data=post_data)
|
response = self.client.post('/i18n/setlang/', post_data, HTTP_REFERER='/i_should_not_be_used/')
|
||||||
self.assertRedirects(response, '/')
|
self.assertRedirects(response, '/')
|
||||||
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
def test_setlang_unsafe_next(self):
|
def test_setlang_unsafe_next(self):
|
||||||
"""
|
"""
|
||||||
The set_language view only redirects to the 'next' argument if it is
|
The set_language view only redirects to the 'next' argument if it is
|
||||||
"safe".
|
"safe".
|
||||||
"""
|
"""
|
||||||
lang_code, lang_name = settings.LANGUAGES[0]
|
lang_code = self._get_inactive_language_code()
|
||||||
post_data = dict(language=lang_code, next='//unsafe/redirection/')
|
post_data = dict(language=lang_code, next='//unsafe/redirection/')
|
||||||
response = self.client.post('/i18n/setlang/', data=post_data)
|
response = self.client.post('/i18n/setlang/', data=post_data)
|
||||||
self.assertEqual(response.url, '/')
|
self.assertEqual(response.url, '/')
|
||||||
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
|
def test_setlang_redirect_to_referer(self):
|
||||||
|
"""
|
||||||
|
The set_language view redirects to the URL in the referer header when
|
||||||
|
there isn't a "next" parameter.
|
||||||
|
"""
|
||||||
|
lang_code = self._get_inactive_language_code()
|
||||||
|
post_data = dict(language=lang_code)
|
||||||
|
response = self.client.post('/i18n/setlang/', post_data, HTTP_REFERER='/i18n/')
|
||||||
|
self.assertRedirects(response, '/i18n/', fetch_redirect_response=False)
|
||||||
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
|
def test_setlang_default_redirect(self):
|
||||||
|
"""
|
||||||
|
The set_language view redirects to '/' when there isn't a referer or
|
||||||
|
"next" parameter.
|
||||||
|
"""
|
||||||
|
lang_code = self._get_inactive_language_code()
|
||||||
|
post_data = dict(language=lang_code)
|
||||||
|
response = self.client.post('/i18n/setlang/', post_data)
|
||||||
|
self.assertRedirects(response, '/')
|
||||||
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
|
def test_setlang_performs_redirect_for_ajax_if_explicitly_requested(self):
|
||||||
|
"""
|
||||||
|
The set_language view redirects to the "next" parameter for AJAX calls.
|
||||||
|
"""
|
||||||
|
lang_code = self._get_inactive_language_code()
|
||||||
|
post_data = dict(language=lang_code, next='/')
|
||||||
|
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
self.assertRedirects(response, '/')
|
||||||
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
|
def test_setlang_doesnt_perform_a_redirect_to_referer_for_ajax(self):
|
||||||
|
"""
|
||||||
|
The set_language view doesn't redirect to the HTTP referer header for
|
||||||
|
AJAX calls.
|
||||||
|
"""
|
||||||
|
lang_code = self._get_inactive_language_code()
|
||||||
|
post_data = dict(language=lang_code)
|
||||||
|
headers = {'HTTP_REFERER': '/', 'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
|
||||||
|
response = self.client.post('/i18n/setlang/', post_data, **headers)
|
||||||
|
self.assertEqual(response.status_code, 204)
|
||||||
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
|
def test_setlang_doesnt_perform_a_default_redirect_for_ajax(self):
|
||||||
|
"""
|
||||||
|
The set_language view returns 204 for AJAX calls by default.
|
||||||
|
"""
|
||||||
|
lang_code = self._get_inactive_language_code()
|
||||||
|
post_data = dict(language=lang_code)
|
||||||
|
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
self.assertEqual(response.status_code, 204)
|
||||||
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
|
def test_setlang_unsafe_next_for_ajax(self):
|
||||||
|
"""
|
||||||
|
The fallback to root URL for the set_language view works for AJAX calls.
|
||||||
|
"""
|
||||||
|
lang_code = self._get_inactive_language_code()
|
||||||
|
post_data = dict(language=lang_code, next='//unsafe/redirection/')
|
||||||
|
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
self.assertEqual(response.url, '/')
|
||||||
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
def test_setlang_reversal(self):
|
def test_setlang_reversal(self):
|
||||||
self.assertEqual(reverse('set_language'), '/i18n/setlang/')
|
self.assertEqual(reverse('set_language'), '/i18n/setlang/')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue