diff --git a/pyproject.toml b/pyproject.toml index de53856..f7f687a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,11 +45,11 @@ dependencies = [ "urllib3>=1.12", ] optional-dependencies.diazo = [ - "diazo>=1.0.5", + "diazo==1.5.0", "lxml>=3.4", ] optional-dependencies.tests = [ - "diazo", + "diazo==1.5.0", "lxml>=3.4", "coverage", "flake8", diff --git a/revproxy/views.py b/revproxy/views.py index 3298d46..6d8dd21 100644 --- a/revproxy/views.py +++ b/revproxy/views.py @@ -10,12 +10,12 @@ try: from django.utils.six.moves.urllib.parse import ( - urlparse, urlencode, quote_plus, quote + urlparse, quote_plus, quote, parse_qsl ) except ImportError: # Django 3 has no six from urllib.parse import ( - urlparse, urlencode, quote_plus, quote + urlparse, quote_plus, quote, parse_qsl ) from django.conf import settings @@ -27,7 +27,7 @@ from .exceptions import InvalidUpstream from .response import get_django_response from .transformer import DiazoTransformer -from .utils import normalize_request_headers, encode_items +from .utils import normalize_request_headers # Chars that don't need to be quoted. We use same than nginx: # https://github.com/nginx/nginx/blob/nginx-1.9/src/core/ngx_string.c @@ -176,8 +176,15 @@ def get_quoted_path(self, path): def get_encoded_query_params(self): """Return encoded query params to be used in proxied request""" - get_data = encode_items(self.request.GET.lists()) - return urlencode(get_data) + params = parse_qsl( + self.request.GET.urlencode(), + keep_blank_values=True, + ) + custom_query = '&'.join( + k if v == '' else f'{k}={v}' + for k, v in params + ) + return custom_query def _created_proxy_response(self, request, path): request_payload = request.body diff --git a/tests/test_views.py b/tests/test_views.py index 1ccddfb..34259e8 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -256,3 +256,30 @@ def get_proxy_request_headers(self, request): preload_content=False, decode_content=False, headers=custom_headers) + + def test_raw_query_string_is_preserved_for_flag_params(self): + """ + Regression test: preserve "flag" params (no '=') when proxying. + + Example: `...?. . .&c` must NOT become `...&c=`. + """ + class CustomProxyView(ProxyView): + upstream = 'http://example.com' + + raw_qs = 'q=1&a=2&c' + path = 'some/path' + + # Important: set QUERY_STRING explicitly so Django keeps it in request.META + request = self.factory.get(path, QUERY_STRING=raw_qs) + CustomProxyView.as_view()(request, path) + + url = 'http://example.com/' + path + '?' + raw_qs + headers = {u'Cookie': u''} + self.urlopen.assert_called_with('GET', + url, + body=b'', + redirect=False, + retries=None, + preload_content=False, + decode_content=False, + headers=headers)