diff --git a/httpie/cli/argparser.py b/httpie/cli/argparser.py index bf981900fd..c8ab9ed5e6 100644 --- a/httpie/cli/argparser.py +++ b/httpie/cli/argparser.py @@ -25,6 +25,7 @@ ) from .exceptions import ParseError from .requestitems import RequestItems +from ..compat import has_ipv6_support from ..context import Environment from ..plugins.registry import plugin_manager from ..utils import ExplicitNullAuth, get_content_type @@ -174,6 +175,7 @@ def parse_args( self._process_output_options() self._process_pretty_options() self._process_format_options() + self._process_ip_version_options() # bellow is a fix for detecting "false-or empty" stdin. # see https://github.com/httpie/cli/issues/1551 for more information. @@ -576,6 +578,15 @@ def _process_format_options(self): parsed_options = parse_format_options(options_group, defaults=parsed_options) self.args.format_options = parsed_options + def _process_ip_version_options(self): + if not has_ipv6_support() and self.args.force_ipv6: + self.error('Unable to force IPv6 because your system lack IPv6 support.') + if self.args.force_ipv4 and self.args.force_ipv6: + self.error( + 'Unable to force both IPv4 and IPv6, omit the flags to allow both. ' + 'The flags "-6" and "-4" are meant to force one of them.' + ) + def print_manual(self): from httpie.output.ui import man_pages diff --git a/httpie/cli/definition.py b/httpie/cli/definition.py index e6786ea657..a58658f936 100644 --- a/httpie/cli/definition.py +++ b/httpie/cli/definition.py @@ -749,6 +749,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False): network.add_argument( '--ipv6', '-6', + dest='force_ipv6', default=False, action='store_true', short_help='Force using a IPv6 address to reach the remote peer.' @@ -756,6 +757,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False): network.add_argument( '--ipv4', '-4', + dest='force_ipv4', default=False, action='store_true', short_help='Force using a IPv4 address to reach the remote peer.' diff --git a/httpie/client.py b/httpie/client.py index 7ca2097878..61801d37cc 100644 --- a/httpie/client.py +++ b/httpie/client.py @@ -116,8 +116,8 @@ def collect_messages( disable_http2=args.disable_http2, disable_http3=args.disable_http3, resolver=resolver, - disable_ipv6=args.ipv4, - disable_ipv4=args.ipv6, + disable_ipv6=args.force_ipv4, + disable_ipv4=args.force_ipv6, source_address=(args.interface, args.local_port), quic_cache=env.config.quic_file, happy_eyeballs=args.happy_eyeballs, @@ -156,12 +156,15 @@ def collect_messages( hooks = None - # The hook set up bellow is crucial for HTTPie. - # It will help us yield the request before it is - # actually sent. This will permit us to know about - # the connection information for example. if request_or_response_callback: - hooks = {"pre_send": [request_or_response_callback], "early_response": [request_or_response_callback]} + # The hook set up bellow is crucial for HTTPie. + # It will help us yield the request before it is + # actually sent. This will permit us to know about + # the connection information for example. + hooks = { + 'pre_send': [request_or_response_callback], + 'early_response': [request_or_response_callback], + } request = niquests.Request(**request_kwargs, hooks=hooks) prepared_request = requests_session.prepare_request(request) @@ -247,11 +250,6 @@ def build_requests_session( if quic_cache is not None: requests_session.quic_cache_layer = QuicCapabilityCache(quic_cache) - if urllib3.util.connection.HAS_IPV6 is False and disable_ipv4 is True: - raise ValueError('Unable to force IPv6 because your system lack IPv6 support.') - if disable_ipv4 and disable_ipv6: - raise ValueError('Unable to force both IPv4 and IPv6, omit the flags to allow both. The flags "-6" and "-4" are meant to force one of them.') - if resolver: resolver_rebuilt = [] for r in resolver: diff --git a/httpie/compat.py b/httpie/compat.py index 5724c54d6e..2ba557972a 100644 --- a/httpie/compat.py +++ b/httpie/compat.py @@ -31,6 +31,14 @@ resolve_ssl_version, ) + +def has_ipv6_support(new_value: Optional[bool] = None) -> bool: + if new_value is not None: + # Allow overriding the default value for testing purposes. + urllib3.util.connection.HAS_IPV6 = new_value + return urllib3.util.connection.HAS_IPV6 + + def enforce_niquests(): """ Force imported 3rd-party plugins to use `niquests` instead of `requests` if they haven’t migrated yet. diff --git a/tests/test_network.py b/tests/test_network.py index db4b6ea51a..0e53d6b7bb 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -9,6 +9,7 @@ local_port_arg_type, parse_local_port_arg, ) +from httpie.compat import has_ipv6_support from .utils import HTTP_OK, http @@ -98,17 +99,16 @@ def test_invalid_interface_arg(httpbin, interface_arg): def test_force_ipv6_on_unsupported_system(remote_httpbin): - from httpie.compat import urllib3 - orig_has_ipv6 = urllib3.util.connection.HAS_IPV6 - urllib3.util.connection.HAS_IPV6 = False + orig = has_ipv6_support() + has_ipv6_support(False) try: r = http( - "-6", # invalid port - remote_httpbin + "/get", + '-6', + remote_httpbin + '/get', tolerate_error_exit_status=True, ) finally: - urllib3.util.connection.HAS_IPV6 = orig_has_ipv6 + has_ipv6_support(orig) assert 'Unable to force IPv6 because your system lack IPv6 support.' in r.stderr