mirror of
https://github.com/httpie/cli.git
synced 2025-03-13 06:18:33 +01:00
improve error messages upon invalid args/values in new flags
This commit is contained in:
parent
da6cc13b8b
commit
e375c259e8
@ -9,6 +9,7 @@ from random import randint
|
||||
from time import monotonic
|
||||
from typing import Any, Dict, Callable, Iterable
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
import ipaddress
|
||||
|
||||
import niquests
|
||||
|
||||
@ -71,12 +72,38 @@ def collect_messages(
|
||||
source_address = None
|
||||
|
||||
if args.interface:
|
||||
# automatically raises ValueError upon invalid IP
|
||||
ipaddress.ip_address(args.interface)
|
||||
|
||||
source_address = (args.interface, 0)
|
||||
if args.local_port:
|
||||
|
||||
if '-' not in args.local_port:
|
||||
source_address = (args.interface or "0.0.0.0", int(args.local_port))
|
||||
try:
|
||||
parsed_port = int(args.local_port)
|
||||
except ValueError:
|
||||
raise ValueError(f'"{args.local_port}" is not a valid port number.')
|
||||
|
||||
source_address = (args.interface or "0.0.0.0", parsed_port)
|
||||
else:
|
||||
min_port, max_port = args.local_port.split('-', 1)
|
||||
if args.local_port.count('-') != 1:
|
||||
raise ValueError(f'"{args.local_port}" is not a valid port range. i.e. we accept value like "25441-65540".')
|
||||
|
||||
try:
|
||||
min_port, max_port = args.local_port.split('-', 1)
|
||||
except ValueError:
|
||||
raise ValueError(f'The port range you gave in input "{args.local_port}" is not a valid range.')
|
||||
|
||||
if min_port == "":
|
||||
raise ValueError("Negative port number are all invalid values.")
|
||||
if max_port == "":
|
||||
raise ValueError('Port range requires both start and end ports to be specified. e.g. "25441-65540".')
|
||||
|
||||
try:
|
||||
min_port, max_port = int(min_port), int(max_port)
|
||||
except ValueError:
|
||||
raise ValueError(f'Either "{min_port}" or/and "{max_port}" is an invalid port number.')
|
||||
|
||||
source_address = (args.interface or "0.0.0.0", randint(int(min_port), int(max_port)))
|
||||
|
||||
parsed_url = parse_url(args.url)
|
||||
@ -91,6 +118,20 @@ def collect_messages(
|
||||
else:
|
||||
resolver = [ensure_resolver, "system://"]
|
||||
|
||||
force_opt_count = [args.force_http1, args.force_http2, args.force_http3].count(True)
|
||||
disable_opt_count = [args.disable_http1, args.disable_http2, args.disable_http3].count(True)
|
||||
|
||||
if force_opt_count > 1:
|
||||
raise ValueError(
|
||||
'You may only force one of --http1, --http2 or --http3. Use --disable-http1, '
|
||||
'--disable-http2 or --disable-http3 instead if you prefer the excluding logic.'
|
||||
)
|
||||
elif force_opt_count == 1 and disable_opt_count:
|
||||
raise ValueError(
|
||||
'You cannot both force a http protocol version and disable some other. e.g. '
|
||||
'--http2 already force HTTP/2, do not use --disable-http1 at the same time.'
|
||||
)
|
||||
|
||||
if args.force_http1:
|
||||
args.disable_http1 = False
|
||||
args.disable_http2 = True
|
||||
@ -245,11 +286,22 @@ 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:
|
||||
# assume it is the in-memory resolver
|
||||
if "://" not in r:
|
||||
if ":" not in r or r.count(':') != 1:
|
||||
raise ValueError("The manual resolver for a specific host requires to be formatted like 'hostname:ip'. e.g. 'pie.dev:1.1.1.1'.")
|
||||
hostname, override_ip = r.split(':')
|
||||
if hostname.strip() == "" or override_ip.strip() == "":
|
||||
raise ValueError("The manual resolver for a specific host requires to be formatted like 'hostname:ip'. e.g. 'pie.dev:1.1.1.1'.")
|
||||
ipaddress.ip_address(override_ip)
|
||||
r = f"in-memory://default/?hosts={r}"
|
||||
resolver_rebuilt.append(r)
|
||||
resolver = resolver_rebuilt
|
||||
|
@ -46,6 +46,50 @@ def test_force_http3(remote_httpbin_secure):
|
||||
assert HTTP_OK in r
|
||||
|
||||
|
||||
def test_force_multiple_error(remote_httpbin_secure):
|
||||
r = http(
|
||||
"--verify=no",
|
||||
'--http3',
|
||||
'--http2',
|
||||
remote_httpbin_secure + '/get',
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert 'You may only force one of --http1, --http2 or --http3.' in r.stderr
|
||||
|
||||
|
||||
def test_disable_all_error_https(remote_httpbin_secure):
|
||||
r = http(
|
||||
"--verify=no",
|
||||
'--disable-http1',
|
||||
'--disable-http2',
|
||||
'--disable-http3',
|
||||
remote_httpbin_secure + '/get',
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert 'You disabled every supported protocols.' in r.stderr
|
||||
|
||||
|
||||
def test_disable_all_error_http(remote_httpbin):
|
||||
r = http(
|
||||
"--verify=no",
|
||||
'--disable-http1',
|
||||
'--disable-http2',
|
||||
remote_httpbin + '/get',
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
try:
|
||||
import qh3 # noqa: F401
|
||||
except ImportError:
|
||||
# that branch means that the user does not have HTTP/3
|
||||
# so, the message says that he disabled everything.
|
||||
assert 'You disabled every supported protocols.' in r.stderr
|
||||
else:
|
||||
assert 'No compatible protocol are enabled to emit request. You currently are connected using TCP Unencrypted and must have HTTP/1.1 or/and HTTP/2 enabled to pursue.' in r.stderr
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_quic_cache_persistent(tmp_path):
|
||||
env = PersistentMockEnvironment()
|
||||
|
@ -38,6 +38,90 @@ def test_ensure_interface_and_port_parameters(httpbin):
|
||||
assert HTTP_OK in r
|
||||
|
||||
|
||||
def test_invalid_interface_given(httpbin):
|
||||
r = http(
|
||||
"--interface=10.25.a.u", # invalid IP
|
||||
httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert "'10.25.a.u' does not appear to be an IPv4 or IPv6 address" in r.stderr
|
||||
|
||||
r = http(
|
||||
"--interface=abc", # invalid IP
|
||||
httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert "'abc' does not appear to be an IPv4 or IPv6 address" in r.stderr
|
||||
|
||||
|
||||
def test_invalid_local_port_given(httpbin):
|
||||
r = http(
|
||||
"--local-port=127.0.0.1", # invalid port
|
||||
httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert '"127.0.0.1" is not a valid port number.' in r.stderr
|
||||
|
||||
r = http(
|
||||
"--local-port=a8", # invalid port
|
||||
httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert '"a8" is not a valid port number.' in r.stderr
|
||||
|
||||
r = http(
|
||||
"--local-port=-8", # invalid port
|
||||
httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert 'Negative port number are all invalid values.' in r.stderr
|
||||
|
||||
r = http(
|
||||
"--local-port=a-8", # invalid port range
|
||||
httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert 'Either "a" or/and "8" is an invalid port number.' in r.stderr
|
||||
|
||||
r = http(
|
||||
"--local-port=5555-", # invalid port range
|
||||
httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert 'Port range requires both start and end ports to be specified.' in r.stderr
|
||||
|
||||
|
||||
def test_force_ipv6_on_unsupported_system(remote_httpbin):
|
||||
from httpie.compat import urllib3
|
||||
urllib3.util.connection.HAS_IPV6 = False
|
||||
r = http(
|
||||
"-6", # invalid port
|
||||
remote_httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
urllib3.util.connection.HAS_IPV6 = True
|
||||
|
||||
assert 'Unable to force IPv6 because your system lack IPv6 support.' in r.stderr
|
||||
|
||||
|
||||
def test_force_both_ipv6_and_ipv4(remote_httpbin):
|
||||
r = http(
|
||||
"-6", # force IPv6
|
||||
"-4", # force IPv4
|
||||
remote_httpbin + "/get",
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
|
||||
assert 'Unable to force both IPv4 and IPv6, omit the flags to allow both.' in r.stderr
|
||||
|
||||
|
||||
def test_happy_eyeballs(remote_httpbin_secure):
|
||||
r = http(
|
||||
"--heb", # this will automatically and concurrently try IPv6 and IPv4 endpoints
|
||||
|
@ -32,3 +32,37 @@ def test_ensure_override_resolver_used(remote_httpbin):
|
||||
)
|
||||
|
||||
assert "Request timed out" in r.stderr or "A socket operation was attempted to an unreachable network" in r.stderr
|
||||
|
||||
|
||||
def test_invalid_override_resolver():
|
||||
r = http(
|
||||
"--resolver=pie.dev:abc", # we do this nonsense on purpose
|
||||
"pie.dev/get",
|
||||
tolerate_error_exit_status=True
|
||||
)
|
||||
|
||||
assert "'abc' does not appear to be an IPv4 or IPv6 address" in r.stderr
|
||||
|
||||
r = http(
|
||||
"--resolver=abc", # we do this nonsense on purpose
|
||||
"pie.dev/get",
|
||||
tolerate_error_exit_status=True
|
||||
)
|
||||
|
||||
assert "The manual resolver for a specific host requires to be formatted like" in r.stderr
|
||||
|
||||
r = http(
|
||||
"--resolver=pie.dev:127.0.0", # we do this nonsense on purpose
|
||||
"pie.dev/get",
|
||||
tolerate_error_exit_status=True
|
||||
)
|
||||
|
||||
assert "'127.0.0' does not appear to be an IPv4 or IPv6 address" in r.stderr
|
||||
|
||||
r = http(
|
||||
"--resolver=doz://example.com", # we do this nonsense on purpose
|
||||
"pie.dev/get",
|
||||
tolerate_error_exit_status=True
|
||||
)
|
||||
|
||||
assert "'doz' is not a valid ProtocolResolver" in r.stderr
|
||||
|
Loading…
Reference in New Issue
Block a user