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 == []