File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -1749,7 +1749,10 @@ def parse_url(url: str) -> ConnectKwargs:
17491749
17501750 for name , value_list in parse_qs (parsed .query ).items ():
17511751 if value_list and len (value_list ) > 0 :
1752- value = unquote (value_list [0 ])
1752+ # parse_qs() already percent-decodes query values, so use the value
1753+ # as-is; unquoting again here would double-decode (e.g. "%2520" ->
1754+ # "%20" -> " "). See issue #4208.
1755+ value = value_list [0 ]
17531756 parser = URL_QUERY_ARGUMENT_PARSERS .get (name )
17541757 if parser :
17551758 try :
Original file line number Diff line number Diff line change @@ -2291,7 +2291,10 @@ def parse_url(url):
22912291
22922292 for name , value in parse_qs (url .query ).items ():
22932293 if value and len (value ) > 0 :
2294- value = unquote (value [0 ])
2294+ # parse_qs() already percent-decodes query values, so use the value
2295+ # as-is; unquoting again here would double-decode (e.g. "%2520" ->
2296+ # "%20" -> " "). See issue #4208.
2297+ value = value [0 ]
22952298 parser = URL_QUERY_ARGUMENT_PARSERS .get (name )
22962299 if parser :
22972300 try :
Original file line number Diff line number Diff line change @@ -617,6 +617,21 @@ def test_client_name_in_querystring(self):
617617 pool = redis .ConnectionPool .from_url ("redis://location?client_name=test-client" )
618618 assert pool .connection_kwargs ["client_name" ] == "test-client"
619619
620+ def test_querystring_value_percent_decoded_once (self ):
621+ # A single percent-encoded sequence is decoded exactly once.
622+ pool = redis .ConnectionPool .from_url (
623+ "redis://location?client_name=worker%20name"
624+ )
625+ assert pool .connection_kwargs ["client_name" ] == "worker name"
626+
627+ def test_querystring_value_not_double_percent_decoded (self ):
628+ # A literal percent sign in a query value (encoded as %2520) must survive
629+ # as "%20" rather than being decoded a second time into a space. See #4208.
630+ pool = redis .ConnectionPool .from_url (
631+ "redis://location?client_name=worker%2520name"
632+ )
633+ assert pool .connection_kwargs ["client_name" ] == "worker%20name"
634+
620635 def test_invalid_extra_typed_querystring_options (self ):
621636 with pytest .raises (ValueError ):
622637 redis .ConnectionPool .from_url (
Original file line number Diff line number Diff line change @@ -515,6 +515,21 @@ def test_client_name_in_querystring(self):
515515 pool = redis .ConnectionPool .from_url ("redis://location?client_name=test-client" )
516516 assert pool .connection_kwargs ["client_name" ] == "test-client"
517517
518+ def test_querystring_value_percent_decoded_once (self ):
519+ # A single percent-encoded sequence is decoded exactly once.
520+ pool = redis .ConnectionPool .from_url (
521+ "redis://location?client_name=worker%20name"
522+ )
523+ assert pool .connection_kwargs ["client_name" ] == "worker name"
524+
525+ def test_querystring_value_not_double_percent_decoded (self ):
526+ # A literal percent sign in a query value (encoded as %2520) must survive
527+ # as "%20" rather than being decoded a second time into a space. See #4208.
528+ pool = redis .ConnectionPool .from_url (
529+ "redis://location?client_name=worker%2520name"
530+ )
531+ assert pool .connection_kwargs ["client_name" ] == "worker%20name"
532+
518533 def test_invalid_extra_typed_querystring_options (self ):
519534 with pytest .raises (ValueError ):
520535 redis .ConnectionPool .from_url (
You can’t perform that action at this time.
0 commit comments