diff --git a/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java b/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java index ad53faee1c..00d126ea5d 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ProtocolFactory.java @@ -21,7 +21,12 @@ 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; +import java.util.List; +import java.util.Map; import org.apache.mina.core.service.IoAcceptor; @@ -145,6 +150,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 +159,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(StandardCharsets.UTF_8)); + 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")); + } }