Skip to content

Commit 4eff1ba

Browse files
eviljeffwilldurand
andcommitted
Return a 414 if the url we'd redirect to for locale and app is too long (#24145)
* Return a 414 if the url we'd redirect to for locale and app is too long * Update src/olympia/amo/middleware.py Co-authored-by: William Durand <[email protected]> * rm trailing space in suggestion to make linter happy --------- Co-authored-by: William Durand <[email protected]>
1 parent f89da92 commit 4eff1ba

File tree

3 files changed

+30
-7
lines changed

3 files changed

+30
-7
lines changed

src/olympia/amo/middleware.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from django.utils.crypto import constant_time_compare
3232
from django.utils.deprecation import MiddlewareMixin
3333
from django.utils.encoding import force_str, iri_to_uri
34+
from django.utils.http import MAX_URL_LENGTH
3435
from django.utils.translation import activate, gettext_lazy as _
3536

3637
import MySQLdb as mysql
@@ -66,21 +67,29 @@ class LocaleAndAppURLMiddleware(MiddlewareMixin):
6667
4. strip them from the URL so we can do stuff
6768
"""
6869

70+
def redirect(self, redirect_to):
71+
from .utils import HttpResponseURITooLong
72+
73+
# Always use a 302 redirect to avoid users being stuck in case of accidental
74+
# misconfiguration. We need to account for `MAX_URL_LENGTH` to match
75+
# `django.shortcuts.redirect()`'s behavior, see:
76+
# https://github.com/django/django/commit/c880530ddd4fabd5939bab0e148bebe36699432a
77+
if len(redirect_to) > MAX_URL_LENGTH:
78+
return HttpResponseURITooLong()
79+
else:
80+
return HttpResponseRedirect(redirect_to)
81+
6982
def process_request(self, request):
7083
# Find locale, app
7184
prefixer = urlresolvers.Prefixer(request)
7285

73-
# Always use a 302 redirect to avoid users being stuck in case of
74-
# accidental misconfiguration.
75-
redirect_type = HttpResponseRedirect
76-
7786
set_url_prefix(prefixer)
7887
full_path = prefixer.fix(prefixer.shortened_path)
7988

8089
if prefixer.app == amo.MOBILE.short and request.path.rstrip('/').endswith(
8190
'/' + amo.MOBILE.short
8291
):
83-
return redirect_type(request.path.replace('/mobile', '/android'))
92+
return self.redirect(request.path.replace('/mobile', '/android'))
8493

8594
if ('lang' in request.GET or 'app' in request.GET) and not re.match(
8695
settings.SUPPORTED_NONAPPS_NONLOCALES_REGEX, prefixer.shortened_path
@@ -92,7 +101,7 @@ def process_request(self, request):
92101
query = request.GET.dict()
93102
query.pop('app', None)
94103
query.pop('lang', None)
95-
return redirect_type(urlparams(new_path, **query))
104+
return self.redirect(urlparams(new_path, **query))
96105

97106
if full_path != request.path:
98107
query_string = request.META.get('QUERY_STRING', '')
@@ -102,7 +111,7 @@ def process_request(self, request):
102111
query_string = force_str(query_string, errors='ignore')
103112
full_path = f'{full_path}?{query_string}'
104113

105-
response = redirect_type(full_path)
114+
response = self.redirect(full_path)
106115
# Cache the redirect for a year.
107116
if not settings.DEBUG:
108117
patch_cache_control(response, max_age=60 * 60 * 24 * 365)

src/olympia/amo/tests/test_url_prefix.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.conf import settings
33
from django.test.client import Client, RequestFactory
44
from django.urls import resolve, reverse, set_script_prefix
5+
from django.utils.http import MAX_URL_LENGTH
56

67
import pytest
78

@@ -160,6 +161,15 @@ def check(url, expected):
160161
)
161162
check('/en-US/firefox?lang=fr-CA', '/fr/firefox/')
162163

164+
def test_redirect_with_long_url(self):
165+
url = '/firefox/?' + 'a' * (MAX_URL_LENGTH - len('/en-US/firefox/?'))
166+
167+
response = self.process(url)
168+
assert response.status_code == 302, len(response.url)
169+
170+
response = self.process(url + 'a')
171+
assert response.status_code == 414, len(response.url)
172+
163173

164174
class TestPrefixer(TestCase):
165175
def tearDown(self):

src/olympia/amo/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,10 @@ class HttpResponseTemporaryRedirect(HttpResponseRedirectBase):
12911291
status_code = 307
12921292

12931293

1294+
class HttpResponseURITooLong(HttpResponse):
1295+
status_code = 414
1296+
1297+
12941298
def is_safe_url(url, request, allowed_hosts=None):
12951299
"""Use Django's `url_has_allowed_host_and_scheme()` and pass a configured
12961300
list of allowed hosts and enforce HTTPS. `allowed_hosts` can be specified."""

0 commit comments

Comments
 (0)