Skip to content

Commit

Permalink
Merge b5ddb13 into b6f55bc
Browse files Browse the repository at this point in the history
  • Loading branch information
d-maurer committed Apr 19, 2022
2 parents b6f55bc + b5ddb13 commit dce4be7
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -11,6 +11,9 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst
5.5.2 (unreleased)
------------------

- Quote all components of a redirect URL (not only the path component)
(`#1027 <https://github.com/zopefoundation/Zope/issues/1027>`_)

- Drop the convenience script generation from the buildout configuration
in order to get rid of a lot of dependency version pins.
These were only needed for maintainers who can install them manually.
Expand Down
29 changes: 19 additions & 10 deletions src/ZPublisher/HTTPResponse.py
Expand Up @@ -233,17 +233,26 @@ def redirect(self, location, status=302, lock=0):
location = location.decode(self.charset)

# To be entirely correct, we must make sure that all non-ASCII
# characters in the path part are quoted correctly. This is required
# as we now allow non-ASCII IDs
# characters are quoted correctly.
parsed = list(urlparse(location))

# Make a hacky guess whether the path component is already
# URL-encoded by checking for %. If it is, we don't touch it.
if '%' not in parsed[2]:
# The list of "safe" characters is from RFC 2396 section 2.3
# (unreserved characters that should not be escaped) plus
# section 3.3 (reserved characters in path components)
parsed[2] = quote(parsed[2], safe="/@!*'~();,=+$")
rfc2396_unreserved = "-_.!~*'()" # RFC 2396 section 2.3
for idx, idx_safe in (
# authority
(1, ";:@?/&=+$,"), # RFC 2396 section 3.2, 3.2.1, 3.2.3
# path
(2, "/;:@&=+$,"), # RFC 2396 section 3.3
# params - actually part of path; empty in Python 3
(3, "/;:@&=+$,"), # RFC 2396 section 3.3
# query
(4, ";/?:@&=+,$"), # RFC 2396 section 3.4
# fragment
(5, ";/?:@&=+$,"), # RFC 2396 section 4
):
# Make a hacky guess whether the component is already
# URL-encoded by checking for %. If it is, we don't touch it.
if '%' not in parsed[idx]:
parsed[idx] = quote(parsed[idx],
safe=rfc2396_unreserved + idx_safe)
location = urlunparse(parsed)

self.setStatus(status, lock=lock)
Expand Down
5 changes: 5 additions & 0 deletions src/ZPublisher/tests/testHTTPResponse.py
Expand Up @@ -800,6 +800,11 @@ def test_redirect_nonascii(self):
exc = HTTPMovedPermanently(BYTES_URL)
self._redirectURLCheck(exc, expected=ENC_URL)

def test_redirect_nonascii_everywhere(self):
URL = u"http://uä:pä@sä:80/pä?qä#fä"
ENC_URL = "http://u%C3%A4:p%C3%A4@s%C3%A4:80/p%C3%A4?q%C3%A4#f%C3%A4"
self._redirectURLCheck(URL, ENC_URL)

def test_redirect_alreadyquoted(self):
# If a URL is already quoted, don't double up on the quoting
ENC_URL = 'http://example.com/M%C3%A4H'
Expand Down

0 comments on commit dce4be7

Please sign in to comment.