Kaydet (Commit) 940b7fd5 authored tarafından Krzysztof Jurewicz's avatar Krzysztof Jurewicz Kaydeden (comit) Claude Paroz

Fixed #21446 -- Allowed not performing redirect in set_language view

Thanks Claude Paroz and Tim Graham for polishing the patch.
üst 12ba20d8
...@@ -34,17 +34,18 @@ def set_language(request): ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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
* Django looks for a ``next`` parameter in the ``POST`` data. redirect to that URL will be performed. Otherwise, Django may fall back to
* If that doesn't exist, or is empty, Django tries the URL in the redirecting the user to the URL from the ``Referer`` header or, if it is not
``Referrer`` header. set, to ``/``, depending on the nature of the request:
* 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. * For AJAX requests, the fallback will be performed only if the ``next``
parameter was set. Otherwise a 204 status code (No Content) will be returned.
* For non-AJAX requests, the fallback will always be performed.
.. 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 ...@@ -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 ...@@ -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/')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment