Skip to content

Commit 5e28401

Browse files
authored
Merge pull request #5081 from stsewd/allow-query-params-in-redirects
Allow query params in redirects
2 parents 4c8c4f6 + daff0d8 commit 5e28401

File tree

6 files changed

+63
-19
lines changed

6 files changed

+63
-19
lines changed

readthedocs/core/resolver.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""URL resolver for documentation."""
44

55
import re
6+
from urllib.parse import urlunparse
67

78
from django.conf import settings
89

@@ -159,8 +160,8 @@ def resolve_domain(self, project, private=None):
159160
return getattr(settings, 'PRODUCTION_DOMAIN')
160161

161162
def resolve(
162-
self, project, require_https=False, filename='', private=None,
163-
**kwargs
163+
self, project, require_https=False, filename='', query_params='',
164+
private=None, **kwargs
164165
):
165166
if private is None:
166167
version_slug = kwargs.get('version_slug')
@@ -192,12 +193,10 @@ def resolve(
192193
])
193194
protocol = 'https' if use_https_protocol else 'http'
194195

195-
return '{protocol}://{domain}{path}'.format(
196-
protocol=protocol,
197-
domain=domain,
198-
path=self.
199-
resolve_path(project, filename=filename, private=private, **kwargs),
196+
path = self.resolve_path(
197+
project, filename=filename, private=private, **kwargs
200198
)
199+
return urlunparse((protocol, domain, path, '', query_params, ''))
201200

202201
def _get_canonical_project(self, project, projects=None):
203202
"""

readthedocs/core/views/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli
113113
114114
Marking exception as optional to make /404/ testing page to work.
115115
"""
116-
response = get_redirect_response(request, path=request.get_full_path())
116+
response = get_redirect_response(request, full_path=request.get_full_path())
117117

118118
if response:
119119
if response.url == request.build_absolute_uri():

readthedocs/core/views/serve.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import mimetypes
3131
import os
3232
from functools import wraps
33+
from urllib.parse import urlparse
3334

3435
from django.conf import settings
3536
from django.http import Http404, HttpResponse, HttpResponseRedirect
@@ -107,15 +108,26 @@ def inner_view( # noqa
107108
@map_subproject_slug
108109
def redirect_project_slug(request, project, subproject): # pylint: disable=unused-argument
109110
"""Handle / -> /en/latest/ directs on subdomains."""
110-
return HttpResponseRedirect(resolve(subproject or project))
111+
urlparse_result = urlparse(request.get_full_path())
112+
return HttpResponseRedirect(
113+
resolve(
114+
subproject or project,
115+
query_params=urlparse_result.query,
116+
)
117+
)
111118

112119

113120
@map_project_slug
114121
@map_subproject_slug
115122
def redirect_page_with_filename(request, project, subproject, filename): # pylint: disable=unused-argument # noqa
116123
"""Redirect /page/file.html to /en/latest/file.html."""
124+
urlparse_result = urlparse(request.get_full_path())
117125
return HttpResponseRedirect(
118-
resolve(subproject or project, filename=filename),
126+
resolve(
127+
subproject or project,
128+
filename=filename,
129+
query_params=urlparse_result.query,
130+
)
119131
)
120132

121133

readthedocs/redirects/managers.py

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def get_redirect_path(self, path, language=None, version_slug=None):
1717
)
1818
if new_path:
1919
return new_path
20+
return None
2021

2122

2223
RedirectManager = Manager.from_queryset(RedirectQuerySet)

readthedocs/redirects/utils.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# -*- coding: utf-8 -*-
2-
31
"""
42
Redirection view support.
53
@@ -11,6 +9,7 @@
119
"""
1210
import logging
1311
import re
12+
from urllib.parse import urlparse, urlunparse
1413

1514
from django.http import HttpResponseRedirect
1615

@@ -69,25 +68,26 @@ def language_and_version_from_path(path):
6968
return None, None, path
7069

7170

72-
def get_redirect_response(request, path):
73-
project, path = project_and_path_from_request(request, path)
71+
def get_redirect_response(request, full_path):
72+
project, full_path = project_and_path_from_request(request, full_path)
7473
if not project:
7574
return None
7675

7776
language = None
7877
version_slug = None
78+
schema, netloc, path, params, query, fragments = urlparse(full_path)
7979
if not project.single_version:
8080
language, version_slug, path = language_and_version_from_path(path)
8181

82-
new_path = project.redirects.get_redirect_path(
83-
path=path,
84-
language=language,
85-
version_slug=version_slug,
82+
path = project.redirects.get_redirect_path(
83+
path=path, language=language, version_slug=version_slug
8684
)
8785

88-
if new_path is None:
86+
if path is None:
8987
return None
9088

89+
new_path = urlunparse((schema, netloc, path, params, query, fragments))
90+
9191
# Re-use the domain and protocol used in the current request.
9292
# Redirects shouldn't change the domain, version or language.
9393
new_path = request.build_absolute_uri(new_path)

readthedocs/rtd_tests/tests/test_redirects.py

+32
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ def test_proper_page_on_main_site(self):
5858
'http://readthedocs.org/docs/pip/en/latest/test.html',
5959
)
6060

61+
def test_page_redirect_with_query_params(self):
62+
r = self.client.get('/docs/pip/page/test.html?foo=bar')
63+
self.assertEqual(r.status_code, 302)
64+
self.assertEqual(
65+
r['Location'],
66+
'http://readthedocs.org/docs/pip/en/latest/test.html?foo=bar'
67+
)
68+
6169
# If slug is neither valid lang nor valid version, it should 404.
6270
# TODO: This should 404 directly, not redirect first
6371
def test_improper_url_with_nonexistent_slug(self):
@@ -97,6 +105,15 @@ def test_proper_subdomain(self):
97105
r['Location'], 'http://pip.readthedocs.org/en/latest/',
98106
)
99107

108+
@override_settings(USE_SUBDOMAIN=True)
109+
def test_root_redirect_with_query_params(self):
110+
r = self.client.get('/?foo=bar', HTTP_HOST='pip.readthedocs.org')
111+
self.assertEqual(r.status_code, 302)
112+
self.assertEqual(
113+
r['Location'],
114+
'http://pip.readthedocs.org/en/latest/?foo=bar'
115+
)
116+
100117
# Specific Page Redirects
101118
@override_settings(USE_SUBDOMAIN=True)
102119
def test_proper_page_on_subdomain(self):
@@ -190,6 +207,21 @@ def test_redirect_page(self):
190207
r['Location'], 'http://pip.readthedocs.org/en/latest/tutorial/install.html',
191208
)
192209

210+
@override_settings(USE_SUBDOMAIN=True)
211+
def test_redirect_with_query_params(self):
212+
Redirect.objects.create(
213+
project=self.pip, redirect_type='page',
214+
from_url='/install.html', to_url='/tutorial/install.html'
215+
)
216+
r = self.client.get(
217+
'/install.html?foo=bar', HTTP_HOST='pip.readthedocs.org'
218+
)
219+
self.assertEqual(r.status_code, 302)
220+
self.assertEqual(
221+
r['Location'],
222+
'http://pip.readthedocs.org/en/latest/tutorial/install.html?foo=bar'
223+
)
224+
193225
@override_settings(USE_SUBDOMAIN=True)
194226
def test_redirect_exact(self):
195227
Redirect.objects.create(

0 commit comments

Comments
 (0)