From a96ce3c0973c89667dd98afcbeef67548fc7d3f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:40:48 +0000 Subject: [PATCH 1/3] Initial plan From 75798d2a8c88ce7425bcef778a7d64522fc37937 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:12:21 +0000 Subject: [PATCH 2/3] Add Proxy-Authorization header for HTTP proxy authentication - Add support for Basic and NTLM authentication methods - Set Proxy-Authorization header when credentials are provided - Basic auth used when only user/password provided - NTLM auth used when domain and workstation also provided - Add comprehensive unit tests for all scenarios - Fixes HTTP proxy 407 authentication errors Co-authored-by: slachiewicz <6705942+slachiewicz@users.noreply.github.com> --- .../java/quickfix/mina/ProtocolFactory.java | 28 ++++++- .../quickfix/mina/ProtocolFactoryTest.java | 81 +++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java b/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java index ad53faee1c..8945617a22 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java @@ -21,7 +21,11 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.Base64; +import java.util.Collections; import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.mina.core.service.IoAcceptor; @@ -145,6 +149,8 @@ private static ProxyRequest createHttpProxyRequest(InetSocketAddress address, String proxyPassword, String proxyDomain, String proxyWorkstation) { + HttpProxyRequest req = new HttpProxyRequest(address); + HashMap props = new HashMap<>(); props.put(HttpProxyConstants.USER_PROPERTY, proxyUser); props.put(HttpProxyConstants.PWD_PROPERTY, proxyPassword); @@ -152,15 +158,33 @@ private static ProxyRequest createHttpProxyRequest(InetSocketAddress address, props.put(HttpProxyConstants.DOMAIN_PROPERTY, proxyDomain); props.put(HttpProxyConstants.WORKSTATION_PROPERTY, proxyWorkstation); } - - HttpProxyRequest req = new HttpProxyRequest(address); req.setProperties(props); + if (proxyVersion != null && proxyVersion.equalsIgnoreCase("1.1")) { req.setHttpVersion(HttpProxyConstants.HTTP_1_1); } else { req.setHttpVersion(HttpProxyConstants.HTTP_1_0); } + // Set Proxy-Authorization header if credentials are provided + // Some proxy servers require this header to be set upfront rather than + // waiting for a 407 response + if (proxyUser != null && proxyPassword != null) { + Map> headers = new HashMap<>(); + + // Use NTLM authentication if domain and workstation are provided + if (proxyDomain != null && proxyWorkstation != null) { + headers.put("Proxy-Authorization", Collections.singletonList("NTLM")); + } else { + // Use Basic authentication + String credentials = proxyUser + ":" + proxyPassword; + String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes()); + headers.put("Proxy-Authorization", Collections.singletonList("Basic " + encodedCredentials)); + } + + req.setHeaders(headers); + } + return req; } diff --git a/quickfixj-core/src/test/java/quickfix/mina/ProtocolFactoryTest.java b/quickfixj-core/src/test/java/quickfix/mina/ProtocolFactoryTest.java index e6de3f42e0..13daf46721 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/ProtocolFactoryTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/ProtocolFactoryTest.java @@ -21,6 +21,7 @@ import org.apache.mina.core.service.IoConnector; import org.apache.mina.proxy.ProxyConnector; +import org.apache.mina.proxy.handlers.http.HttpProxyRequest; import org.apache.mina.proxy.session.ProxyIoSession; import org.apache.mina.transport.socket.SocketConnector; import org.apache.mina.util.AvailablePortFinder; @@ -28,8 +29,14 @@ import quickfix.ConfigError; import java.net.InetSocketAddress; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class ProtocolFactoryTest { @@ -46,4 +53,78 @@ public void shouldCreateProxyConnectorWithoutPreferredAuthOrder() throws ConfigE ProxyIoSession proxySession = proxyConnector.getProxyIoSession(); assertNull(proxySession.getPreferedOrder()); } + + @Test + public void shouldSetBasicAuthorizationHeaderForHttpProxy() throws ConfigError { + InetSocketAddress address = new InetSocketAddress(AvailablePortFinder.getNextAvailable()); + InetSocketAddress proxyAddress = new InetSocketAddress(AvailablePortFinder.getNextAvailable()); + + IoConnector connector = ProtocolFactory.createIoConnector(address); + ProxyConnector proxyConnector = ProtocolFactory + .createIoProxyConnector((SocketConnector) connector, address, proxyAddress, "http", "1.0", "testuser", + "testpassword", null, null); + + ProxyIoSession proxySession = proxyConnector.getProxyIoSession(); + HttpProxyRequest request = (HttpProxyRequest) proxySession.getRequest(); + + Map> headers = request.getHeaders(); + assertNotNull("Headers should not be null", headers); + assertTrue("Headers should contain Proxy-Authorization", headers.containsKey("Proxy-Authorization")); + + List authHeaders = headers.get("Proxy-Authorization"); + assertNotNull("Proxy-Authorization header should not be null", authHeaders); + assertEquals("Should have exactly one Proxy-Authorization header", 1, authHeaders.size()); + + String authHeader = authHeaders.get(0); + assertTrue("Auth header should start with 'Basic '", authHeader.startsWith("Basic ")); + + // Verify the encoded credentials + String encodedPart = authHeader.substring("Basic ".length()); + String decoded = new String(Base64.getDecoder().decode(encodedPart)); + assertEquals("Decoded credentials should match", "testuser:testpassword", decoded); + } + + @Test + public void shouldSetNTLMAuthorizationHeaderForHttpProxyWithDomain() throws ConfigError { + InetSocketAddress address = new InetSocketAddress(AvailablePortFinder.getNextAvailable()); + InetSocketAddress proxyAddress = new InetSocketAddress(AvailablePortFinder.getNextAvailable()); + + IoConnector connector = ProtocolFactory.createIoConnector(address); + ProxyConnector proxyConnector = ProtocolFactory + .createIoProxyConnector((SocketConnector) connector, address, proxyAddress, "http", "1.0", "testuser", + "testpassword", "TESTDOMAIN", "TESTWORKSTATION"); + + ProxyIoSession proxySession = proxyConnector.getProxyIoSession(); + HttpProxyRequest request = (HttpProxyRequest) proxySession.getRequest(); + + Map> headers = request.getHeaders(); + assertNotNull("Headers should not be null", headers); + assertTrue("Headers should contain Proxy-Authorization", headers.containsKey("Proxy-Authorization")); + + List authHeaders = headers.get("Proxy-Authorization"); + assertNotNull("Proxy-Authorization header should not be null", authHeaders); + assertEquals("Should have exactly one Proxy-Authorization header", 1, authHeaders.size()); + + String authHeader = authHeaders.get(0); + assertEquals("Auth header should be 'NTLM'", "NTLM", authHeader); + } + + @Test + public void shouldNotSetAuthorizationHeaderWhenCredentialsNotProvided() throws ConfigError { + InetSocketAddress address = new InetSocketAddress(AvailablePortFinder.getNextAvailable()); + InetSocketAddress proxyAddress = new InetSocketAddress(AvailablePortFinder.getNextAvailable()); + + IoConnector connector = ProtocolFactory.createIoConnector(address); + ProxyConnector proxyConnector = ProtocolFactory + .createIoProxyConnector((SocketConnector) connector, address, proxyAddress, "http", "1.0", null, + null, null, null); + + ProxyIoSession proxySession = proxyConnector.getProxyIoSession(); + HttpProxyRequest request = (HttpProxyRequest) proxySession.getRequest(); + + Map> headers = request.getHeaders(); + // Headers should either be null or not contain Proxy-Authorization + assertTrue("Headers should be null or empty when no credentials provided", + headers == null || !headers.containsKey("Proxy-Authorization")); + } } From c2556bc26fed9bed26a6356d20bae535e698353e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:14:45 +0000 Subject: [PATCH 3/3] Use UTF-8 charset for Base64 encoding credentials - Ensures consistent encoding across platforms - Prevents issues with non-ASCII characters in credentials Co-authored-by: slachiewicz <6705942+slachiewicz@users.noreply.github.com> --- .../src/main/java/quickfix/mina/ProtocolFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java b/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java index 8945617a22..00d126ea5d 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java @@ -21,6 +21,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; import java.util.HashMap; @@ -178,7 +179,7 @@ private static ProxyRequest createHttpProxyRequest(InetSocketAddress address, } else { // Use Basic authentication String credentials = proxyUser + ":" + proxyPassword; - String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes()); + String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); headers.put("Proxy-Authorization", Collections.singletonList("Basic " + encodedCredentials)); }