diff --git a/mangum/handlers/aws_alb.py b/mangum/handlers/aws_alb.py index 33e0968f..d9933e87 100644 --- a/mangum/handlers/aws_alb.py +++ b/mangum/handlers/aws_alb.py @@ -16,6 +16,42 @@ class AwsAlb(AbstractHandler): TYPE = "AWS_ALB" + def encode_query_string(self) -> bytes: + """ + Encodes the queryStringParameters. + The parameters must be decoded, and then encoded again to prevent double + encoding. + + https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html # noqa: E501 + "If the query parameters are URL-encoded, the load balancer does not decode + them. You must decode them in your Lambda function." + + Issue: https://github.com/jordaneremieff/mangum/issues/178 + """ + + params = self.trigger_event.get("multiValueQueryStringParameters") + if not params: + params = self.trigger_event.get("queryStringParameters") + if not params: + return b"" # No query parameters, exit early with an empty byte string. + + # Loop through the query parameters, unquote each key and value and append the + # pair as a tuple to the query list. If value is a list or a tuple, loop + # through the nested struture and unqote. + query = [] + for key, value in params.items(): + if isinstance(value, (tuple, list)): + for v in value: + query.append( + (urllib.parse.unquote_plus(key), urllib.parse.unquote_plus(v)) + ) + else: + query.append( + (urllib.parse.unquote_plus(key), urllib.parse.unquote_plus(value)) + ) + + return urllib.parse.urlencode(query).encode() + @property def request(self) -> Request: event = self.trigger_event @@ -27,9 +63,7 @@ def request(self) -> Request: source_ip = headers.get("x-forwarded-for", "") path = event["path"] http_method = event["httpMethod"] - query_string = urllib.parse.urlencode( - event.get("queryStringParameters", {}), doseq=True - ).encode() + query_string = self.encode_query_string() server_name = headers.get("host", "mangum") if ":" not in server_name: diff --git a/tests/handlers/test_aws_alb.py b/tests/handlers/test_aws_alb.py index d922c37d..6d9335d1 100644 --- a/tests/handlers/test_aws_alb.py +++ b/tests/handlers/test_aws_alb.py @@ -15,7 +15,7 @@ def get_mock_aws_alb_event( }, "httpMethod": method, "path": path, - "queryStringParameters": multi_value_query_parameters + "multiValueQueryStringParameters": multi_value_query_parameters if multi_value_query_parameters else {}, "headers": { @@ -52,7 +52,13 @@ def test_aws_alb_basic(): }, "httpMethod": "GET", "path": "/lambda", - "queryStringParameters": {"query": "1234ABCD"}, + "queryStringParameters": { + "q1": "1234ABCD", + "q2": "b c", # not encoded + "q3": "b%20c", # encoded + "q4": "/some/path/", # not encoded + "q5": "%2Fsome%2Fpath%2F", # encoded + }, "headers": { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9," "image/webp,image/apng,*/*;q=0.8", @@ -107,7 +113,7 @@ def test_aws_alb_basic(): "http_version": "1.1", "method": "GET", "path": "/lambda", - "query_string": b"query=1234ABCD", + "query_string": b"q1=1234ABCD&q2=b+c&q3=b+c&q4=%2Fsome%2Fpath%2F&q5=%2Fsome%2Fpath%2F", # noqa: E501 "raw_path": None, "root_path": "", "scheme": "http",