diff --git a/httpie/cli/argparser.py b/httpie/cli/argparser.py index 9bf09b3b73..ddbb865842 100644 --- a/httpie/cli/argparser.py +++ b/httpie/cli/argparser.py @@ -5,7 +5,7 @@ import sys from argparse import RawDescriptionHelpFormatter from textwrap import dedent -from urllib.parse import urlsplit +from urllib.parse import unquote, urlsplit from requests.utils import get_netrc_auth @@ -289,8 +289,8 @@ def _process_auth(self): if self.args.auth is None and not auth_type_set: if url.username is not None: # Handle http://username:password@hostname/ - username = url.username - password = url.password or '' + username = unquote(url.username) + password = unquote(url.password or '') self.args.auth = AuthCredentials( key=username, value=password, diff --git a/httpie/manager/tasks/plugins.py b/httpie/manager/tasks/plugins.py index c3c7d540c7..a149651353 100644 --- a/httpie/manager/tasks/plugins.py +++ b/httpie/manager/tasks/plugins.py @@ -88,7 +88,7 @@ def _install(self, targets: List[str], mode='install') -> Tuple[ if self.debug: self.env.stderr.write('Command failed: ') self.env.stderr.write('pip ' + ' '.join(pip_args) + '\n') - self.env.stderr.write(textwrap.indent(' ', stderr)) + self.env.stderr.write(textwrap.indent(stderr, ' ')) last_line = stderr.strip().splitlines()[-1] severity, _, message = last_line.partition(': ') diff --git a/setup.cfg b/setup.cfg index 85490981aa..d1fa836784 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,7 +48,7 @@ project_urls = packages = find: install_requires = pip - charset_normalizer>=2.0.0 + charset_normalizer>=2.0.0,<3.4.2 defusedxml>=0.6.0 requests[socks] >=2.22.0 Pygments>=2.5.2 @@ -90,7 +90,7 @@ dev = flake8-deprecated flake8-mutable flake8-tuple - pyopenssl + pyopenssl<26 pytest-cov pyyaml twine diff --git a/tests/test_auth.py b/tests/test_auth.py index 83423efec0..3ba634d607 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -55,6 +55,16 @@ def test_credentials_in_url(httpbin_both): assert r.json == {'authenticated': True, 'user': 'user'} +def test_credentials_in_url_are_percent_decoded(): + args = httpie.cli.definition.parser.parse_args( + args=['https://u%40d:1%3D2%3F@example.org'], + env=MockEnvironment(), + ) + + assert args.auth.username == 'u@d' + assert args.auth.password == '1=2?' + + def test_credentials_in_url_auth_flag_has_priority(httpbin_both): """When credentials are passed in URL and via -a at the same time, then the ones from -a are used.""" diff --git a/tests/test_cli_ui.py b/tests/test_cli_ui.py index bb744cdc4e..ac9da8a30d 100644 --- a/tests/test_cli_ui.py +++ b/tests/test_cli_ui.py @@ -1,6 +1,9 @@ -import pytest -import shutil import os +import shutil +import sys + +import pytest + from tests.utils import http NAKED_BASE_TEMPLATE = """\ @@ -25,9 +28,18 @@ error_msg="argument --pretty: expected one argument" ) +PRETTY_INVALID_CHOICES = ( + "all, colors, format, none" + if sys.version_info >= (3, 12) + else "'all', 'colors', 'format', 'none'" +) + NAKED_HELP_MESSAGE_PRETTY_WITH_INVALID_ARG = NAKED_BASE_TEMPLATE.format( extra_args="--pretty {all, colors, format, none} ", - error_msg="argument --pretty: invalid choice: '$invalid' (choose from 'all', 'colors', 'format', 'none')" + error_msg=( + "argument --pretty: invalid choice: '$invalid' " + f"(choose from {PRETTY_INVALID_CHOICES})" + ) ) diff --git a/tests/utils/plugins_cli.py b/tests/utils/plugins_cli.py index 95b6157955..ba3ed332ca 100644 --- a/tests/utils/plugins_cli.py +++ b/tests/utils/plugins_cli.py @@ -1,3 +1,4 @@ +import os import secrets import site import sys @@ -217,8 +218,14 @@ def runner(*args, cli_mode: bool = True): # Prevent installed plugins from showing up. original_plugins = plugin_manager.copy() clean_sys_path = set(sys.path).difference(site.getsitepackages()) + # These dummy plugins do not need isolated build dependencies, and the + # suite's pytest-httpbin CA bundle is only for test servers. If pip + # inherits it, plugin installs cannot verify PyPI's certificate. with patch('sys.path', list(clean_sys_path)): - response = httpie(*args, env=interface.environment) + with patch.dict(os.environ, {'PIP_NO_BUILD_ISOLATION': '1'}, clear=False): + os.environ.pop('REQUESTS_CA_BUNDLE', None) + os.environ.pop('CURL_CA_BUNDLE', None) + response = httpie(*args, env=interface.environment) plugin_manager.clear() plugin_manager.extend(original_plugins) return response @@ -229,7 +236,7 @@ def runner(*args, cli_mode: bool = True): @pytest.fixture def httpie_plugins_success(httpie_plugins): def runner(*args, cli_mode: bool = True): - response = httpie_plugins(*args, cli_mode=True) - assert response.exit_status == ExitStatus.SUCCESS + response = httpie_plugins(*args, cli_mode=cli_mode) + assert response.exit_status == ExitStatus.SUCCESS, response.stderr return response.splitlines() return runner