Skip to content

fix(proxy): percent-decode username and password in parse_proxy() - #1767

Open
okxint wants to merge 1 commit into
python-websockets:mainfrom
okxint:fix/parse-proxy-percent-decode-credentials
Open

okxint wants to merge 1 commit into
python-websockets:mainfrom
okxint:fix/parse-proxy-percent-decode-credentials

Conversation

@okxint

@okxint okxint commented Sep 17, 2026

Copy link
Copy Markdown

Fixes #1761.

Problem

parse_proxy() uses urllib.parse.urlparse() to extract the userinfo
components, but urlparse returns the raw, still-percent-encoded strings
for username and password. A proxy URL like

socks5://alice:p%40ss@127.0.0.1:1080

(where the password is p@ss, percent-encoded because @ is a reserved
character in the authority component) produced:

>>> parse_proxy("socks5://alice:p%40ss@127.0.0.1:1080").password
'p%40ss'   # ← should be 'p@ss'

The encoded string p%40ss was then:

  • base64-encoded verbatim by build_authorization_basic() for HTTP CONNECT
    proxies, producing a wrong Proxy-Authorization header (HTTP 407);
  • passed unchanged to python-socks for SOCKS proxies, which rejected the
    connection because its own credential comparison expected the decoded form
    (ProxyError: failed to connect to SOCKS proxy).

This is a regression relative to python-socks itself (_helpers.parse_proxy_url
calls unquote() on both fields) and requests (same).

Fix

Apply urllib.parse.unquote() to username and password immediately after
urlparse(), so Proxy always stores plain (decoded) credentials.

Also remove the urllib.parse.quote() re-encoding in the IRI branch: that
block was encoding non-ASCII credentials back into percent-encoded form and
storing them in Proxy, which was equally wrong because both
build_authorization_basic() and python-socks expect decoded strings.

Tests

  • Added two new entries to PROXIES_WITH_USER_INFO:
    • socks5://alice:p%40ss@proxy("alice", "p@ss")
    • socks5://alice%3Abob:secret%25word@proxy("alice:bob", "secret%word")
  • Updated the existing non-ASCII IRI case to expect decoded credentials
    (("üser", "påss") instead of ("%C3%BCser", "p%C3%A5ss")).

urllib.parse.urlparse() returns the raw (percent-encoded) userinfo
components rather than decoded strings.  parse_proxy() was passing
those raw values straight through to Proxy.username / Proxy.password,
so a credential such as p%40ss was sent to the proxy server instead of
the intended p@ss.

Fix by applying urllib.parse.unquote() to both components immediately
after urlparse().  This matches the behaviour of python-socks
(_helpers.parse_proxy_url) and requests, which both unquote proxy
userinfo before using it.

Also remove the urllib.parse.quote() call in the IRI branch: Proxy
should always store plain (decoded) credentials; encoding them back
was incorrect because build_authorization_basic() and python-socks
both expect decoded strings.

Tests: add two percent-encoded cases to PROXIES_WITH_USER_INFO and
update the existing non-ASCII IRI case to expect decoded credentials.

Fixes python-websockets#1761
@aaugustin

Copy link
Copy Markdown
Member

Hello, could you clarify how this PR accounts for my spec of what needs to be done to solve the issue.

It seems that you've only solved a small subset. If everything else is actually OK, can you provide the evidence?

Please just don't throw an AI at my comment; I have an AI, and I'd rather run it myself than through you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proxy credentials in the proxy URL are not percent-decoded

2 participants