From 05f8c76bded4c7285d122881263248537787edaa Mon Sep 17 00:00:00 2001 From: Marek Aufart Date: Tue, 4 Nov 2025 14:43:20 +0100 Subject: [PATCH] Pass proxy from environment to jvm args It was reported that proxy setup from environment variables is not correctly recognized by jdtls. The jdtls startup python script now transforms those settings to JVM parameters. Fixes: https://github.com/konveyor/java-analyzer-bundle/issues/172 Signed-off-by: Marek Aufart --- jdtls-bin-override/jdtls.py | 50 +++++++++ jdtls-bin-override/test_jdtls.py | 180 +++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 jdtls-bin-override/test_jdtls.py diff --git a/jdtls-bin-override/jdtls.py b/jdtls-bin-override/jdtls.py index cd4c5a0..d7cc1fa 100755 --- a/jdtls-bin-override/jdtls.py +++ b/jdtls-bin-override/jdtls.py @@ -18,6 +18,7 @@ import subprocess from pathlib import Path import tempfile +from urllib.parse import urlparse def get_java_executable(known_args): if known_args.java_executable is not None: @@ -69,6 +70,54 @@ def get_shared_config_path(jdtls_base_path): return jdtls_base_path / config_dir +def get_proxy_jvm_args(): + jvm_args = [] + + # HTTP Proxy + http_proxy = os.environ.get('HTTP_PROXY') or os.environ.get('http_proxy') + if http_proxy: + # Add default scheme if missing + if '://' not in http_proxy: + http_proxy = 'http://' + http_proxy + parsed = urlparse(http_proxy) + if parsed.hostname: + jvm_args.append(f'-Dhttp.proxyHost={parsed.hostname}') + if parsed.port: + jvm_args.append(f'-Dhttp.proxyPort={parsed.port}') + elif parsed.scheme == 'http': + jvm_args.append('-Dhttp.proxyPort=80') + if parsed.username: + jvm_args.append(f'-Dhttp.proxyUser={parsed.username}') + if parsed.password: + jvm_args.append(f'-Dhttp.proxyPassword={parsed.password}') + + # HTTPS Proxy + https_proxy = os.environ.get('HTTPS_PROXY') or os.environ.get('https_proxy') + if https_proxy: + # Add default scheme if missing + if '://' not in https_proxy: + https_proxy = 'https://' + https_proxy + parsed = urlparse(https_proxy) + if parsed.hostname: + jvm_args.append(f'-Dhttps.proxyHost={parsed.hostname}') + if parsed.port: + jvm_args.append(f'-Dhttps.proxyPort={parsed.port}') + elif parsed.scheme == 'https': + jvm_args.append('-Dhttps.proxyPort=443') + if parsed.username: + jvm_args.append(f'-Dhttps.proxyUser={parsed.username}') + if parsed.password: + jvm_args.append(f'-Dhttps.proxyPassword={parsed.password}') + + # NO_PROXY / Non-proxy hosts + no_proxy = os.environ.get('NO_PROXY') or os.environ.get('no_proxy') + if no_proxy: + # Convert comma-separated list to pipe-separated for Java + no_proxy_hosts = no_proxy.replace(',', '|') + jvm_args.append(f'-Dhttp.nonProxyHosts={no_proxy_hosts}') + + return jvm_args + def main(args): cwd_name = os.path.basename(os.getcwd()) jdtls_data_path = os.path.join(tempfile.gettempdir(), "jdtls-" + sha1(cwd_name.encode()).hexdigest()) @@ -103,6 +152,7 @@ def main(args): "--add-modules=ALL-SYSTEM", "--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.lang=ALL-UNNAMED"] \ + + get_proxy_jvm_args() \ + known_args.jvm_arg \ + ["-jar", jar_path, "-data", known_args.data] \ diff --git a/jdtls-bin-override/test_jdtls.py b/jdtls-bin-override/test_jdtls.py new file mode 100644 index 0000000..dd4fa21 --- /dev/null +++ b/jdtls-bin-override/test_jdtls.py @@ -0,0 +1,180 @@ +import os +import pytest +from unittest.mock import patch +from jdtls import get_proxy_jvm_args + + +class TestGetProxyJvmArgs: + """Test suite for get_proxy_jvm_args function.""" + + def test_no_proxy_variables(self): + """Test with no proxy environment variables set.""" + with patch.dict(os.environ, {}, clear=True): + result = get_proxy_jvm_args() + assert result == [] + + def test_http_proxy_with_hostname_only(self): + """Test HTTP_PROXY with just hostname.""" + with patch.dict(os.environ, {'HTTP_PROXY': 'http://proxy.example.com'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.proxyHost=proxy.example.com' in result + assert '-Dhttp.proxyPort=80' in result + + def test_http_proxy_with_port(self): + """Test HTTP_PROXY with hostname and port.""" + with patch.dict(os.environ, {'HTTP_PROXY': 'http://proxy.example.com:8080'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.proxyHost=proxy.example.com' in result + assert '-Dhttp.proxyPort=8080' in result + + def test_http_proxy_lowercase(self): + """Test http_proxy (lowercase) environment variable.""" + with patch.dict(os.environ, {'http_proxy': 'http://proxy.example.com:3128'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.proxyHost=proxy.example.com' in result + assert '-Dhttp.proxyPort=3128' in result + + def test_http_proxy_with_credentials(self): + """Test HTTP_PROXY with username and password.""" + with patch.dict(os.environ, {'HTTP_PROXY': 'http://user:pass@proxy.example.com:8080'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.proxyHost=proxy.example.com' in result + assert '-Dhttp.proxyPort=8080' in result + assert '-Dhttp.proxyUser=user' in result + assert '-Dhttp.proxyPassword=pass' in result + + def test_https_proxy_with_hostname_only(self): + """Test HTTPS_PROXY with just hostname.""" + with patch.dict(os.environ, {'HTTPS_PROXY': 'https://secure-proxy.example.com'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttps.proxyHost=secure-proxy.example.com' in result + assert '-Dhttps.proxyPort=443' in result + + def test_https_proxy_with_port(self): + """Test HTTPS_PROXY with hostname and port.""" + with patch.dict(os.environ, {'HTTPS_PROXY': 'https://secure-proxy.example.com:8443'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttps.proxyHost=secure-proxy.example.com' in result + assert '-Dhttps.proxyPort=8443' in result + + def test_https_proxy_lowercase(self): + """Test https_proxy (lowercase) environment variable.""" + with patch.dict(os.environ, {'https_proxy': 'https://secure-proxy.example.com:8443'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttps.proxyHost=secure-proxy.example.com' in result + assert '-Dhttps.proxyPort=8443' in result + + def test_https_proxy_with_credentials(self): + """Test HTTPS_PROXY with username and password.""" + with patch.dict(os.environ, {'HTTPS_PROXY': 'https://user:pass@secure-proxy.example.com:8443'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttps.proxyHost=secure-proxy.example.com' in result + assert '-Dhttps.proxyPort=8443' in result + assert '-Dhttps.proxyUser=user' in result + assert '-Dhttps.proxyPassword=pass' in result + + def test_no_proxy_single_host(self): + """Test NO_PROXY with a single host.""" + with patch.dict(os.environ, {'NO_PROXY': 'localhost'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.nonProxyHosts=localhost' in result + + def test_no_proxy_multiple_hosts(self): + """Test NO_PROXY with comma-separated hosts.""" + with patch.dict(os.environ, {'NO_PROXY': 'localhost,127.0.0.1,.example.com'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.nonProxyHosts=localhost|127.0.0.1|.example.com' in result + + def test_no_proxy_lowercase(self): + """Test no_proxy (lowercase) environment variable.""" + with patch.dict(os.environ, {'no_proxy': 'localhost,127.0.0.1'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.nonProxyHosts=localhost|127.0.0.1' in result + + def test_all_proxies_combined(self): + """Test with all proxy environment variables set.""" + env_vars = { + 'HTTP_PROXY': 'http://user:pass@proxy.example.com:8080', + 'HTTPS_PROXY': 'https://user:pass@secure-proxy.example.com:8443', + 'NO_PROXY': 'localhost,127.0.0.1,.local' + } + with patch.dict(os.environ, env_vars, clear=True): + result = get_proxy_jvm_args() + + # HTTP proxy assertions + assert '-Dhttp.proxyHost=proxy.example.com' in result + assert '-Dhttp.proxyPort=8080' in result + assert '-Dhttp.proxyUser=user' in result + assert '-Dhttp.proxyPassword=pass' in result + + # HTTPS proxy assertions + assert '-Dhttps.proxyHost=secure-proxy.example.com' in result + assert '-Dhttps.proxyPort=8443' in result + assert '-Dhttps.proxyUser=user' in result + assert '-Dhttps.proxyPassword=pass' in result + + # NO_PROXY assertions + assert '-Dhttp.nonProxyHosts=localhost|127.0.0.1|.local' in result + + def test_uppercase_takes_precedence(self): + """Test that uppercase environment variables take precedence over lowercase.""" + env_vars = { + 'HTTP_PROXY': 'http://upper.example.com:8080', + 'http_proxy': 'http://lower.example.com:3128' + } + with patch.dict(os.environ, env_vars, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.proxyHost=upper.example.com' in result + assert '-Dhttp.proxyPort=8080' in result + + def test_proxy_without_scheme(self): + """Test proxy URL without scheme (should add default scheme).""" + with patch.dict(os.environ, {'HTTP_PROXY': 'proxy.example.com:8080'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.proxyHost=proxy.example.com' in result + assert '-Dhttp.proxyPort=8080' in result + + def test_https_proxy_without_scheme(self): + """Test HTTPS proxy URL without scheme (should add default https scheme).""" + with patch.dict(os.environ, {'HTTPS_PROXY': 'secure-proxy.example.com:8443'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttps.proxyHost=secure-proxy.example.com' in result + assert '-Dhttps.proxyPort=8443' in result + + def test_proxy_with_username_no_password(self): + """Test proxy with username but no password.""" + with patch.dict(os.environ, {'HTTP_PROXY': 'http://user@proxy.example.com:8080'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.proxyHost=proxy.example.com' in result + assert '-Dhttp.proxyPort=8080' in result + assert '-Dhttp.proxyUser=user' in result + # Password should not be in result since it's None + assert not any('proxyPassword' in arg for arg in result) + + def test_http_proxy_no_port_non_http_scheme(self): + """Test HTTP proxy without port and non-http scheme doesn't add default port.""" + with patch.dict(os.environ, {'HTTP_PROXY': 'socks://proxy.example.com'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttp.proxyHost=proxy.example.com' in result + # Should not add default port for non-http scheme + assert not any('proxyPort' in arg for arg in result) + + def test_https_proxy_no_port_non_https_scheme(self): + """Test HTTPS proxy without port and non-https scheme doesn't add default port.""" + with patch.dict(os.environ, {'HTTPS_PROXY': 'socks://proxy.example.com'}, clear=True): + result = get_proxy_jvm_args() + assert '-Dhttps.proxyHost=proxy.example.com' in result + # Should not add default port for non-https scheme + assert not any('proxyPort' in arg for arg in result) + + def test_empty_proxy_value(self): + """Test with empty proxy environment variable.""" + with patch.dict(os.environ, {'HTTP_PROXY': ''}, clear=True): + result = get_proxy_jvm_args() + assert result == [] + + def test_invalid_url_no_hostname(self): + """Test with invalid URL that has no hostname.""" + with patch.dict(os.environ, {'HTTP_PROXY': 'http://'}, clear=True): + result = get_proxy_jvm_args() + assert result == []