diff --git a/CHANGES.txt b/CHANGES.txt index b9ead323f..4052f3f16 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +5.4.0 (Sep 12, 2025) +- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. +- Added ProxyConfiguration parameter to support proxies, including Harness Forward Proxy, allowing also for more secured authentication options: MTLS, Bearer token and user/password authentication. Read more in our docs. + 5.3.2 (Aug 20, 2025) - Fixed issue with uncaught exception on flags preloading. diff --git a/build.gradle b/build.gradle index 3ec95d825..a0030dcd5 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ apply from: 'spec.gradle' apply from: 'jacoco.gradle' ext { - splitVersion = '5.4.0-rc1' + splitVersion = '5.4.0' jacocoVersion = '0.8.8' } @@ -284,6 +284,3 @@ tasks.withType(Test) { forkEvery = 100 maxHeapSize = "1024m" } - - - diff --git a/src/main/java/io/split/android/client/network/HttpClientImpl.java b/src/main/java/io/split/android/client/network/HttpClientImpl.java index 0d955e19d..3b2a4be33 100644 --- a/src/main/java/io/split/android/client/network/HttpClientImpl.java +++ b/src/main/java/io/split/android/client/network/HttpClientImpl.java @@ -71,8 +71,7 @@ public class HttpClientImpl implements HttpClient { mSslSocketFactory = sslSocketFactory; mUrlSanitizer = urlSanitizer; mCertificateChecker = certificateChecker; - mConnectionHandler = mHttpProxy != null && mSslSocketFactory != null && - (mHttpProxy.getCaCertStream() != null || mHttpProxy.getClientCertStream() != null) ? + mConnectionHandler = mHttpProxy != null && mSslSocketFactory != null ? new ProxyCacertConnectionHandler() : null; } @@ -339,6 +338,9 @@ private SSLSocketFactory createSslSocketFactoryFromProxy(HttpProxy proxyParams) if (caCertBytes != null) { return factoryProvider.create(new ByteArrayInputStream(caCertBytes)); } + } else { + // No custom auth path + return factoryProvider.create(null); } } catch (Exception e) { Logger.e("Failed to create SSLSocketFactory for proxy: " + proxyParams.getHost() + ", error: " + e.getMessage()); diff --git a/src/main/java/io/split/android/client/network/HttpRequestHelper.java b/src/main/java/io/split/android/client/network/HttpRequestHelper.java index 0fe702bd4..e18bda179 100644 --- a/src/main/java/io/split/android/client/network/HttpRequestHelper.java +++ b/src/main/java/io/split/android/client/network/HttpRequestHelper.java @@ -32,7 +32,9 @@ static HttpURLConnection createConnection(@NonNull URL url, @Nullable ProxyCredentialsProvider proxyCredentialsProvider, @Nullable String body) throws IOException { - if (httpProxy != null && sslSocketFactory != null && (httpProxy.getCaCertStream() != null || httpProxy.getClientCertStream() != null)) { + // Use the new tunnel path only when there is no legacy authenticator present. + // If a legacy authenticator proxy, we prefer the legacy path to preserve 407 retry behavior. + if (httpProxy != null && sslSocketFactory != null && !httpProxy.isLegacy()) { try { HttpResponse response = mConnectionHandler.executeRequest( httpProxy, @@ -60,12 +62,11 @@ private static HttpURLConnection openConnection(@Nullable Proxy proxy, @NonNull HttpMethod method, @NonNull Map headers, boolean useProxyAuthentication) throws IOException { - - // Check if we need custom SSL proxy handling - if (httpProxy != null && (httpProxy.getCaCertStream() != null || httpProxy.getClientCertStream() != null)) { + + if (httpProxy != null && !httpProxy.isLegacy() && (httpProxy.getCaCertStream() != null || httpProxy.getClientCertStream() != null)) { throw new IOException("SSL proxy scenarios require custom handling - use executeRequest method instead"); } - + // Standard HttpURLConnection proxy handling HttpURLConnection connection; if (proxy != null) { diff --git a/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java b/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java index 3a010c04f..d6f48b8d9 100644 --- a/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java +++ b/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java @@ -123,7 +123,7 @@ private void closeBufferedReader() { private HttpStreamResponse getRequest() throws HttpException, IOException { HttpStreamResponse response; try { - if (mConnectionHandler != null && mHttpProxy != null && mSslSocketFactory != null && (mHttpProxy.getCaCertStream() != null || mHttpProxy.getClientCertStream() != null)) { + if (mConnectionHandler != null && mHttpProxy != null && mSslSocketFactory != null) { response = mConnectionHandler.executeStreamRequest(mHttpProxy, getUrl(), mHttpMethod, mHeaders, mSslSocketFactory, mProxyCredentialsProvider); } else { mConnection = setUpConnection(false); diff --git a/src/test/java/io/split/android/client/network/HttpClientTest.java b/src/test/java/io/split/android/client/network/HttpClientTest.java index 3ecec1cc3..2daa5063b 100644 --- a/src/test/java/io/split/android/client/network/HttpClientTest.java +++ b/src/test/java/io/split/android/client/network/HttpClientTest.java @@ -281,7 +281,7 @@ public MockResponse dispatch(RecordedRequest request) { HttpClient client = new HttpClientImpl.Builder() .setContext(mock(Context.class)) .setUrlSanitizer(mUrlSanitizerMock) - .setProxy(HttpProxy.newBuilder(mProxyServer.getHostName(), mProxyServer.getPort()).build()) + .setProxy(HttpProxy.newBuilder(mProxyServer.getHostName(), mProxyServer.getPort()).buildLegacy()) .build(); HttpRequest request = client.request(mWebServer.url("/test1/").uri(), HttpMethod.GET); @@ -325,7 +325,7 @@ public SplitAuthenticatedRequest authenticate(@NonNull SplitAuthenticatedRequest return request; } }) - .setProxy(HttpProxy.newBuilder(mProxyServer.getHostName(), mProxyServer.getPort()).build()) + .setProxy(HttpProxy.newBuilder(mProxyServer.getHostName(), mProxyServer.getPort()).buildLegacy()) .build(); HttpRequest request = client.request(mWebServer.url("/test1/").uri(), HttpMethod.GET); @@ -380,7 +380,7 @@ public SplitAuthenticatedRequest authenticate(@NonNull SplitAuthenticatedRequest return request; } }) - .setProxy(HttpProxy.newBuilder(mProxyServer.getHostName(), mProxyServer.getPort()).build()) + .setProxy(HttpProxy.newBuilder(mProxyServer.getHostName(), mProxyServer.getPort()).buildLegacy()) .build(); HttpRequest request = client.request(mWebServer.url("/test1/").uri(), HttpMethod.POST, "{}");