diff --git a/news/3236.bugfix b/news/3236.bugfix
new file mode 100644
index 000000000..51d358458
--- /dev/null
+++ b/news/3236.bugfix
@@ -0,0 +1,1 @@
+"Support URL-encoded characters in URL credentials."
diff --git a/pip/download.py b/pip/download.py
index 41709db5e..894d0ff9a 100644
--- a/pip/download.py
+++ b/pip/download.py
@@ -21,6 +21,7 @@ except ImportError:
 
 from pip._vendor.six.moves.urllib import parse as urllib_parse
 from pip._vendor.six.moves.urllib import request as urllib_request
+from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
 
 import pip
 
@@ -207,8 +208,9 @@ class MultiDomainBasicAuth(AuthBase):
         if "@" in netloc:
             userinfo = netloc.rsplit("@", 1)[0]
             if ":" in userinfo:
-                return userinfo.split(":", 1)
-            return userinfo, None
+                user, pwd = userinfo.split(":", 1)
+                return (urllib_unquote(user), urllib_unquote(pwd))
+            return urllib_unquote(userinfo), None
         return None, None
 
 

Failure to authenticate private repository when URL-encoded character in password
See https://github.com/pypa/pip/issues/51#issuecomment-128548720 

I have this issue with pip 7.1.2. There's a symbol in the password that needs url-encoding.

With the password url-encoded, pip fails to authenticate (gets a 401 response) and prompts for username/password interactively. It then works fine if I enter the user/pass interactively. Like @bcbrown I suspect it's not url-decoding the password.

diff --git a/tests/unit/test_download.py b/tests/unit/test_download.py
index ee4b11c7d..1630393f0 100644
--- a/tests/unit/test_download.py
+++ b/tests/unit/test_download.py
@@ -13,7 +13,7 @@ import pip
 from pip.exceptions import HashMismatch
 from pip.download import (
     PipSession, SafeFileCache, path_to_url, unpack_http_url, url_to_path,
-    unpack_file_url,
+    unpack_file_url, MultiDomainBasicAuth
 )
 from pip.index import Link
 from pip.utils.hashes import Hashes
@@ -333,3 +333,14 @@ class TestPipSession:
         )
 
         assert not hasattr(session.adapters["https://example.com/"], "cache")
+
+
+def test_parse_credentials():
+    auth = MultiDomainBasicAuth()
+    assert auth.parse_credentials("foo:bar@example.com") == ('foo', 'bar')
+    assert auth.parse_credentials("foo@example.com") == ('foo', None)
+    assert auth.parse_credentials("example.com") == (None, None)
+
+    # URL-encoded reserved characters:
+    assert auth.parse_credentials("user%3Aname:%23%40%5E@example.com") \
+        == ("user:name", "#@^")

pip install -e .
parse_log_pytest
pytest
pytest pytest-cov pytest-rerunfailures pytest-timeout pytest-xdist freezegun mock pretend pyyaml setuptools>=39.2.0 scripttest virtualenv wheel
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
coverage==7.8.0
distlib==0.3.9
exceptiongroup==1.2.0
execnet==2.1.1
filelock==3.18.0
freezegun==1.5.1
iniconfig==1.1.1
mock==5.2.0
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
pretend==1.0.9
pytest==8.3.4
pytest-cov==6.0.0
pytest-rerunfailures==15.0
pytest-timeout==2.3.1
pytest-xdist==3.6.1
python-dateutil==2.9.0.post0
PyYAML==6.0.2
scripttest==2.0
six==1.17.0
tomli==2.0.1
virtualenv==20.29.3

name: pip
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.8.0
      - distlib==0.3.9
      - execnet==2.1.1
      - filelock==3.18.0
      - freezegun==1.5.1
      - mock==5.2.0
      - pip==10.0.0.dev0
      - platformdirs==4.3.7
      - pretend==1.0.9
      - pytest-cov==6.0.0
      - pytest-rerunfailures==15.0
      - pytest-timeout==2.3.1
      - pytest-xdist==3.6.1
      - python-dateutil==2.9.0.post0
      - pyyaml==6.0.2
      - scripttest==2.0
      - six==1.17.0
      - virtualenv==20.29.3
prefix: /opt/conda/envs/pip

tests/unit/test_download.py::test_parse_credentials
tests/unit/test_download.py::test_unpack_http_url_with_urllib_response_without_content_type tests/unit/test_download.py::test_user_agent tests/unit/test_download.py::test_unpack_http_url_bad_downloaded_checksum tests/unit/test_download.py::test_path_to_url_unix tests/unit/test_download.py::test_url_to_path_unix tests/unit/test_download.py::Test_unpack_file_url::test_unpack_file_url_no_download tests/unit/test_download.py::Test_unpack_file_url::test_unpack_file_url_and_download tests/unit/test_download.py::Test_unpack_file_url::test_unpack_file_url_download_already_exists tests/unit/test_download.py::Test_unpack_file_url::test_unpack_file_url_bad_hash tests/unit/test_download.py::Test_unpack_file_url::test_unpack_file_url_download_bad_hash tests/unit/test_download.py::Test_unpack_file_url::test_unpack_file_url_thats_a_dir tests/unit/test_download.py::TestSafeFileCache::test_cache_roundtrip tests/unit/test_download.py::TestSafeFileCache::test_safe_get_no_perms tests/unit/test_download.py::TestSafeFileCache::test_safe_set_no_perms tests/unit/test_download.py::TestSafeFileCache::test_safe_delete_no_perms tests/unit/test_download.py::TestPipSession::test_cache_defaults_off tests/unit/test_download.py::TestPipSession::test_cache_is_enabled tests/unit/test_download.py::TestPipSession::test_http_cache_is_not_enabled tests/unit/test_download.py::TestPipSession::test_insecure_host_cache_is_not_enabled
news/3236.bugfix pip/download.py
news/3236.bugfix pip/download.py