From 51b0a170a434e0c713dc81da1eb4125ba6b74f48 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Feb 2026 15:31:34 -0300 Subject: [PATCH 01/14] Http modules scaffolding --- build.gradle | 2 ++ http-domain/.gitignore | 1 + http-domain/README.md | 5 +++++ http-domain/build.gradle | 22 ++++++++++++++++++++ http-domain/consumer-rules.pro | 1 + http-domain/proguard-rules.pro | 21 +++++++++++++++++++ http-domain/src/main/AndroidManifest.xml | 4 ++++ http/.gitignore | 1 + http/README.md | 5 +++++ http/build.gradle | 26 ++++++++++++++++++++++++ http/consumer-rules.pro | 1 + http/proguard-rules.pro | 21 +++++++++++++++++++ http/src/main/AndroidManifest.xml | 4 ++++ main/build.gradle | 2 ++ settings.gradle | 2 ++ sonar-project.properties | 22 ++++++++++++++------ 16 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 http-domain/.gitignore create mode 100644 http-domain/README.md create mode 100644 http-domain/build.gradle create mode 100644 http-domain/consumer-rules.pro create mode 100644 http-domain/proguard-rules.pro create mode 100644 http-domain/src/main/AndroidManifest.xml create mode 100644 http/.gitignore create mode 100644 http/README.md create mode 100644 http/build.gradle create mode 100644 http/consumer-rules.pro create mode 100644 http/proguard-rules.pro create mode 100644 http/src/main/AndroidManifest.xml diff --git a/build.gradle b/build.gradle index f521dda49..64f9cdfbc 100644 --- a/build.gradle +++ b/build.gradle @@ -140,6 +140,8 @@ dependencies { include project(':events') include project(':events-domain') include project(':api') + include project(':http-domain') + include project(':http') } def javadocSourceProjects = providers.provider { diff --git a/http-domain/.gitignore b/http-domain/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/http-domain/.gitignore @@ -0,0 +1 @@ +/build diff --git a/http-domain/README.md b/http-domain/README.md new file mode 100644 index 000000000..362956fbb --- /dev/null +++ b/http-domain/README.md @@ -0,0 +1,5 @@ +# HTTP Domain module + +This module contains public HTTP configuration contracts exposed to consumers of the Split SDK. + +Includes certificate pinning configuration, proxy settings, SSL configuration, and authenticator interfaces. diff --git a/http-domain/build.gradle b/http-domain/build.gradle new file mode 100644 index 000000000..05b24204a --- /dev/null +++ b/http-domain/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'com.android.library' +} + +apply from: "$rootDir/gradle/common-android-library.gradle" + +android { + namespace 'io.split.android.client.network.domain' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation libs.annotation + implementation project(':logger') + + testImplementation libs.junit4 + testImplementation libs.mockitoCore +} diff --git a/http-domain/consumer-rules.pro b/http-domain/consumer-rules.pro new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/http-domain/consumer-rules.pro @@ -0,0 +1 @@ + diff --git a/http-domain/proguard-rules.pro b/http-domain/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/http-domain/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/http-domain/src/main/AndroidManifest.xml b/http-domain/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8bdb7e14b --- /dev/null +++ b/http-domain/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/http/.gitignore b/http/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/http/.gitignore @@ -0,0 +1 @@ +/build diff --git a/http/README.md b/http/README.md new file mode 100644 index 000000000..199f22805 --- /dev/null +++ b/http/README.md @@ -0,0 +1,5 @@ +# HTTP module + +This module provides the HTTP client implementation used internally by the Split SDK. + +Includes request/response lifecycle, certificate pinning runtime, proxy tunnelling, and TLS configuration. Hidden from SDK consumers. diff --git a/http/build.gradle b/http/build.gradle new file mode 100644 index 000000000..85ef7d707 --- /dev/null +++ b/http/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'com.android.library' +} + +apply from: "$rootDir/gradle/common-android-library.gradle" + +android { + namespace 'io.split.android.client.network.http' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation libs.annotation + implementation project(':logger') + implementation project(':http-domain') + + testImplementation libs.junit4 + testImplementation libs.mockitoCore + testImplementation libs.mockitoInline + testImplementation libs.okhttpMockwebserver + testImplementation libs.okhttpTls +} diff --git a/http/consumer-rules.pro b/http/consumer-rules.pro new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/http/consumer-rules.pro @@ -0,0 +1 @@ + diff --git a/http/proguard-rules.pro b/http/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/http/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/http/src/main/AndroidManifest.xml b/http/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8bdb7e14b --- /dev/null +++ b/http/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/main/build.gradle b/main/build.gradle index 7ec1e3110..70d932be8 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -52,7 +52,9 @@ dependencies { // Public api modules api project(':logger') api project(':api') + api project(':http-domain') // Internal module dependencies + implementation project(':http') implementation project(':events-domain') // External dependencies diff --git a/settings.gradle b/settings.gradle index b584365a6..7eaafe2d3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,8 @@ rootProject.name = 'android-client' include ':api' include ':logger' +include ':http-domain' +include ':http' include ':main' include ':events' include ':events-domain' diff --git a/sonar-project.properties b/sonar-project.properties index f598dd559..a3cc5e80d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,15 +3,17 @@ sonar.projectKey=splitio_android-client sonar.projectName=android-client # Path to source directories (multi-module) -# Root project contains modules: main, events, logger -sonar.sources=main/src/main/java,events/src/main/java,logger/src/main/java +# Root project contains modules: main, events, logger, http-domain, http +sonar.sources=main/src/main/java,events/src/main/java,logger/src/main/java,http-domain/src/main/java,http/src/main/java # Path to compiled classes (multi-module) -# Include binary paths for all modules: main, events, logger +# Include binary paths for all modules: main, events, logger, http-domain, http sonar.java.binaries=\ main/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ events/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ - logger/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes + logger/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ + http-domain/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ + http/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes # Path to dependency/libraries jars (multi-module) sonar.java.libraries=\ @@ -26,11 +28,19 @@ sonar.java.libraries=\ logger/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ logger/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ logger/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ - logger/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar + logger/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar,\ + http-domain/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ + http-domain/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ + http-domain/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ + http-domain/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar,\ + http/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ + http/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ + http/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ + http/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar # Path to test directories (multi-module) # Only include test source folders that are guaranteed to exist in all environments -sonar.tests=main/src/test/java,main/src/androidTest/java,main/src/sharedTest/java,events/src/test/java,logger/src/test/java +sonar.tests=main/src/test/java,main/src/androidTest/java,main/src/sharedTest/java,events/src/test/java,logger/src/test/java,http-domain/src/test/java,http/src/test/java # Encoding of the source code sonar.sourceEncoding=UTF-8 From b16802877bf1b7490b1a39b5f2aabe269d42cf58 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Feb 2026 16:05:23 -0300 Subject: [PATCH 02/14] Move files to http modules --- http-domain/build.gradle | 2 ++ .../android/client/network/Algorithm.java | 7 +++++++ .../client/network/AuthenticatedRequest.java | 2 +- .../android/client/network/Authenticator.java | 9 ++++++++ .../android/client/network/Base64Decoder.java | 2 +- .../network/BasicCredentialsProvider.java | 0 .../network/BearerCredentialsProvider.java | 0 .../network/CertificateCheckerHelper.java | 6 +++--- .../client/network/CertificatePin.java | 6 +----- .../CertificatePinningConfiguration.java | 3 +-- .../CertificatePinningFailureListener.java | 0 .../client/network/DefaultBase64Decoder.java | 20 ++++++++++++++++++ .../client/network/DevelopmentSslConfig.java | 0 .../android/client/network/HttpProxy.java | 0 .../android/client/network/PinEncoder.java | 2 +- .../client/network/PinEncoderImpl.java | 2 +- .../client/network/ProxyConfiguration.java | 0 .../network/ProxyCredentialsProvider.java | 0 .../client/network/SplitAuthenticator.java | 2 +- .../network/CertificateCheckerHelperTest.java | 0 .../CertificatePinningConfigurationTest.java | 0 .../client/network/PinEncoderImplTest.java | 0 .../android/client/network/Algorithm.java | 7 ------- .../android/client/network/Authenticator.java | 9 -------- ...rtificatePinningConfigurationProvider.java | 21 +++++++++++++++---- .../client/network/DefaultBase64Decoder.java | 11 ---------- .../network/SplitAuthenticatedRequest.java | 2 +- .../network/SplitBasicAuthenticator.java | 2 +- .../SplitUrlConnectionAuthenticator.java | 2 +- .../android/client/SplitClientConfigTest.java | 3 ++- .../client/network/HttpClientTest.java | 4 ++-- .../network/SplitAuthenticatorTest.java | 6 +++--- .../network/SplitBasicAuthenticatorTest.java | 2 +- 33 files changed, 76 insertions(+), 56 deletions(-) create mode 100644 http-domain/src/main/java/io/split/android/client/network/Algorithm.java rename {main => http-domain}/src/main/java/io/split/android/client/network/AuthenticatedRequest.java (90%) create mode 100644 http-domain/src/main/java/io/split/android/client/network/Authenticator.java rename {main => http-domain}/src/main/java/io/split/android/client/network/Base64Decoder.java (70%) rename {main => http-domain}/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java (100%) rename {main => http-domain}/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java (100%) rename {main => http-domain}/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java (91%) rename {main => http-domain}/src/main/java/io/split/android/client/network/CertificatePin.java (84%) rename {main => http-domain}/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java (98%) rename {main => http-domain}/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java (100%) create mode 100644 http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java rename {main => http-domain}/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java (100%) rename {main => http-domain}/src/main/java/io/split/android/client/network/HttpProxy.java (100%) rename {main => http-domain}/src/main/java/io/split/android/client/network/PinEncoder.java (84%) rename {main => http-domain}/src/main/java/io/split/android/client/network/PinEncoderImpl.java (96%) rename {main => http-domain}/src/main/java/io/split/android/client/network/ProxyConfiguration.java (100%) rename {main => http-domain}/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java (100%) rename {main => http-domain}/src/main/java/io/split/android/client/network/SplitAuthenticator.java (81%) rename {main => http-domain}/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java (100%) rename {main => http-domain}/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java (100%) rename {main => http-domain}/src/test/java/io/split/android/client/network/PinEncoderImplTest.java (100%) delete mode 100644 main/src/main/java/io/split/android/client/network/Algorithm.java delete mode 100644 main/src/main/java/io/split/android/client/network/Authenticator.java delete mode 100644 main/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java diff --git a/http-domain/build.gradle b/http-domain/build.gradle index 05b24204a..b15da12d4 100644 --- a/http-domain/build.gradle +++ b/http-domain/build.gradle @@ -19,4 +19,6 @@ dependencies { testImplementation libs.junit4 testImplementation libs.mockitoCore + testImplementation libs.mockitoInline + testImplementation libs.okhttpTls } diff --git a/http-domain/src/main/java/io/split/android/client/network/Algorithm.java b/http-domain/src/main/java/io/split/android/client/network/Algorithm.java new file mode 100644 index 000000000..bd3784efe --- /dev/null +++ b/http-domain/src/main/java/io/split/android/client/network/Algorithm.java @@ -0,0 +1,7 @@ +package io.split.android.client.network; + +public class Algorithm { + + public static final String SHA256 = "sha256"; + public static final String SHA1 = "sha1"; +} diff --git a/main/src/main/java/io/split/android/client/network/AuthenticatedRequest.java b/http-domain/src/main/java/io/split/android/client/network/AuthenticatedRequest.java similarity index 90% rename from main/src/main/java/io/split/android/client/network/AuthenticatedRequest.java rename to http-domain/src/main/java/io/split/android/client/network/AuthenticatedRequest.java index f6dfa1a43..6e541e3de 100644 --- a/main/src/main/java/io/split/android/client/network/AuthenticatedRequest.java +++ b/http-domain/src/main/java/io/split/android/client/network/AuthenticatedRequest.java @@ -5,7 +5,7 @@ import java.util.Map; -interface AuthenticatedRequest { +public interface AuthenticatedRequest { void setHeader(@NonNull String name, @NonNull String value); diff --git a/http-domain/src/main/java/io/split/android/client/network/Authenticator.java b/http-domain/src/main/java/io/split/android/client/network/Authenticator.java new file mode 100644 index 000000000..4fab265e4 --- /dev/null +++ b/http-domain/src/main/java/io/split/android/client/network/Authenticator.java @@ -0,0 +1,9 @@ +package io.split.android.client.network; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public interface Authenticator { + + @Nullable AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request); +} diff --git a/main/src/main/java/io/split/android/client/network/Base64Decoder.java b/http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java similarity index 70% rename from main/src/main/java/io/split/android/client/network/Base64Decoder.java rename to http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java index 387a900f0..f31358046 100644 --- a/main/src/main/java/io/split/android/client/network/Base64Decoder.java +++ b/http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -interface Base64Decoder { +public interface Base64Decoder { byte[] decode(String base64); } diff --git a/main/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java b/http-domain/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java rename to http-domain/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java diff --git a/main/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java b/http-domain/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java rename to http-domain/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java diff --git a/main/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java b/http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java similarity index 91% rename from main/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java rename to http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java index 709534b88..45245ed0d 100644 --- a/main/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java +++ b/http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java @@ -15,10 +15,10 @@ import io.split.android.client.utils.logger.Logger; -class CertificateCheckerHelper { +public class CertificateCheckerHelper { @Nullable - static Set getPinsForHost(String pattern, Map> configuredPins) { + public static Set getPinsForHost(String pattern, Map> configuredPins) { Set hostPins = configuredPins.get(pattern); Set wildcardPins = new LinkedHashSet<>(); @@ -53,7 +53,7 @@ static Set getPinsForHost(String pattern, Map getPinsFromInputStream(InputStream inputStream, PinEncoder pinEncoder) { + public static Set getPinsFromInputStream(InputStream inputStream, PinEncoder pinEncoder) { try (InputStream stream = inputStream) { CertificateFactory factory = CertificateFactory.getInstance("X.509"); diff --git a/main/src/main/java/io/split/android/client/network/CertificatePin.java b/http-domain/src/main/java/io/split/android/client/network/CertificatePin.java similarity index 84% rename from main/src/main/java/io/split/android/client/network/CertificatePin.java rename to http-domain/src/main/java/io/split/android/client/network/CertificatePin.java index 6056ff7e7..98739d294 100644 --- a/main/src/main/java/io/split/android/client/network/CertificatePin.java +++ b/http-domain/src/main/java/io/split/android/client/network/CertificatePin.java @@ -1,18 +1,14 @@ package io.split.android.client.network; -import com.google.gson.annotations.SerializedName; - import java.util.Arrays; import java.util.Objects; public class CertificatePin { - @SerializedName("pin") private final byte[] mPin; - @SerializedName("algo") private final String mAlgorithm; - CertificatePin(byte[] pin, String algorithm) { + public CertificatePin(byte[] pin, String algorithm) { mPin = pin; mAlgorithm = algorithm; } diff --git a/main/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java b/http-domain/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java similarity index 98% rename from main/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java rename to http-domain/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java index 23ec94d5c..b110ba5be 100644 --- a/main/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java +++ b/http-domain/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java @@ -9,7 +9,6 @@ import java.util.Map; import java.util.Set; -import io.split.android.client.utils.Base64Util; import io.split.android.client.utils.logger.Logger; public class CertificatePinningConfiguration { @@ -160,7 +159,7 @@ public Builder failureListener(@NonNull CertificatePinningFailureListener failur } // Meant to be used only when setting up bg sync jobs - void addPins(String host, Set pins) { + public void addPins(String host, Set pins) { if (host == null || host.trim().isEmpty()) { Logger.e("Host cannot be null or empty. Ignoring entry"); return; diff --git a/main/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java b/http-domain/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java rename to http-domain/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java diff --git a/http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java b/http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java new file mode 100644 index 000000000..486eb5be2 --- /dev/null +++ b/http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java @@ -0,0 +1,20 @@ +package io.split.android.client.network; + +import android.util.Base64; + +import io.split.android.client.utils.logger.Logger; + +public class DefaultBase64Decoder implements Base64Decoder { + + @Override + public byte[] decode(String base64) { + try { + return Base64.decode(base64, Base64.DEFAULT); + } catch (IllegalArgumentException e) { + Logger.e("Received bytes didn't correspond to a valid Base64 encoded string." + e.getLocalizedMessage()); + } catch (Exception e) { + Logger.e("An unknown error has occurred " + e.getLocalizedMessage()); + } + return null; + } +} diff --git a/main/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java b/http-domain/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java rename to http-domain/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java diff --git a/main/src/main/java/io/split/android/client/network/HttpProxy.java b/http-domain/src/main/java/io/split/android/client/network/HttpProxy.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpProxy.java rename to http-domain/src/main/java/io/split/android/client/network/HttpProxy.java diff --git a/main/src/main/java/io/split/android/client/network/PinEncoder.java b/http-domain/src/main/java/io/split/android/client/network/PinEncoder.java similarity index 84% rename from main/src/main/java/io/split/android/client/network/PinEncoder.java rename to http-domain/src/main/java/io/split/android/client/network/PinEncoder.java index 3de8beecf..d34212ca8 100644 --- a/main/src/main/java/io/split/android/client/network/PinEncoder.java +++ b/http-domain/src/main/java/io/split/android/client/network/PinEncoder.java @@ -2,7 +2,7 @@ import androidx.annotation.NonNull; -interface PinEncoder { +public interface PinEncoder { @NonNull byte[] encodeCertPin(String algorithm, byte[] encodedPublicKey); diff --git a/main/src/main/java/io/split/android/client/network/PinEncoderImpl.java b/http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java similarity index 96% rename from main/src/main/java/io/split/android/client/network/PinEncoderImpl.java rename to http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java index 7132b1828..f1e010d51 100644 --- a/main/src/main/java/io/split/android/client/network/PinEncoderImpl.java +++ b/http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java @@ -7,7 +7,7 @@ import io.split.android.client.utils.logger.Logger; -class PinEncoderImpl implements PinEncoder { +public class PinEncoderImpl implements PinEncoder { @Override @NonNull diff --git a/main/src/main/java/io/split/android/client/network/ProxyConfiguration.java b/http-domain/src/main/java/io/split/android/client/network/ProxyConfiguration.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ProxyConfiguration.java rename to http-domain/src/main/java/io/split/android/client/network/ProxyConfiguration.java diff --git a/main/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java b/http-domain/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java rename to http-domain/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java diff --git a/main/src/main/java/io/split/android/client/network/SplitAuthenticator.java b/http-domain/src/main/java/io/split/android/client/network/SplitAuthenticator.java similarity index 81% rename from main/src/main/java/io/split/android/client/network/SplitAuthenticator.java rename to http-domain/src/main/java/io/split/android/client/network/SplitAuthenticator.java index 542ff42dc..494ba736e 100644 --- a/main/src/main/java/io/split/android/client/network/SplitAuthenticator.java +++ b/http-domain/src/main/java/io/split/android/client/network/SplitAuthenticator.java @@ -1,6 +1,6 @@ package io.split.android.client.network; /** @noinspection unused*/ -public abstract class SplitAuthenticator implements Authenticator { +public abstract class SplitAuthenticator implements Authenticator { } diff --git a/main/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java b/http-domain/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java rename to http-domain/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java diff --git a/main/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java b/http-domain/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java rename to http-domain/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java diff --git a/main/src/test/java/io/split/android/client/network/PinEncoderImplTest.java b/http-domain/src/test/java/io/split/android/client/network/PinEncoderImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/PinEncoderImplTest.java rename to http-domain/src/test/java/io/split/android/client/network/PinEncoderImplTest.java diff --git a/main/src/main/java/io/split/android/client/network/Algorithm.java b/main/src/main/java/io/split/android/client/network/Algorithm.java deleted file mode 100644 index 2e193751f..000000000 --- a/main/src/main/java/io/split/android/client/network/Algorithm.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.split.android.client.network; - -class Algorithm { - - static final String SHA256 = "sha256"; - static final String SHA1 = "sha1"; -} diff --git a/main/src/main/java/io/split/android/client/network/Authenticator.java b/main/src/main/java/io/split/android/client/network/Authenticator.java deleted file mode 100644 index c23a39994..000000000 --- a/main/src/main/java/io/split/android/client/network/Authenticator.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.split.android.client.network; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -interface Authenticator> { - - @Nullable T authenticate(@NonNull T request); -} diff --git a/main/src/main/java/io/split/android/client/network/CertificatePinningConfigurationProvider.java b/main/src/main/java/io/split/android/client/network/CertificatePinningConfigurationProvider.java index aa640fbc5..801baa909 100644 --- a/main/src/main/java/io/split/android/client/network/CertificatePinningConfigurationProvider.java +++ b/main/src/main/java/io/split/android/client/network/CertificatePinningConfigurationProvider.java @@ -1,8 +1,10 @@ package io.split.android.client.network; +import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -13,14 +15,18 @@ public class CertificatePinningConfigurationProvider { public static CertificatePinningConfiguration getCertificatePinningConfiguration(String pinsJson) { try { - Type type = new TypeToken>>() { + Type type = new TypeToken>>() { }.getType(); - Map> certificatePins = Json.fromJson(pinsJson, type); + Map> certificatePins = Json.fromJson(pinsJson, type); if (certificatePins != null && !certificatePins.isEmpty()) { CertificatePinningConfiguration.Builder builder = CertificatePinningConfiguration.builder(); - for (Map.Entry> entry : certificatePins.entrySet()) { - builder.addPins(entry.getKey(), entry.getValue()); + for (Map.Entry> entry : certificatePins.entrySet()) { + Set pins = new HashSet<>(); + for (CertificatePinDto dto : entry.getValue()) { + pins.add(new CertificatePin(dto.pin, dto.algorithm)); + } + builder.addPins(entry.getKey(), pins); } return builder @@ -32,4 +38,11 @@ public static CertificatePinningConfiguration getCertificatePinningConfiguration return null; } + + private static class CertificatePinDto { + @SerializedName("pin") + byte[] pin; + @SerializedName("algo") + String algorithm; + } } diff --git a/main/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java b/main/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java deleted file mode 100644 index c84903fb6..000000000 --- a/main/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.split.android.client.network; - -import io.split.android.client.utils.Base64Util; - -class DefaultBase64Decoder implements Base64Decoder { - - @Override - public byte[] decode(String base64) { - return Base64Util.bytesDecode(base64); - } -} diff --git a/main/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java b/main/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java index 9b426385c..cddb6370e 100644 --- a/main/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java +++ b/main/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java @@ -8,7 +8,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class SplitAuthenticatedRequest implements AuthenticatedRequest { +public class SplitAuthenticatedRequest implements AuthenticatedRequest { private final String mUrl; private final Map mHeaders = new ConcurrentHashMap<>(); diff --git a/main/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java b/main/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java index bd49d9ca4..b87c6699f 100644 --- a/main/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java +++ b/main/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java @@ -19,7 +19,7 @@ class SplitBasicAuthenticator extends SplitAuthenticator { @Nullable @Override - public SplitAuthenticatedRequest authenticate(@NonNull SplitAuthenticatedRequest request) { + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { String credential = basic(mUsername, mPassword); request.setHeader(PROXY_AUTHORIZATION_HEADER, credential); diff --git a/main/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java b/main/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java index fdb97f302..2c0cd3d5a 100644 --- a/main/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java +++ b/main/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java @@ -12,7 +12,7 @@ class SplitUrlConnectionAuthenticator { } HttpURLConnection authenticate(HttpURLConnection connection) { - SplitAuthenticatedRequest authenticatedRequest = mProxyAuthenticator.authenticate(new SplitAuthenticatedRequest(connection)); + AuthenticatedRequest authenticatedRequest = mProxyAuthenticator.authenticate(new SplitAuthenticatedRequest(connection)); if (authenticatedRequest != null) { Map headers = authenticatedRequest.getHeaders(); diff --git a/main/src/test/java/io/split/android/client/SplitClientConfigTest.java b/main/src/test/java/io/split/android/client/SplitClientConfigTest.java index b97ea6381..4163818ed 100644 --- a/main/src/test/java/io/split/android/client/SplitClientConfigTest.java +++ b/main/src/test/java/io/split/android/client/SplitClientConfigTest.java @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; import io.split.android.client.fallback.FallbackTreatmentsConfiguration; +import io.split.android.client.network.AuthenticatedRequest; import io.split.android.client.network.CertificatePinningConfiguration; import io.split.android.client.network.ProxyConfiguration; import io.split.android.client.network.SplitAuthenticatedRequest; @@ -298,7 +299,7 @@ public void proxyAuthenticatorAndProxyConfigurationSetLogWarning() { .proxyAuthenticator(new SplitAuthenticator() { @Nullable @Override - public SplitAuthenticatedRequest authenticate(@NonNull SplitAuthenticatedRequest request) { + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { return null; } }) diff --git a/main/src/test/java/io/split/android/client/network/HttpClientTest.java b/main/src/test/java/io/split/android/client/network/HttpClientTest.java index 3ecc24ee2..a2f2c8c86 100644 --- a/main/src/test/java/io/split/android/client/network/HttpClientTest.java +++ b/main/src/test/java/io/split/android/client/network/HttpClientTest.java @@ -320,7 +320,7 @@ public MockResponse dispatch(RecordedRequest request) { .setUrlSanitizer(mUrlSanitizerMock) .setProxyAuthenticator(new SplitAuthenticator() { @Override - public SplitAuthenticatedRequest authenticate(@NonNull SplitAuthenticatedRequest request) { + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { authLatch.countDown(); request.setHeader("Proxy-Authorization", "my-auth"); @@ -375,7 +375,7 @@ public MockResponse dispatch(RecordedRequest request) { .setUrlSanitizer(mUrlSanitizerMock) .setProxyAuthenticator(new SplitAuthenticator() { @Override - public SplitAuthenticatedRequest authenticate(@NonNull SplitAuthenticatedRequest request) { + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { authLatch.countDown(); request.setHeader("Proxy-Authorization", "my-auth"); diff --git a/main/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java b/main/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java index 3380c43a1..dae394a09 100644 --- a/main/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java +++ b/main/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java @@ -18,9 +18,9 @@ public class SplitAuthenticatorTest { @Test public void authenticatorModifiesHeaders() { - Authenticator> splitAuthenticator = new Authenticator>() { + Authenticator splitAuthenticator = new Authenticator() { @Override - public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { request.setHeader("new-header", "value"); return request; @@ -48,7 +48,7 @@ public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequ assertEquals("value", finalHeaders.get("new-header")); } - private static class AuthenticatedMockRequest implements AuthenticatedRequest { + private static class AuthenticatedMockRequest implements AuthenticatedRequest { private final MockRequest mRequest; diff --git a/main/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java b/main/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java index 7b56e0291..5b0f27531 100644 --- a/main/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java +++ b/main/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java @@ -29,7 +29,7 @@ public void callingAuthenticateUsesEncoder() { @Test public void callingAuthenticateReturnsCorrectHeaderInRequest() { SplitBasicAuthenticator authenticator = new SplitBasicAuthenticator("user", "pass", mBase64Encoder); - SplitAuthenticatedRequest request = authenticator.authenticate(mock(SplitAuthenticatedRequest.class)); + AuthenticatedRequest request = authenticator.authenticate(mock(SplitAuthenticatedRequest.class)); verify(request).setHeader("Proxy-Authorization", "Basic user:pass"); } From ef7778ba8adcba475289315ea3fc96c76e7588cb Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Feb 2026 16:25:35 -0300 Subject: [PATCH 03/14] Http client config --- .../network/HttpClientConfiguration.java | 142 ++++++++++++++++++ .../network/HttpClientConfigurationTest.java | 97 ++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 http-domain/src/main/java/io/split/android/client/network/HttpClientConfiguration.java create mode 100644 http-domain/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java diff --git a/http-domain/src/main/java/io/split/android/client/network/HttpClientConfiguration.java b/http-domain/src/main/java/io/split/android/client/network/HttpClientConfiguration.java new file mode 100644 index 000000000..6bd6f7d58 --- /dev/null +++ b/http-domain/src/main/java/io/split/android/client/network/HttpClientConfiguration.java @@ -0,0 +1,142 @@ +package io.split.android.client.network; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + + +public class HttpClientConfiguration { + + private final long mConnectionTimeout; + private final long mReadTimeout; + @Nullable + private final HttpProxy mProxy; + @Nullable + private final CertificatePinningConfiguration mCertificatePinningConfiguration; + @Nullable + private final DevelopmentSslConfig mDevelopmentSslConfig; + @Nullable + private final SplitAuthenticator mProxyAuthenticator; + + private HttpClientConfiguration(Builder builder) { + mConnectionTimeout = builder.mConnectionTimeout; + mReadTimeout = builder.mReadTimeout; + mProxy = builder.mProxy; + mCertificatePinningConfiguration = builder.mCertificatePinningConfiguration; + mDevelopmentSslConfig = builder.mDevelopmentSslConfig; + mProxyAuthenticator = builder.mProxyAuthenticator; + } + + public long getConnectionTimeout() { + return mConnectionTimeout; + } + + public long getReadTimeout() { + return mReadTimeout; + } + + @Nullable + public HttpProxy getProxy() { + return mProxy; + } + + @Nullable + public CertificatePinningConfiguration getCertificatePinningConfiguration() { + return mCertificatePinningConfiguration; + } + + @Nullable + public DevelopmentSslConfig getDevelopmentSslConfig() { + return mDevelopmentSslConfig; + } + + @Nullable + public SplitAuthenticator getProxyAuthenticator() { + return mProxyAuthenticator; + } + + @NonNull + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private long mConnectionTimeout; + private long mReadTimeout; + @Nullable + private HttpProxy mProxy; + @Nullable + private CertificatePinningConfiguration mCertificatePinningConfiguration; + @Nullable + private DevelopmentSslConfig mDevelopmentSslConfig; + @Nullable + private SplitAuthenticator mProxyAuthenticator; + + private Builder() { + } + + /** + * Sets the connection timeout in milliseconds. + */ + @NonNull + public Builder connectionTimeout(long connectionTimeout) { + mConnectionTimeout = connectionTimeout; + return this; + } + + /** + * Sets the read timeout in milliseconds. + */ + @NonNull + public Builder readTimeout(long readTimeout) { + mReadTimeout = readTimeout; + return this; + } + + /** + * Sets the HTTP proxy configuration. + */ + @NonNull + public Builder proxy(@Nullable HttpProxy proxy) { + mProxy = proxy; + return this; + } + + /** + * Sets the certificate pinning configuration. + */ + @NonNull + public Builder certificatePinningConfiguration(@Nullable CertificatePinningConfiguration configuration) { + mCertificatePinningConfiguration = configuration; + return this; + } + + /** + * Sets the development SSL configuration. + *

+ * This is intended for development/testing environments only. + */ + @NonNull + public Builder developmentSslConfig(@Nullable DevelopmentSslConfig developmentSslConfig) { + mDevelopmentSslConfig = developmentSslConfig; + return this; + } + + /** + * Sets the proxy authenticator. + */ + @NonNull + public Builder proxyAuthenticator(@Nullable SplitAuthenticator proxyAuthenticator) { + mProxyAuthenticator = proxyAuthenticator; + return this; + } + + /** + * Builds the configuration. + */ + @NonNull + public HttpClientConfiguration build() { + return new HttpClientConfiguration(this); + } + } +} diff --git a/http-domain/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java b/http-domain/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java new file mode 100644 index 000000000..f722a3439 --- /dev/null +++ b/http-domain/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java @@ -0,0 +1,97 @@ +package io.split.android.client.network; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class HttpClientConfigurationTest { + + @Test + public void builderSetsConnectionTimeout() { + HttpClientConfiguration config = HttpClientConfiguration.builder() + .connectionTimeout(15_000) + .build(); + + assertEquals(15_000, config.getConnectionTimeout()); + } + + @Test + public void builderSetsReadTimeout() { + HttpClientConfiguration config = HttpClientConfiguration.builder() + .readTimeout(30_000) + .build(); + + assertEquals(30_000, config.getReadTimeout()); + } + + @Test + public void builderSetsProxy() { + HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080).build(); + HttpClientConfiguration config = HttpClientConfiguration.builder() + .proxy(proxy) + .build(); + + assertNotNull(config.getProxy()); + assertEquals("proxy.example.com", config.getProxy().getHost()); + assertEquals(8080, config.getProxy().getPort()); + } + + @Test + public void builderSetsCertificatePinningConfiguration() { + CertificatePinningConfiguration certConfig = CertificatePinningConfiguration.builder() + .addPin("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") + .build(); + HttpClientConfiguration config = HttpClientConfiguration.builder() + .certificatePinningConfiguration(certConfig) + .build(); + + assertNotNull(config.getCertificatePinningConfiguration()); + } + + @Test + public void builderSetsDevelopmentSslConfig() { + // DevelopmentSslConfig requires non-null args; just verify null default + HttpClientConfiguration config = HttpClientConfiguration.builder().build(); + assertNull(config.getDevelopmentSslConfig()); + } + + @Test + public void builderSetsProxyAuthenticator() { + HttpClientConfiguration config = HttpClientConfiguration.builder().build(); + assertNull(config.getProxyAuthenticator()); + } + + @Test + public void defaultValuesAreZeroAndNull() { + HttpClientConfiguration config = HttpClientConfiguration.builder().build(); + + assertEquals(0, config.getConnectionTimeout()); + assertEquals(0, config.getReadTimeout()); + assertNull(config.getProxy()); + assertNull(config.getCertificatePinningConfiguration()); + assertNull(config.getDevelopmentSslConfig()); + assertNull(config.getProxyAuthenticator()); + } + + @Test + public void builderSetsAllFields() { + HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080).build(); + CertificatePinningConfiguration certConfig = CertificatePinningConfiguration.builder() + .addPin("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") + .build(); + + HttpClientConfiguration config = HttpClientConfiguration.builder() + .connectionTimeout(10_000) + .readTimeout(20_000) + .proxy(proxy) + .certificatePinningConfiguration(certConfig) + .build(); + + assertEquals(10_000, config.getConnectionTimeout()); + assertEquals(20_000, config.getReadTimeout()); + assertNotNull(config.getProxy()); + assertNotNull(config.getCertificatePinningConfiguration()); + } +} From 153daedaa3af5a14606000da25e1e6a2ba781727 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Feb 2026 17:24:22 -0300 Subject: [PATCH 04/14] Fix instrumented test --- .../androidTest/java/tests/integration/ProxyFactoryTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/androidTest/java/tests/integration/ProxyFactoryTest.java b/main/src/androidTest/java/tests/integration/ProxyFactoryTest.java index 28f80e19d..cfbe02240 100644 --- a/main/src/androidTest/java/tests/integration/ProxyFactoryTest.java +++ b/main/src/androidTest/java/tests/integration/ProxyFactoryTest.java @@ -27,7 +27,7 @@ import io.split.android.client.SplitFactoryBuilder; import io.split.android.client.api.Key; import io.split.android.client.events.SplitEvent; -import io.split.android.client.network.SplitAuthenticatedRequest; +import io.split.android.client.network.AuthenticatedRequest; import io.split.android.client.network.SplitAuthenticator; import io.split.android.client.service.impressions.ImpressionsMode; import io.split.android.client.service.synchronizer.ThreadUtils; @@ -248,7 +248,7 @@ public MockResponse dispatch(RecordedRequest request) { .serviceEndpoints(endpoints) .proxyAuthenticator(new SplitAuthenticator() { @Override - public SplitAuthenticatedRequest authenticate(@NonNull SplitAuthenticatedRequest request) { + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { request.setHeader("Proxy-Authorization", "Bearer 1234567890"); return request; } From 8d26f928911d5179954ac026bc0e66fe2f20550d Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Feb 2026 16:43:29 -0300 Subject: [PATCH 05/14] Populate HTTP module --- http/build.gradle | 2 +- .../android/client/network/Base64Encoder.java | 0 .../client/network/BaseHttpResponse.java | 0 .../client/network/BaseHttpResponseImpl.java | 0 .../client/network/CertificateChecker.java | 0 .../network/CertificateCheckerImpl.java | 0 .../android/client/network/ChainCleaner.java | 0 .../client/network/ChainCleanerImpl.java | 0 .../client/network/DefaultBase64Encoder.java | 22 ++++++++++ .../android/client/network/HttpClient.java | 0 .../client/network/HttpClientImpl.java | 32 ++++---------- .../android/client/network/HttpException.java | 0 .../android/client/network/HttpHeaders.java | 43 +++++++++++++++++++ .../android/client/network/HttpMethod.java | 0 .../network/HttpOverTunnelExecutor.java | 0 .../client/network/HttpQueryParameters.java | 29 +++++++++++++ .../android/client/network/HttpRequest.java | 0 .../client/network/HttpRequestHelper.java | 5 +-- .../client/network/HttpRequestImpl.java | 21 +++++---- .../android/client/network/HttpResponse.java | 0 .../HttpResponseConnectionAdapter.java | 0 .../client/network/HttpResponseImpl.java | 0 .../client/network/HttpStreamRequest.java | 0 .../client/network/HttpStreamRequestImpl.java | 11 +++-- .../client/network/HttpStreamResponse.java | 0 .../network/HttpStreamResponseImpl.java | 0 .../client/network/PercentEscaper.java | 8 ++-- .../network/ProxyCacertConnectionHandler.java | 0 .../ProxySslSocketFactoryProvider.java | 0 .../ProxySslSocketFactoryProviderImpl.java | 4 +- .../client/network/RawHttpResponseParser.java | 0 .../network/SplitAuthenticatedRequest.java | 0 .../network/SplitBasicAuthenticator.java | 0 .../SplitUrlConnectionAuthenticator.java | 0 .../network/SslProxyTunnelEstablisher.java | 0 .../network/Tls12OnlySocketFactory.java | 0 .../android/client/network/TlsUpdater.java | 14 ++++++ .../client/network/TrustManagerProvider.java | 0 .../android/client/network/URIBuilder.java | 24 +++++------ .../client/network/UnicodeEscaper.java | 6 +-- .../android/client/network/UrlEscapers.java | 0 .../android/client/network/UrlSanitizer.java | 0 .../client/network/UrlSanitizerImpl.java | 0 .../network/CertificateCheckerImplTest.java | 0 .../client/network/ChainCleanerImplTest.java | 0 .../network/DefaultBase64EncoderTest.java | 36 ++++++++-------- .../HttpClientTunnellingProxyTest.java | 0 .../network/HttpOverTunnelExecutorTest.java | 0 .../client/network/HttpRequestHelperTest.java | 0 .../HttpResponseConnectionAdapterTest.java | 0 .../network/HttpStreamResponseTest.java | 0 ...ProxySslSocketFactoryProviderImplTest.java | 0 .../network/RawHttpResponseParserTest.java | 0 .../network/SplitAuthenticatorTest.java | 0 .../network/SplitBasicAuthenticatorTest.java | 0 .../SplitUrlConnectionAuthenticatorTest.java | 0 .../SslProxyTunnelEstablisherTest.java | 0 .../network/TrustManagerProviderTest.java | 0 .../android/client/SplitFactoryImpl.java | 3 +- .../client/network/DefaultBase64Encoder.java | 16 ------- .../network/LegacyTlsUpdaterAdapter.java | 29 +++++++++++++ .../client/network/HttpClientTest.java | 43 +++++++------------ 62 files changed, 223 insertions(+), 125 deletions(-) rename {main => http}/src/main/java/io/split/android/client/network/Base64Encoder.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/BaseHttpResponse.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/CertificateChecker.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/ChainCleaner.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/ChainCleanerImpl.java (100%) create mode 100644 http/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java rename {main => http}/src/main/java/io/split/android/client/network/HttpClient.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/HttpClientImpl.java (94%) rename {main => http}/src/main/java/io/split/android/client/network/HttpException.java (100%) create mode 100644 http/src/main/java/io/split/android/client/network/HttpHeaders.java rename {main => http}/src/main/java/io/split/android/client/network/HttpMethod.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java (100%) create mode 100644 http/src/main/java/io/split/android/client/network/HttpQueryParameters.java rename {main => http}/src/main/java/io/split/android/client/network/HttpRequest.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/HttpRequestHelper.java (96%) rename {main => http}/src/main/java/io/split/android/client/network/HttpRequestImpl.java (94%) rename {main => http}/src/main/java/io/split/android/client/network/HttpResponse.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/HttpResponseImpl.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/HttpStreamRequest.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java (96%) rename {main => http}/src/main/java/io/split/android/client/network/HttpStreamResponse.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/PercentEscaper.java (97%) rename {main => http}/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java (99%) rename {main => http}/src/main/java/io/split/android/client/network/RawHttpResponseParser.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java (100%) create mode 100644 http/src/main/java/io/split/android/client/network/TlsUpdater.java rename {main => http}/src/main/java/io/split/android/client/network/TrustManagerProvider.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/URIBuilder.java (76%) rename {main => http}/src/main/java/io/split/android/client/network/UnicodeEscaper.java (98%) rename {main => http}/src/main/java/io/split/android/client/network/UrlEscapers.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/UrlSanitizer.java (100%) rename {main => http}/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java (57%) rename {main => http}/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java (100%) rename {main => http}/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java (100%) delete mode 100644 main/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java create mode 100644 main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java diff --git a/http/build.gradle b/http/build.gradle index 85ef7d707..f613652b7 100644 --- a/http/build.gradle +++ b/http/build.gradle @@ -16,7 +16,7 @@ android { dependencies { implementation libs.annotation implementation project(':logger') - implementation project(':http-domain') + api project(':http-domain') testImplementation libs.junit4 testImplementation libs.mockitoCore diff --git a/main/src/main/java/io/split/android/client/network/Base64Encoder.java b/http/src/main/java/io/split/android/client/network/Base64Encoder.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/Base64Encoder.java rename to http/src/main/java/io/split/android/client/network/Base64Encoder.java diff --git a/main/src/main/java/io/split/android/client/network/BaseHttpResponse.java b/http/src/main/java/io/split/android/client/network/BaseHttpResponse.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/BaseHttpResponse.java rename to http/src/main/java/io/split/android/client/network/BaseHttpResponse.java diff --git a/main/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java b/http/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java rename to http/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java diff --git a/main/src/main/java/io/split/android/client/network/CertificateChecker.java b/http/src/main/java/io/split/android/client/network/CertificateChecker.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/CertificateChecker.java rename to http/src/main/java/io/split/android/client/network/CertificateChecker.java diff --git a/main/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java b/http/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java rename to http/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java diff --git a/main/src/main/java/io/split/android/client/network/ChainCleaner.java b/http/src/main/java/io/split/android/client/network/ChainCleaner.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ChainCleaner.java rename to http/src/main/java/io/split/android/client/network/ChainCleaner.java diff --git a/main/src/main/java/io/split/android/client/network/ChainCleanerImpl.java b/http/src/main/java/io/split/android/client/network/ChainCleanerImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ChainCleanerImpl.java rename to http/src/main/java/io/split/android/client/network/ChainCleanerImpl.java diff --git a/http/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java b/http/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java new file mode 100644 index 000000000..4106c7784 --- /dev/null +++ b/http/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java @@ -0,0 +1,22 @@ +package io.split.android.client.network; + +import android.util.Base64; + +class DefaultBase64Encoder implements Base64Encoder { + + @Override + public String encode(String value) { + if (value == null) { + return null; + } + return Base64.encodeToString(value.getBytes(), Base64.NO_WRAP); + } + + @Override + public String encode(byte[] bytes) { + if (bytes == null) { + return null; + } + return Base64.encodeToString(bytes, Base64.NO_WRAP); + } +} diff --git a/main/src/main/java/io/split/android/client/network/HttpClient.java b/http/src/main/java/io/split/android/client/network/HttpClient.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpClient.java rename to http/src/main/java/io/split/android/client/network/HttpClient.java diff --git a/main/src/main/java/io/split/android/client/network/HttpClientImpl.java b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java similarity index 94% rename from main/src/main/java/io/split/android/client/network/HttpClientImpl.java rename to http/src/main/java/io/split/android/client/network/HttpClientImpl.java index f41271796..d846462b7 100644 --- a/main/src/main/java/io/split/android/client/network/HttpClientImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java @@ -1,7 +1,5 @@ package io.split.android.client.network; -import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -20,8 +18,6 @@ import javax.net.ssl.SSLSocketFactory; -import io.split.android.client.utils.Base64Util; -import io.split.android.client.utils.Utils; import io.split.android.client.utils.logger.Logger; public class HttpClientImpl implements HttpClient { @@ -180,7 +176,7 @@ private SplitUrlConnectionAuthenticator initializeProxyAuthenticator(HttpProxy p return null; } else if (proxyAuthenticator != null) { return new SplitUrlConnectionAuthenticator(proxyAuthenticator); - } else if (!Utils.isNullOrEmpty(proxy.getUsername())) { + } else if (proxy.getUsername() != null && !proxy.getUsername().isEmpty()) { return createBasicAuthenticator(proxy.getUsername(), proxy.getPassword()); } @@ -188,18 +184,7 @@ private SplitUrlConnectionAuthenticator initializeProxyAuthenticator(HttpProxy p } private static SplitUrlConnectionAuthenticator createBasicAuthenticator(String username, String password) { - return new SplitUrlConnectionAuthenticator(new SplitBasicAuthenticator(username, password, new Base64Encoder() { - - @Override - public String encode(String value) { - return Base64Util.encode(value); - } - - @Override - public String encode(byte[] bytes) { - return Base64Util.encode(bytes); - } - })); + return new SplitUrlConnectionAuthenticator(new SplitBasicAuthenticator(username, password, new DefaultBase64Encoder())); } public static class Builder { @@ -211,14 +196,15 @@ public static class Builder { private long mConnectionTimeout = -1; private DevelopmentSslConfig mDevelopmentSslConfig = null; private SSLSocketFactory mSslSocketFactory = null; - private Context mHostAppContext; + @Nullable + private TlsUpdater mTlsUpdater; private UrlSanitizer mUrlSanitizer; private CertificatePinningConfiguration mCertificatePinningConfiguration; private CertificateChecker mCertificateChecker; private Base64Decoder mBase64Decoder = new DefaultBase64Decoder(); - public Builder setContext(Context context) { - mHostAppContext = context; + public Builder setTlsUpdater(@Nullable TlsUpdater tlsUpdater) { + mTlsUpdater = tlsUpdater; return this; } @@ -279,13 +265,13 @@ Builder setBase64Decoder(Base64Decoder base64Decoder) { public HttpClient build() { if (mDevelopmentSslConfig == null) { - if (LegacyTlsUpdater.couldBeOld()) { - LegacyTlsUpdater.update(mHostAppContext); + if (mTlsUpdater != null && mTlsUpdater.couldBeOld()) { + mTlsUpdater.update(); } if (mProxy != null) { mSslSocketFactory = createSslSocketFactoryFromProxy(mProxy); - } else if (LegacyTlsUpdater.couldBeOld()) { + } else if (mTlsUpdater != null && mTlsUpdater.couldBeOld()) { try { mSslSocketFactory = new Tls12OnlySocketFactory(); } catch (NoSuchAlgorithmException | KeyManagementException e) { diff --git a/main/src/main/java/io/split/android/client/network/HttpException.java b/http/src/main/java/io/split/android/client/network/HttpException.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpException.java rename to http/src/main/java/io/split/android/client/network/HttpException.java diff --git a/http/src/main/java/io/split/android/client/network/HttpHeaders.java b/http/src/main/java/io/split/android/client/network/HttpHeaders.java new file mode 100644 index 000000000..f4d62cd89 --- /dev/null +++ b/http/src/main/java/io/split/android/client/network/HttpHeaders.java @@ -0,0 +1,43 @@ +package io.split.android.client.network; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public class HttpHeaders { + + private final Map mHeaders; + + public HttpHeaders() { + mHeaders = new LinkedHashMap<>(); + } + + public HttpHeaders(@NonNull Map headers) { + mHeaders = new LinkedHashMap<>(headers); + } + + public void set(@NonNull String name, @NonNull String value) { + mHeaders.put(name, value); + } + + @Nullable + public String get(@NonNull String name) { + return mHeaders.get(name); + } + + @NonNull + public Map asMap() { + return Collections.unmodifiableMap(mHeaders); + } + + public boolean isEmpty() { + return mHeaders.isEmpty(); + } + + public int size() { + return mHeaders.size(); + } +} diff --git a/main/src/main/java/io/split/android/client/network/HttpMethod.java b/http/src/main/java/io/split/android/client/network/HttpMethod.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpMethod.java rename to http/src/main/java/io/split/android/client/network/HttpMethod.java diff --git a/main/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java b/http/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java rename to http/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java diff --git a/http/src/main/java/io/split/android/client/network/HttpQueryParameters.java b/http/src/main/java/io/split/android/client/network/HttpQueryParameters.java new file mode 100644 index 000000000..f2edc62bf --- /dev/null +++ b/http/src/main/java/io/split/android/client/network/HttpQueryParameters.java @@ -0,0 +1,29 @@ +package io.split.android.client.network; + +import androidx.annotation.NonNull; + +import java.util.AbstractMap; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +public class HttpQueryParameters { + + private final Set> mParams = new LinkedHashSet<>(); + + @NonNull + public HttpQueryParameters add(@NonNull String key, @NonNull String value) { + mParams.add(new AbstractMap.SimpleEntry<>(key, value)); + return this; + } + + @NonNull + public Set> entries() { + return Collections.unmodifiableSet(mParams); + } + + public boolean isEmpty() { + return mParams.isEmpty(); + } +} diff --git a/main/src/main/java/io/split/android/client/network/HttpRequest.java b/http/src/main/java/io/split/android/client/network/HttpRequest.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpRequest.java rename to http/src/main/java/io/split/android/client/network/HttpRequest.java diff --git a/main/src/main/java/io/split/android/client/network/HttpRequestHelper.java b/http/src/main/java/io/split/android/client/network/HttpRequestHelper.java similarity index 96% rename from main/src/main/java/io/split/android/client/network/HttpRequestHelper.java rename to http/src/main/java/io/split/android/client/network/HttpRequestHelper.java index 4688f00b7..7cefcabaf 100644 --- a/main/src/main/java/io/split/android/client/network/HttpRequestHelper.java +++ b/http/src/main/java/io/split/android/client/network/HttpRequestHelper.java @@ -1,6 +1,5 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.getAsInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -92,11 +91,11 @@ private static HttpURLConnection openConnection(@Nullable Proxy proxy, static void applyTimeouts(long readTimeout, long connectionTimeout, HttpURLConnection connection) { if (readTimeout > 0) { - connection.setReadTimeout(getAsInt(readTimeout)); + connection.setReadTimeout((int) readTimeout); } if (connectionTimeout > 0) { - connection.setConnectTimeout(getAsInt(connectionTimeout)); + connection.setConnectTimeout((int) connectionTimeout); } } diff --git a/main/src/main/java/io/split/android/client/network/HttpRequestImpl.java b/http/src/main/java/io/split/android/client/network/HttpRequestImpl.java similarity index 94% rename from main/src/main/java/io/split/android/client/network/HttpRequestImpl.java rename to http/src/main/java/io/split/android/client/network/HttpRequestImpl.java index 1f2a0c402..669dd8598 100644 --- a/main/src/main/java/io/split/android/client/network/HttpRequestImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpRequestImpl.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; import static io.split.android.client.network.HttpRequestHelper.applySslConfig; import static io.split.android.client.network.HttpRequestHelper.applyTimeouts; @@ -29,7 +29,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocketFactory; -import io.split.android.client.service.http.HttpStatus; import io.split.android.client.utils.logger.Logger; public class HttpRequestImpl implements HttpRequest { @@ -37,6 +36,12 @@ public class HttpRequestImpl implements HttpRequest { public static final String CONTENT_TYPE = "Content-Type"; public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json; charset=utf-8"; + /** + * Non-retryable status code for SSL errors. + * Mirrors HttpStatus.INTERNAL_NON_RETRYABLE from :main. + */ + static final int NON_RETRYABLE_STATUS_CODE = 9009; + private final URI mUri; private final String mBody; private final HttpMethod mHttpMethod; @@ -73,11 +78,11 @@ public class HttpRequestImpl implements HttpRequest { @Nullable SSLSocketFactory sslSocketFactory, @NonNull UrlSanitizer urlSanitizer, @Nullable CertificateChecker certificateChecker) { - mUri = checkNotNull(uri); - mHttpMethod = checkNotNull(httpMethod); + mUri = requireNonNull(uri); + mHttpMethod = requireNonNull(httpMethod); mBody = body; - mUrlSanitizer = checkNotNull(urlSanitizer); - mHeaders = new HashMap<>(checkNotNull(headers)); + mUrlSanitizer = requireNonNull(urlSanitizer); + mHeaders = new HashMap<>(requireNonNull(headers)); mProxy = proxy; mHttpProxy = httpProxy; mProxyAuthenticator = proxyAuthenticator; @@ -119,7 +124,7 @@ private HttpResponse getRequest(AtomicBoolean wasRetried) throws HttpException { } catch (ProtocolException e) { throw new HttpException("Http method not allowed: " + e.getLocalizedMessage()); } catch (SSLPeerUnverifiedException e) { - throw new HttpException("SSL Peer Unverified: " + e.getLocalizedMessage(), HttpStatus.INTERNAL_NON_RETRYABLE.getCode()); + throw new HttpException("SSL Peer Unverified: " + e.getLocalizedMessage(), NON_RETRYABLE_STATUS_CODE); } catch (IOException e) { throw new HttpException("Something happened while retrieving data: " + e.getLocalizedMessage()); } finally { @@ -146,7 +151,7 @@ private HttpResponse postRequest(AtomicBoolean wasRetried) throws HttpException response = handleProxyAuthentication(response, false, wasRetried); } } catch (SSLPeerUnverifiedException e) { - throw new HttpException("SSL Peer Unverified: " + e.getLocalizedMessage(), HttpStatus.INTERNAL_NON_RETRYABLE.getCode()); + throw new HttpException("SSL Peer Unverified: " + e.getLocalizedMessage(), NON_RETRYABLE_STATUS_CODE); } catch (IOException e) { throw new HttpException("Something happened while posting data: " + e.getLocalizedMessage()); } finally { diff --git a/main/src/main/java/io/split/android/client/network/HttpResponse.java b/http/src/main/java/io/split/android/client/network/HttpResponse.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpResponse.java rename to http/src/main/java/io/split/android/client/network/HttpResponse.java diff --git a/main/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java b/http/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java rename to http/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java diff --git a/main/src/main/java/io/split/android/client/network/HttpResponseImpl.java b/http/src/main/java/io/split/android/client/network/HttpResponseImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpResponseImpl.java rename to http/src/main/java/io/split/android/client/network/HttpResponseImpl.java diff --git a/main/src/main/java/io/split/android/client/network/HttpStreamRequest.java b/http/src/main/java/io/split/android/client/network/HttpStreamRequest.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpStreamRequest.java rename to http/src/main/java/io/split/android/client/network/HttpStreamRequest.java diff --git a/main/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java b/http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java similarity index 96% rename from main/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java rename to http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java index d6f48b8d9..c8573e235 100644 --- a/main/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java @@ -2,7 +2,7 @@ import static io.split.android.client.network.HttpRequestHelper.checkPins; import static io.split.android.client.network.HttpRequestHelper.createConnection; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; import static io.split.android.client.network.HttpRequestHelper.applySslConfig; import static io.split.android.client.network.HttpRequestHelper.applyTimeouts; @@ -28,7 +28,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocketFactory; -import io.split.android.client.service.http.HttpStatus; import io.split.android.client.utils.logger.Logger; public class HttpStreamRequestImpl implements HttpStreamRequest { @@ -72,11 +71,11 @@ public class HttpStreamRequestImpl implements HttpStreamRequest { @Nullable HttpProxy httpProxy, @Nullable ProxyCredentialsProvider proxyCredentialsProvider, @Nullable ProxyCacertConnectionHandler proxyCacertConnectionHandler) { - mUri = checkNotNull(uri); + mUri = requireNonNull(uri); mHttpMethod = HttpMethod.GET; mProxy = proxy; - mUrlSanitizer = checkNotNull(urlSanitizer); - mHeaders = new HashMap<>(checkNotNull(headers)); + mUrlSanitizer = requireNonNull(urlSanitizer); + mHeaders = new HashMap<>(requireNonNull(headers)); mProxyAuthenticator = proxyAuthenticator; mConnectionTimeout = connectionTimeout; mDevelopmentSslConfig = developmentSslConfig; @@ -141,7 +140,7 @@ private HttpStreamResponse getRequest() throws HttpException, IOException { throw new HttpException("Http method not allowed: " + e.getLocalizedMessage()); } catch (SSLPeerUnverifiedException e) { disconnect(); - throw new HttpException("SSL peer not verified: " + e.getLocalizedMessage(), HttpStatus.INTERNAL_NON_RETRYABLE.getCode()); + throw new HttpException("SSL peer not verified: " + e.getLocalizedMessage(), HttpRequestImpl.NON_RETRYABLE_STATUS_CODE); } catch (SocketException e) { disconnect(); // Let socket-related IOExceptions pass through unwrapped for consistent error handling diff --git a/main/src/main/java/io/split/android/client/network/HttpStreamResponse.java b/http/src/main/java/io/split/android/client/network/HttpStreamResponse.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpStreamResponse.java rename to http/src/main/java/io/split/android/client/network/HttpStreamResponse.java diff --git a/main/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java b/http/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java rename to http/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java diff --git a/main/src/main/java/io/split/android/client/network/PercentEscaper.java b/http/src/main/java/io/split/android/client/network/PercentEscaper.java similarity index 97% rename from main/src/main/java/io/split/android/client/network/PercentEscaper.java rename to http/src/main/java/io/split/android/client/network/PercentEscaper.java index b61bed710..9f99ceb8e 100644 --- a/main/src/main/java/io/split/android/client/network/PercentEscaper.java +++ b/http/src/main/java/io/split/android/client/network/PercentEscaper.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; /** * Based on Guava PercentEscaper @@ -37,7 +37,7 @@ final class PercentEscaper extends UnicodeEscaper { * @throws IllegalArgumentException if any of the parameters were invalid */ public PercentEscaper(String safeChars, boolean plusForSpace) { - checkNotNull(safeChars); // eager for GWT. + requireNonNull(safeChars); // eager for GWT. // Avoid any misunderstandings about the behavior of this escaper if (safeChars.matches(".*[0-9A-Za-z].*")) { throw new IllegalArgumentException( @@ -78,7 +78,7 @@ private static boolean[] createSafeOctets(String safeChars) { */ @Override protected int nextEscapeIndex(CharSequence csq, int index, int end) { - checkNotNull(csq); + requireNonNull(csq); for (; index < end; index++) { char c = csq.charAt(index); if (c >= safeOctets.length || !safeOctets[c]) { @@ -94,7 +94,7 @@ protected int nextEscapeIndex(CharSequence csq, int index, int end) { */ @Override public String escape(String s) { - checkNotNull(s); + requireNonNull(s); int slen = s.length(); for (int index = 0; index < slen; index++) { char c = s.charAt(index); diff --git a/main/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java b/http/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java rename to http/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java diff --git a/main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java b/http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java rename to http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java diff --git a/main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java b/http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java similarity index 99% rename from main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java rename to http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java index 49a84c134..8978258cf 100644 --- a/main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java +++ b/http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,7 +43,7 @@ class ProxySslSocketFactoryProviderImpl implements ProxySslSocketFactoryProvider } ProxySslSocketFactoryProviderImpl(@NonNull Base64Decoder base64Decoder) { - mBase64Decoder = checkNotNull(base64Decoder); + mBase64Decoder = requireNonNull(base64Decoder); } @Override diff --git a/main/src/main/java/io/split/android/client/network/RawHttpResponseParser.java b/http/src/main/java/io/split/android/client/network/RawHttpResponseParser.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/RawHttpResponseParser.java rename to http/src/main/java/io/split/android/client/network/RawHttpResponseParser.java diff --git a/main/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java b/http/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java rename to http/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java diff --git a/main/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java b/http/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java rename to http/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java diff --git a/main/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java b/http/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java rename to http/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java diff --git a/main/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java b/http/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java rename to http/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java diff --git a/main/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java b/http/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java rename to http/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java diff --git a/http/src/main/java/io/split/android/client/network/TlsUpdater.java b/http/src/main/java/io/split/android/client/network/TlsUpdater.java new file mode 100644 index 000000000..4fff431f4 --- /dev/null +++ b/http/src/main/java/io/split/android/client/network/TlsUpdater.java @@ -0,0 +1,14 @@ +package io.split.android.client.network; + +public interface TlsUpdater { + + /** + * Return true if the device may need a TLS update. + */ + boolean couldBeOld(); + + /** + * Perform the TLS update. + */ + void update(); +} diff --git a/main/src/main/java/io/split/android/client/network/TrustManagerProvider.java b/http/src/main/java/io/split/android/client/network/TrustManagerProvider.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/TrustManagerProvider.java rename to http/src/main/java/io/split/android/client/network/TrustManagerProvider.java diff --git a/main/src/main/java/io/split/android/client/network/URIBuilder.java b/http/src/main/java/io/split/android/client/network/URIBuilder.java similarity index 76% rename from main/src/main/java/io/split/android/client/network/URIBuilder.java rename to http/src/main/java/io/split/android/client/network/URIBuilder.java index e5aacc0e5..3611aaf27 100644 --- a/main/src/main/java/io/split/android/client/network/URIBuilder.java +++ b/http/src/main/java/io/split/android/client/network/URIBuilder.java @@ -1,25 +1,23 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; import androidx.annotation.NonNull; -import androidx.core.util.Pair; - import java.net.URI; import java.net.URISyntaxException; +import java.util.AbstractMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; -import io.split.android.client.utils.Utils; - public class URIBuilder { private final URI mRootURI; - private final Set> mParams; + private final Set> mParams; private String mPath; private String mQueryString; public URIBuilder(@NonNull URI rootURI, String path) { - mRootURI = checkNotNull(rootURI); + mRootURI = requireNonNull(rootURI); String rootPath = mRootURI.getRawPath(); if (path != null && rootPath != null) { mPath = String.format("%s/%s", rootPath, path); @@ -40,13 +38,13 @@ public URIBuilder(@NonNull URI rootURI) { public URIBuilder addParameter(@NonNull String param, @NonNull String value) { if (param != null && value != null) { - mParams.add(new Pair<>(param, value)); + mParams.add(new AbstractMap.SimpleEntry<>(param, value)); } return this; } public URIBuilder defaultQueryString(@NonNull String queryString) { - if (!Utils.isNullOrEmpty(queryString)) { + if (queryString != null && !queryString.isEmpty()) { mQueryString = queryString; } return this; @@ -57,14 +55,14 @@ public URI build() throws URISyntaxException { String params = null; if (mParams.size() > 0) { StringBuilder query = new StringBuilder(); - for (Pair param : mParams) { - query.append(param.first).append("=").append(param.second).append("&"); + for (Map.Entry param : mParams) { + query.append(param.getKey()).append("=").append(param.getValue()).append("&"); } params = query.substring(0, query.length() - 1); } - if (!Utils.isNullOrEmpty(mQueryString)) { - if (!Utils.isNullOrEmpty(params)) { + if (mQueryString != null && !mQueryString.isEmpty()) { + if (params != null && !params.isEmpty()) { if (!"&".equals(mQueryString.substring(0, 1))) { params = params + "&"; } diff --git a/main/src/main/java/io/split/android/client/network/UnicodeEscaper.java b/http/src/main/java/io/split/android/client/network/UnicodeEscaper.java similarity index 98% rename from main/src/main/java/io/split/android/client/network/UnicodeEscaper.java rename to http/src/main/java/io/split/android/client/network/UnicodeEscaper.java index 4ed19ab54..7f3f6fd67 100644 --- a/main/src/main/java/io/split/android/client/network/UnicodeEscaper.java +++ b/http/src/main/java/io/split/android/client/network/UnicodeEscaper.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; /** * Based on Guava UnicodeEscaper @@ -14,7 +14,7 @@ protected UnicodeEscaper() {} protected abstract char[] escape(int cp); public String escape(String string) { - checkNotNull(string); + requireNonNull(string); int end = string.length(); int index = nextEscapeIndex(string, 0, end); return index == end ? string : escapeSlow(string, index); @@ -136,7 +136,7 @@ protected final String escapeSlow(String s, int index) { * surrogate character at the end of the sequence */ protected static int codePointAt(CharSequence seq, int index, int end) { - checkNotNull(seq); + requireNonNull(seq); if (index < end) { char c1 = seq.charAt(index++); if (c1 < Character.MIN_HIGH_SURROGATE || c1 > Character.MAX_LOW_SURROGATE) { diff --git a/main/src/main/java/io/split/android/client/network/UrlEscapers.java b/http/src/main/java/io/split/android/client/network/UrlEscapers.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/UrlEscapers.java rename to http/src/main/java/io/split/android/client/network/UrlEscapers.java diff --git a/main/src/main/java/io/split/android/client/network/UrlSanitizer.java b/http/src/main/java/io/split/android/client/network/UrlSanitizer.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/UrlSanitizer.java rename to http/src/main/java/io/split/android/client/network/UrlSanitizer.java diff --git a/main/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java b/http/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java rename to http/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java diff --git a/main/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java b/http/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java rename to http/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java diff --git a/main/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java b/http/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java rename to http/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java diff --git a/main/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java b/http/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java similarity index 57% rename from main/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java rename to http/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java index 738300ce7..ddbbc5078 100644 --- a/main/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java +++ b/http/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java @@ -2,6 +2,8 @@ import static org.mockito.Mockito.mockStatic; +import android.util.Base64; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -9,39 +11,37 @@ import java.nio.charset.StandardCharsets; -import io.split.android.client.utils.Base64Util; - public class DefaultBase64EncoderTest { - + private DefaultBase64Encoder encoder; - private MockedStatic mockedBase64Util; - + private MockedStatic mockedBase64; + @Before public void setUp() { encoder = new DefaultBase64Encoder(); - mockedBase64Util = mockStatic(Base64Util.class); + mockedBase64 = mockStatic(Base64.class); } - + @After public void tearDown() { - mockedBase64Util.close(); + mockedBase64.close(); } - + @Test - public void encodeStringUsesBase64Util() { + public void encodeStringUsesAndroidBase64() { String input = "test string"; - + encoder.encode(input); - - mockedBase64Util.verify(() -> Base64Util.encode(input)); + + mockedBase64.verify(() -> Base64.encodeToString(input.getBytes(), Base64.NO_WRAP)); } - + @Test - public void encodeByteArrayUsesBase64Util() { + public void encodeByteArrayUsesAndroidBase64() { byte[] input = "test bytes".getBytes(StandardCharsets.UTF_8); - + encoder.encode(input); - - mockedBase64Util.verify(() -> Base64Util.encode(input)); + + mockedBase64.verify(() -> Base64.encodeToString(input, Base64.NO_WRAP)); } } diff --git a/main/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java b/http/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java rename to http/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java diff --git a/main/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java b/http/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java rename to http/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java diff --git a/main/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java b/http/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java rename to http/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java diff --git a/main/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java b/http/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java rename to http/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java diff --git a/main/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java b/http/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java rename to http/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java diff --git a/main/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java b/http/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java rename to http/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java diff --git a/main/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java b/http/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java rename to http/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java diff --git a/main/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java b/http/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java rename to http/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java diff --git a/main/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java b/http/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java rename to http/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java diff --git a/main/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java b/http/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java rename to http/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java diff --git a/main/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java b/http/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java rename to http/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java diff --git a/main/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java b/http/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java rename to http/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java diff --git a/main/src/main/java/io/split/android/client/SplitFactoryImpl.java b/main/src/main/java/io/split/android/client/SplitFactoryImpl.java index 8bb12d71f..0016b6a76 100644 --- a/main/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/main/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -33,6 +33,7 @@ import io.split.android.client.lifecycle.SplitLifecycleManagerImpl; import io.split.android.client.network.HttpClient; import io.split.android.client.network.HttpClientImpl; +import io.split.android.client.network.LegacyTlsUpdaterAdapter; import io.split.android.client.service.CleanUpDatabaseTask; import io.split.android.client.service.SplitApiFacade; import io.split.android.client.service.executor.SplitSingleThreadTaskExecutor; @@ -385,7 +386,7 @@ private static HttpClient getHttpClient(@NonNull String apiToken, .setConnectionTimeout(config.connectionTimeout()) .setReadTimeout(config.readTimeout()) .setDevelopmentSslConfig(config.developmentSslConfig()) - .setContext(context) + .setTlsUpdater(new LegacyTlsUpdaterAdapter(context)) .setProxyAuthenticator(config.authenticator()); if (config.proxy() != null) { builder.setProxy(config.proxy()); diff --git a/main/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java b/main/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java deleted file mode 100644 index e1333ca80..000000000 --- a/main/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.split.android.client.network; - -import io.split.android.client.utils.Base64Util; - -class DefaultBase64Encoder implements Base64Encoder { - - @Override - public String encode(String value) { - return Base64Util.encode(value); - } - - @Override - public String encode(byte[] bytes) { - return Base64Util.encode(bytes); - } -} diff --git a/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java b/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java new file mode 100644 index 000000000..6a1bf7d42 --- /dev/null +++ b/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java @@ -0,0 +1,29 @@ +package io.split.android.client.network; + +import android.content.Context; + +import androidx.annotation.Nullable; + +/** + * Adapter that bridges the :http module's {@link TlsUpdater} SPI with the + * :main module's {@link LegacyTlsUpdater}. + */ +public class LegacyTlsUpdaterAdapter implements TlsUpdater { + + @Nullable + private final Context mContext; + + public LegacyTlsUpdaterAdapter(@Nullable Context context) { + mContext = context; + } + + @Override + public boolean couldBeOld() { + return LegacyTlsUpdater.couldBeOld(); + } + + @Override + public void update() { + LegacyTlsUpdater.update(mContext); + } +} diff --git a/main/src/test/java/io/split/android/client/network/HttpClientTest.java b/main/src/test/java/io/split/android/client/network/HttpClientTest.java index a2f2c8c86..6a4610127 100644 --- a/main/src/test/java/io/split/android/client/network/HttpClientTest.java +++ b/main/src/test/java/io/split/android/client/network/HttpClientTest.java @@ -10,8 +10,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.content.Context; - import androidx.annotation.NonNull; import com.google.gson.reflect.TypeToken; @@ -281,7 +279,6 @@ public MockResponse dispatch(RecordedRequest request) { mProxyServer.start(); HttpClient client = new HttpClientImpl.Builder() - .setContext(mock(Context.class)) .setUrlSanitizer(mUrlSanitizerMock) .setProxy(HttpProxy.newBuilder(mProxyServer.getHostName(), mProxyServer.getPort()).buildLegacy()) .build(); @@ -316,7 +313,6 @@ public MockResponse dispatch(RecordedRequest request) { mProxyServer.start(); HttpClient client = new HttpClientImpl.Builder() - .setContext(mock(Context.class)) .setUrlSanitizer(mUrlSanitizerMock) .setProxyAuthenticator(new SplitAuthenticator() { @Override @@ -371,7 +367,6 @@ public MockResponse dispatch(RecordedRequest request) { mProxyServer.start(); HttpClient client = new HttpClientImpl.Builder() - .setContext(mock(Context.class)) .setUrlSanitizer(mUrlSanitizerMock) .setProxyAuthenticator(new SplitAuthenticator() { @Override @@ -407,36 +402,30 @@ public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) @Test public void buildUsesTls12FactoryWhenLegacyAndNoProxy() throws Exception { - Context context = mock(Context.class); - - try (MockedStatic legacyMock = Mockito.mockStatic(LegacyTlsUpdater.class)) { - legacyMock.when(LegacyTlsUpdater::couldBeOld).thenReturn(true); + TlsUpdater tlsUpdater = mock(TlsUpdater.class); + when(tlsUpdater.couldBeOld()).thenReturn(true); - HttpClient legacyClient = new HttpClientImpl.Builder() - .setContext(context) - .setUrlSanitizer(mUrlSanitizerMock) - .build(); + HttpClient legacyClient = new HttpClientImpl.Builder() + .setTlsUpdater(tlsUpdater) + .setUrlSanitizer(mUrlSanitizerMock) + .build(); - legacyMock.verify(() -> LegacyTlsUpdater.update(context)); - assertTrue(((HttpClientImpl) legacyClient).getSslSocketFactory() instanceof Tls12OnlySocketFactory); - } + Mockito.verify(tlsUpdater).update(); + assertTrue(((HttpClientImpl) legacyClient).getSslSocketFactory() instanceof Tls12OnlySocketFactory); } @Test public void buildUsesDefaultSslWhenNotLegacyAndNoProxy() throws Exception { - Context context = mock(Context.class); + TlsUpdater tlsUpdater = mock(TlsUpdater.class); + when(tlsUpdater.couldBeOld()).thenReturn(false); - try (MockedStatic legacyMock = Mockito.mockStatic(LegacyTlsUpdater.class)) { - legacyMock.when(LegacyTlsUpdater::couldBeOld).thenReturn(false); - - HttpClient modernClient = new HttpClientImpl.Builder() - .setContext(context) - .setUrlSanitizer(mUrlSanitizerMock) - .build(); + HttpClient modernClient = new HttpClientImpl.Builder() + .setTlsUpdater(tlsUpdater) + .setUrlSanitizer(mUrlSanitizerMock) + .build(); - legacyMock.verify(() -> LegacyTlsUpdater.update(context), Mockito.never()); - assertNull(((HttpClientImpl) modernClient).getSslSocketFactory()); - } + Mockito.verify(tlsUpdater, Mockito.never()).update(); + assertNull(((HttpClientImpl) modernClient).getSslSocketFactory()); } From 3317ea865c968c450bf7ba742a8f1c86a8949d63 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Feb 2026 16:49:30 -0300 Subject: [PATCH 06/14] Update http README --- http/README.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/http/README.md b/http/README.md index 199f22805..8ece8d648 100644 --- a/http/README.md +++ b/http/README.md @@ -1,5 +1,58 @@ # HTTP module -This module provides the HTTP client implementation used internally by the Split SDK. +Internal HTTP client for the Split SDK. Not exposed to SDK consumers. -Includes request/response lifecycle, certificate pinning runtime, proxy tunnelling, and TLS configuration. Hidden from SDK consumers. +## Building an `HttpClient` + +Use `HttpClientImpl.Builder` to create an instance: + +```java +HttpClient client = new HttpClientImpl.Builder() + .setConnectionTimeout(15_000) + .setReadTimeout(15_000) + .setTlsUpdater(tlsUpdater) // optional – TlsUpdater SPI + .setProxy(httpProxy) // optional – proxy config from :http-domain + .setProxyAuthenticator(authenticator) // optional – SplitAuthenticator from :http-domain + .setCertificatePinningConfiguration(pinConfig) // optional – cert pins from :http-domain + .setDevelopmentSslConfig(devSslConfig) // optional – dev/test SSL overrides + .build(); +``` + +## Making requests + +```java +// Simple GET +HttpRequest req = client.request(uri, HttpMethod.GET); +HttpResponse resp = req.execute(); + +// POST with body and extra headers +HttpRequest post = client.request(uri, HttpMethod.POST, jsonBody, extraHeaders); +HttpResponse resp = post.execute(); + +// SSE streaming +HttpStreamRequest stream = client.streamRequest(uri); +HttpStreamResponse streamResp = stream.execute(); +``` + +## Global headers + +```java +client.setHeader("Authorization", "Bearer " + apiKey); +client.addHeaders(commonHeaders); + +// Streaming-specific headers (only applied to streamRequest calls) +client.setStreamingHeader("SplitSDKClientKey", clientKey); +``` + +## TLS on older devices + +Implement the `TlsUpdater` SPI and pass it to the builder. The client calls `couldBeOld()` to decide whether to force TLS 1.2 via `Tls12OnlySocketFactory`. + +## URI building + +```java +URI uri = new URIBuilder("https://sdk.split.io/api") + .addPath("splitChanges") + .addParameter("since", "-1") + .build(); +``` From 666e6666d3f4777fa27973f74432ab1b9b446db0 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Feb 2026 10:04:09 -0300 Subject: [PATCH 07/14] Remove unused classes; move helper to private method --- .../android/client/network/HttpHeaders.java | 43 ------------------- .../client/network/HttpQueryParameters.java | 29 ------------- .../client/network/HttpRequestHelper.java | 11 ++++- .../io/split/android/client/utils/Utils.java | 8 ---- 4 files changed, 9 insertions(+), 82 deletions(-) delete mode 100644 http/src/main/java/io/split/android/client/network/HttpHeaders.java delete mode 100644 http/src/main/java/io/split/android/client/network/HttpQueryParameters.java diff --git a/http/src/main/java/io/split/android/client/network/HttpHeaders.java b/http/src/main/java/io/split/android/client/network/HttpHeaders.java deleted file mode 100644 index f4d62cd89..000000000 --- a/http/src/main/java/io/split/android/client/network/HttpHeaders.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.split.android.client.network; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -public class HttpHeaders { - - private final Map mHeaders; - - public HttpHeaders() { - mHeaders = new LinkedHashMap<>(); - } - - public HttpHeaders(@NonNull Map headers) { - mHeaders = new LinkedHashMap<>(headers); - } - - public void set(@NonNull String name, @NonNull String value) { - mHeaders.put(name, value); - } - - @Nullable - public String get(@NonNull String name) { - return mHeaders.get(name); - } - - @NonNull - public Map asMap() { - return Collections.unmodifiableMap(mHeaders); - } - - public boolean isEmpty() { - return mHeaders.isEmpty(); - } - - public int size() { - return mHeaders.size(); - } -} diff --git a/http/src/main/java/io/split/android/client/network/HttpQueryParameters.java b/http/src/main/java/io/split/android/client/network/HttpQueryParameters.java deleted file mode 100644 index f2edc62bf..000000000 --- a/http/src/main/java/io/split/android/client/network/HttpQueryParameters.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.split.android.client.network; - -import androidx.annotation.NonNull; - -import java.util.AbstractMap; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -public class HttpQueryParameters { - - private final Set> mParams = new LinkedHashSet<>(); - - @NonNull - public HttpQueryParameters add(@NonNull String key, @NonNull String value) { - mParams.add(new AbstractMap.SimpleEntry<>(key, value)); - return this; - } - - @NonNull - public Set> entries() { - return Collections.unmodifiableSet(mParams); - } - - public boolean isEmpty() { - return mParams.isEmpty(); - } -} diff --git a/http/src/main/java/io/split/android/client/network/HttpRequestHelper.java b/http/src/main/java/io/split/android/client/network/HttpRequestHelper.java index 7cefcabaf..14e5a5b06 100644 --- a/http/src/main/java/io/split/android/client/network/HttpRequestHelper.java +++ b/http/src/main/java/io/split/android/client/network/HttpRequestHelper.java @@ -91,14 +91,21 @@ private static HttpURLConnection openConnection(@Nullable Proxy proxy, static void applyTimeouts(long readTimeout, long connectionTimeout, HttpURLConnection connection) { if (readTimeout > 0) { - connection.setReadTimeout((int) readTimeout); + connection.setReadTimeout(getAsInt(readTimeout)); } if (connectionTimeout > 0) { - connection.setConnectTimeout((int) connectionTimeout); + connection.setConnectTimeout(getAsInt(connectionTimeout)); } } + private static int getAsInt(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) value; + } + static void applySslConfig(SSLSocketFactory sslSocketFactory, DevelopmentSslConfig developmentSslConfig, HttpURLConnection connection) { if (sslSocketFactory != null) { if (connection instanceof HttpsURLConnection) { diff --git a/main/src/main/java/io/split/android/client/utils/Utils.java b/main/src/main/java/io/split/android/client/utils/Utils.java index 8341d776c..ff8e7d4eb 100644 --- a/main/src/main/java/io/split/android/client/utils/Utils.java +++ b/main/src/main/java/io/split/android/client/utils/Utils.java @@ -55,14 +55,6 @@ public static void checkArgument(boolean expression) { } } - public static int getAsInt(long value) { - if (value > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } else { - return (int) value; - } - } - public static List> partition(List list, int size) { if (list == null) { return new ArrayList<>(); From a92c45c35cf5314fef1e8846d3f349235bbf1554 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Feb 2026 10:06:46 -0300 Subject: [PATCH 08/14] Fix Javadoc --- .../split/android/client/network/LegacyTlsUpdaterAdapter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java b/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java index 6a1bf7d42..162fcee9b 100644 --- a/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java +++ b/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java @@ -5,8 +5,8 @@ import androidx.annotation.Nullable; /** - * Adapter that bridges the :http module's {@link TlsUpdater} SPI with the - * :main module's {@link LegacyTlsUpdater}. + * Adapter that bridges the :http module's {@link TlsUpdater} interface with the + * :main module's {@link LegacyTlsUpdater} class. */ public class LegacyTlsUpdaterAdapter implements TlsUpdater { From 021951f1567ccf30f2d58d76b6cf39f587f4a40f Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 9 Feb 2026 17:18:35 -0300 Subject: [PATCH 09/14] Polishing --- http-domain/README.md | 99 ++++++++++++++++++- .../android/client/network/Algorithm.java | 2 +- .../android/client/network/Base64Decoder.java | 2 +- .../network/CertificateCheckerHelper.java | 2 +- .../client/network/DefaultBase64Decoder.java | 2 +- .../android/client/network/PinEncoder.java | 2 +- .../client/network/PinEncoderImpl.java | 2 +- http/README.md | 92 ++++++++++++++--- .../client/network/HttpClientImpl.java | 32 ++++++ .../client/network/HttpRequestImpl.java | 2 +- .../client/network/HttpStreamRequestImpl.java | 2 +- .../network/HttpStreamResponseImpl.java | 2 +- .../android/client/SplitFactoryImpl.java | 26 ++--- 13 files changed, 230 insertions(+), 37 deletions(-) diff --git a/http-domain/README.md b/http-domain/README.md index 362956fbb..bad8159a8 100644 --- a/http-domain/README.md +++ b/http-domain/README.md @@ -1,5 +1,100 @@ # HTTP Domain module -This module contains public HTTP configuration contracts exposed to consumers of the Split SDK. +Public contracts and configuration types for the HTTP client. +These types are exposed to SDK consumers through the `:main` module's `api` dependency. -Includes certificate pinning configuration, proxy settings, SSL configuration, and authenticator interfaces. +## `HttpClientConfiguration` + +Bundles all HTTP client settings into a single object: + +```java +HttpClientConfiguration config = HttpClientConfiguration.builder() + .connectionTimeout(15_000) + .readTimeout(15_000) + .proxy(proxy) + .proxyAuthenticator(authenticator) + .certificatePinningConfiguration(pinConfig) + .developmentSslConfig(devSsl) + .build(); +``` + +## Proxy configuration + +### Basic auth + +```java +HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080) + .basicAuth("user", "pass") + .build(); +``` + +### mTLS with custom CA + +```java +HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8443) + .proxyCacert(caCertInputStream) + .mtls(clientCertInputStream, clientKeyInputStream) + .build(); +``` + +### Custom credentials provider + +```java +// Bearer token +HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080) + .credentialsProvider(() -> fetchBearerToken()) + .build(); + +// Basic credentials +HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080) + .credentialsProvider(new BasicCredentialsProvider() { + public String getUsername() { return "user"; } + public String getPassword() { return "pass"; } + }) + .build(); +``` + +## Custom proxy authenticator + +Implement `SplitAuthenticator` to handle proxy challenge/response flows: + +```java +SplitAuthenticator authenticator = new SplitAuthenticator() { + @Override + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { + request.setHeader("Proxy-Authorization", "Bearer " + getToken()); + return request; + } +}; +``` + +The `AuthenticatedRequest` gives access to existing headers and the request URL, so the authenticator can make decisions based on context. + +## Certificate pinning + +```java +CertificatePinningConfiguration pinConfig = CertificatePinningConfiguration.builder() + // Pin by hash (sha256 or sha1) + .addPin("sdk.split.io", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") + // Pin from a certificate file (derives hashes automatically) + .addPin("*.split.io", certInputStream) + // Optional: get notified on pin failures + .failureListener((host, certificateChain) -> { + Log.w("Split", "Pin failed for " + host + + ", chain size: " + certificateChain.size()); + }) + .build(); +``` + +Wildcard hosts are supported: `*.example.com` matches one subdomain, `**.example.com` matches any depth. + +## Development SSL overrides + +For test environments with self-signed certificates: + +```java +DevelopmentSslConfig devSsl = new DevelopmentSslConfig(trustManager, hostnameVerifier); + +// Or, if you already have an SSLSocketFactory: +DevelopmentSslConfig devSsl = new DevelopmentSslConfig(sslSocketFactory, trustManager, hostnameVerifier); +``` diff --git a/http-domain/src/main/java/io/split/android/client/network/Algorithm.java b/http-domain/src/main/java/io/split/android/client/network/Algorithm.java index bd3784efe..e0d669e05 100644 --- a/http-domain/src/main/java/io/split/android/client/network/Algorithm.java +++ b/http-domain/src/main/java/io/split/android/client/network/Algorithm.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -public class Algorithm { +class Algorithm { public static final String SHA256 = "sha256"; public static final String SHA1 = "sha1"; diff --git a/http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java b/http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java index f31358046..387a900f0 100644 --- a/http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java +++ b/http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -public interface Base64Decoder { +interface Base64Decoder { byte[] decode(String base64); } diff --git a/http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java b/http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java index 45245ed0d..09504f9e2 100644 --- a/http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java +++ b/http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java @@ -15,7 +15,7 @@ import io.split.android.client.utils.logger.Logger; -public class CertificateCheckerHelper { +class CertificateCheckerHelper { @Nullable public static Set getPinsForHost(String pattern, Map> configuredPins) { diff --git a/http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java b/http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java index 486eb5be2..b46f38309 100644 --- a/http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java +++ b/http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java @@ -4,7 +4,7 @@ import io.split.android.client.utils.logger.Logger; -public class DefaultBase64Decoder implements Base64Decoder { +class DefaultBase64Decoder implements Base64Decoder { @Override public byte[] decode(String base64) { diff --git a/http-domain/src/main/java/io/split/android/client/network/PinEncoder.java b/http-domain/src/main/java/io/split/android/client/network/PinEncoder.java index d34212ca8..3de8beecf 100644 --- a/http-domain/src/main/java/io/split/android/client/network/PinEncoder.java +++ b/http-domain/src/main/java/io/split/android/client/network/PinEncoder.java @@ -2,7 +2,7 @@ import androidx.annotation.NonNull; -public interface PinEncoder { +interface PinEncoder { @NonNull byte[] encodeCertPin(String algorithm, byte[] encodedPublicKey); diff --git a/http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java b/http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java index f1e010d51..7132b1828 100644 --- a/http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java +++ b/http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java @@ -7,7 +7,7 @@ import io.split.android.client.utils.logger.Logger; -public class PinEncoderImpl implements PinEncoder { +class PinEncoderImpl implements PinEncoder { @Override @NonNull diff --git a/http/README.md b/http/README.md index 8ece8d648..19f3a3374 100644 --- a/http/README.md +++ b/http/README.md @@ -1,23 +1,89 @@ # HTTP module -Internal HTTP client for the Split SDK. Not exposed to SDK consumers. +HTTP client for the Split SDK. ## Building an `HttpClient` -Use `HttpClientImpl.Builder` to create an instance: +### Minimal ```java HttpClient client = new HttpClientImpl.Builder() .setConnectionTimeout(15_000) .setReadTimeout(15_000) - .setTlsUpdater(tlsUpdater) // optional – TlsUpdater SPI - .setProxy(httpProxy) // optional – proxy config from :http-domain - .setProxyAuthenticator(authenticator) // optional – SplitAuthenticator from :http-domain - .setCertificatePinningConfiguration(pinConfig) // optional – cert pins from :http-domain - .setDevelopmentSslConfig(devSslConfig) // optional – dev/test SSL overrides .build(); ``` +### With `HttpClientConfiguration` (preferred) + +Bundle all settings into a single config object from `:http-domain`: + +```java +HttpClientConfiguration config = HttpClientConfiguration.builder() + .connectionTimeout(15_000) + .readTimeout(15_000) + .proxy(proxy) // optional + .proxyAuthenticator(authenticator) // optional + .certificatePinningConfiguration(pinConfig) // optional + .developmentSslConfig(devSsl) // optional + .build(); + +HttpClient client = new HttpClientImpl.Builder() + .setConfiguration(config) + .setTlsUpdater(tlsUpdater) // optional – TlsUpdater + .build(); +``` + +Individual setter calls on the builder take precedence over the configuration object. + +### Proxy + +```java +// Basic auth proxy +HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080) + .basicAuth("user", "pass") + .build(); + +// mTLS proxy with custom CA +HttpProxy mtlsProxy = HttpProxy.newBuilder("proxy.example.com", 8443) + .proxyCacert(caCertInputStream) + .mtls(clientCertInputStream, clientKeyInputStream) + .build(); + +// With a credentials provider (e.g. bearer token) +HttpProxy bearerProxy = HttpProxy.newBuilder("proxy.example.com", 8080) + .credentialsProvider(new BearerCredentialsProvider(tokenSupplier)) + .build(); +``` + +### Certificate pinning + +```java +CertificatePinningConfiguration pinConfig = CertificatePinningConfiguration.builder() + .addPin("sdk.split.io", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") + .addPin("*.split.io", certInputStream) // derive pins from a certificate file + .failureListener(failedHost -> { + Log.w("Split", "Certificate pinning failed for " + failedHost); + }) + .build(); +``` + +### Development SSL overrides + +For test environments where the server uses a self-signed certificate: + +```java +DevelopmentSslConfig devSsl = new DevelopmentSslConfig(trustManager, hostnameVerifier); +``` + +### TLS on older devices + +Implement the `TlsUpdater` SPI and pass it to the builder. +The client calls `couldBeOld()` to decide whether to force TLS 1.2 via `Tls12OnlySocketFactory`. + +```java +TlsUpdater tlsUpdater = new LegacyTlsUpdaterAdapter(context); // provided by :main +``` + ## Making requests ```java @@ -25,6 +91,10 @@ HttpClient client = new HttpClientImpl.Builder() HttpRequest req = client.request(uri, HttpMethod.GET); HttpResponse resp = req.execute(); +// POST with body +HttpRequest post = client.request(uri, HttpMethod.POST, jsonBody); +HttpResponse resp = post.execute(); + // POST with body and extra headers HttpRequest post = client.request(uri, HttpMethod.POST, jsonBody, extraHeaders); HttpResponse resp = post.execute(); @@ -42,17 +112,13 @@ client.addHeaders(commonHeaders); // Streaming-specific headers (only applied to streamRequest calls) client.setStreamingHeader("SplitSDKClientKey", clientKey); +client.addStreamingHeaders(streamingHeaders); ``` -## TLS on older devices - -Implement the `TlsUpdater` SPI and pass it to the builder. The client calls `couldBeOld()` to decide whether to force TLS 1.2 via `Tls12OnlySocketFactory`. - ## URI building ```java -URI uri = new URIBuilder("https://sdk.split.io/api") - .addPath("splitChanges") +URI uri = new URIBuilder(new URI("https://sdk.split.io/api"), "splitChanges") .addParameter("since", "-1") .build(); ``` diff --git a/http/src/main/java/io/split/android/client/network/HttpClientImpl.java b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java index d846462b7..dde0450e4 100644 --- a/http/src/main/java/io/split/android/client/network/HttpClientImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java @@ -202,6 +202,8 @@ public static class Builder { private CertificatePinningConfiguration mCertificatePinningConfiguration; private CertificateChecker mCertificateChecker; private Base64Decoder mBase64Decoder = new DefaultBase64Decoder(); + @Nullable + private HttpClientConfiguration mConfiguration; public Builder setTlsUpdater(@Nullable TlsUpdater tlsUpdater) { mTlsUpdater = tlsUpdater; @@ -263,7 +265,16 @@ Builder setBase64Decoder(Base64Decoder base64Decoder) { return this; } + public Builder setConfiguration(@NonNull HttpClientConfiguration configuration) { + mConfiguration = configuration; + return this; + } + public HttpClient build() { + if (mConfiguration != null) { + applyConfiguration(mConfiguration); + } + if (mDevelopmentSslConfig == null) { if (mTlsUpdater != null && mTlsUpdater.couldBeOld()) { mTlsUpdater.update(); @@ -310,6 +321,27 @@ public HttpClient build() { certificateChecker); } + private void applyConfiguration(@NonNull HttpClientConfiguration configuration) { + if (mConnectionTimeout == -1) { + setConnectionTimeout(configuration.getConnectionTimeout()); + } + if (mReadTimeout == -1) { + setReadTimeout(configuration.getReadTimeout()); + } + if (mProxy == null && configuration.getProxy() != null) { + setProxy(configuration.getProxy()); + } + if (mCertificatePinningConfiguration == null && configuration.getCertificatePinningConfiguration() != null) { + setCertificatePinningConfiguration(configuration.getCertificatePinningConfiguration()); + } + if (mDevelopmentSslConfig == null) { + setDevelopmentSslConfig(configuration.getDevelopmentSslConfig()); + } + if (mProxyAuthenticator == null) { + setProxyAuthenticator(configuration.getProxyAuthenticator()); + } + } + private SSLSocketFactory createSslSocketFactoryFromProxy(HttpProxy proxyParams) { ProxySslSocketFactoryProviderImpl factoryProvider = new ProxySslSocketFactoryProviderImpl(mBase64Decoder); try { diff --git a/http/src/main/java/io/split/android/client/network/HttpRequestImpl.java b/http/src/main/java/io/split/android/client/network/HttpRequestImpl.java index 669dd8598..864a9836f 100644 --- a/http/src/main/java/io/split/android/client/network/HttpRequestImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpRequestImpl.java @@ -31,7 +31,7 @@ import io.split.android.client.utils.logger.Logger; -public class HttpRequestImpl implements HttpRequest { +class HttpRequestImpl implements HttpRequest { public static final String CONTENT_TYPE = "Content-Type"; public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json; charset=utf-8"; diff --git a/http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java b/http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java index c8573e235..08d4f5376 100644 --- a/http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java @@ -30,7 +30,7 @@ import io.split.android.client.utils.logger.Logger; -public class HttpStreamRequestImpl implements HttpStreamRequest { +class HttpStreamRequestImpl implements HttpStreamRequest { private static final int STREAMING_READ_TIMEOUT_IN_MILLISECONDS = 80000; diff --git a/http/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java b/http/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java index bf24d0e74..bae64f68d 100644 --- a/http/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java @@ -8,7 +8,7 @@ import io.split.android.client.utils.logger.Logger; -public class HttpStreamResponseImpl extends BaseHttpResponseImpl implements HttpStreamResponse { +class HttpStreamResponseImpl extends BaseHttpResponseImpl implements HttpStreamResponse { private final BufferedReader mData; diff --git a/main/src/main/java/io/split/android/client/SplitFactoryImpl.java b/main/src/main/java/io/split/android/client/SplitFactoryImpl.java index 0016b6a76..898a7c37d 100644 --- a/main/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/main/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -32,6 +32,7 @@ import io.split.android.client.lifecycle.SplitLifecycleManager; import io.split.android.client.lifecycle.SplitLifecycleManagerImpl; import io.split.android.client.network.HttpClient; +import io.split.android.client.network.HttpClientConfiguration; import io.split.android.client.network.HttpClientImpl; import io.split.android.client.network.LegacyTlsUpdaterAdapter; import io.split.android.client.service.CleanUpDatabaseTask; @@ -382,20 +383,19 @@ private static HttpClient getHttpClient(@NonNull String apiToken, @Nullable GeneralInfoStorage generalInfoStorage) { HttpClient defaultHttpClient; if (httpClient == null) { - HttpClientImpl.Builder builder = new HttpClientImpl.Builder() - .setConnectionTimeout(config.connectionTimeout()) - .setReadTimeout(config.readTimeout()) - .setDevelopmentSslConfig(config.developmentSslConfig()) + HttpClientConfiguration httpConfig = HttpClientConfiguration.builder() + .connectionTimeout(config.connectionTimeout()) + .readTimeout(config.readTimeout()) + .developmentSslConfig(config.developmentSslConfig()) + .proxy(config.proxy()) + .certificatePinningConfiguration(config.certificatePinningConfiguration()) + .proxyAuthenticator(config.authenticator()) + .build(); + + defaultHttpClient = new HttpClientImpl.Builder() + .setConfiguration(httpConfig) .setTlsUpdater(new LegacyTlsUpdaterAdapter(context)) - .setProxyAuthenticator(config.authenticator()); - if (config.proxy() != null) { - builder.setProxy(config.proxy()); - } - if (config.certificatePinningConfiguration() != null) { - builder.setCertificatePinningConfiguration(config.certificatePinningConfiguration()); - } - - defaultHttpClient = builder.build(); + .build(); // This should be extracted; has nothing to do with the method. if (config.proxy() != null && generalInfoStorage != null) { From 491deb3c379b3a12c686ab57622f37f3a7a6e534 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Feb 2026 09:43:06 -0300 Subject: [PATCH 10/14] Rename http-domain to http-api --- build.gradle | 2 +- {http-domain => http-api}/.gitignore | 0 {http-domain => http-api}/README.md | 2 +- {http-domain => http-api}/build.gradle | 2 +- {http-domain => http-api}/consumer-rules.pro | 0 {http-domain => http-api}/proguard-rules.pro | 0 .../src/main/AndroidManifest.xml | 0 .../android/client/network/Algorithm.java | 0 .../client/network/AuthenticatedRequest.java | 0 .../android/client/network/Authenticator.java | 0 .../android/client/network/Base64Decoder.java | 0 .../network/BasicCredentialsProvider.java | 0 .../network/BearerCredentialsProvider.java | 0 .../network/CertificateCheckerHelper.java | 0 .../android/client/network/CertificatePin.java | 0 .../CertificatePinningConfiguration.java | 0 .../CertificatePinningFailureListener.java | 0 .../client/network/DefaultBase64Decoder.java | 0 .../client/network/DevelopmentSslConfig.java | 0 .../network/HttpClientConfiguration.java | 0 .../android/client/network/HttpProxy.java | 0 .../android/client/network/PinEncoder.java | 0 .../android/client/network/PinEncoderImpl.java | 0 .../client/network/ProxyConfiguration.java | 0 .../network/ProxyCredentialsProvider.java | 0 .../client/network/SplitAuthenticator.java | 0 .../network/CertificateCheckerHelperTest.java | 0 .../CertificatePinningConfigurationTest.java | 0 .../network/HttpClientConfigurationTest.java | 0 .../client/network/PinEncoderImplTest.java | 0 http/README.md | 2 +- http/build.gradle | 2 +- main/build.gradle | 2 +- settings.gradle | 2 +- sonar-project.properties | 18 +++++++++--------- 35 files changed, 16 insertions(+), 16 deletions(-) rename {http-domain => http-api}/.gitignore (100%) rename {http-domain => http-api}/README.md (99%) rename {http-domain => http-api}/build.gradle (89%) rename {http-domain => http-api}/consumer-rules.pro (100%) rename {http-domain => http-api}/proguard-rules.pro (100%) rename {http-domain => http-api}/src/main/AndroidManifest.xml (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/Algorithm.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/AuthenticatedRequest.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/Authenticator.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/Base64Decoder.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/CertificatePin.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/HttpClientConfiguration.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/HttpProxy.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/PinEncoder.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/PinEncoderImpl.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/ProxyConfiguration.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java (100%) rename {http-domain => http-api}/src/main/java/io/split/android/client/network/SplitAuthenticator.java (100%) rename {http-domain => http-api}/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java (100%) rename {http-domain => http-api}/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java (100%) rename {http-domain => http-api}/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java (100%) rename {http-domain => http-api}/src/test/java/io/split/android/client/network/PinEncoderImplTest.java (100%) diff --git a/build.gradle b/build.gradle index 64f9cdfbc..a947d08b6 100644 --- a/build.gradle +++ b/build.gradle @@ -140,7 +140,7 @@ dependencies { include project(':events') include project(':events-domain') include project(':api') - include project(':http-domain') + include project(':http-api') include project(':http') } diff --git a/http-domain/.gitignore b/http-api/.gitignore similarity index 100% rename from http-domain/.gitignore rename to http-api/.gitignore diff --git a/http-domain/README.md b/http-api/README.md similarity index 99% rename from http-domain/README.md rename to http-api/README.md index bad8159a8..1d3cc5caf 100644 --- a/http-domain/README.md +++ b/http-api/README.md @@ -1,4 +1,4 @@ -# HTTP Domain module +# HTTP API module Public contracts and configuration types for the HTTP client. These types are exposed to SDK consumers through the `:main` module's `api` dependency. diff --git a/http-domain/build.gradle b/http-api/build.gradle similarity index 89% rename from http-domain/build.gradle rename to http-api/build.gradle index b15da12d4..7e915b5f3 100644 --- a/http-domain/build.gradle +++ b/http-api/build.gradle @@ -5,7 +5,7 @@ plugins { apply from: "$rootDir/gradle/common-android-library.gradle" android { - namespace 'io.split.android.client.network.domain' + namespace 'io.split.android.client.network.api' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/http-domain/consumer-rules.pro b/http-api/consumer-rules.pro similarity index 100% rename from http-domain/consumer-rules.pro rename to http-api/consumer-rules.pro diff --git a/http-domain/proguard-rules.pro b/http-api/proguard-rules.pro similarity index 100% rename from http-domain/proguard-rules.pro rename to http-api/proguard-rules.pro diff --git a/http-domain/src/main/AndroidManifest.xml b/http-api/src/main/AndroidManifest.xml similarity index 100% rename from http-domain/src/main/AndroidManifest.xml rename to http-api/src/main/AndroidManifest.xml diff --git a/http-domain/src/main/java/io/split/android/client/network/Algorithm.java b/http-api/src/main/java/io/split/android/client/network/Algorithm.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/Algorithm.java rename to http-api/src/main/java/io/split/android/client/network/Algorithm.java diff --git a/http-domain/src/main/java/io/split/android/client/network/AuthenticatedRequest.java b/http-api/src/main/java/io/split/android/client/network/AuthenticatedRequest.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/AuthenticatedRequest.java rename to http-api/src/main/java/io/split/android/client/network/AuthenticatedRequest.java diff --git a/http-domain/src/main/java/io/split/android/client/network/Authenticator.java b/http-api/src/main/java/io/split/android/client/network/Authenticator.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/Authenticator.java rename to http-api/src/main/java/io/split/android/client/network/Authenticator.java diff --git a/http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java b/http-api/src/main/java/io/split/android/client/network/Base64Decoder.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/Base64Decoder.java rename to http-api/src/main/java/io/split/android/client/network/Base64Decoder.java diff --git a/http-domain/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java b/http-api/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java rename to http-api/src/main/java/io/split/android/client/network/BasicCredentialsProvider.java diff --git a/http-domain/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java b/http-api/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java rename to http-api/src/main/java/io/split/android/client/network/BearerCredentialsProvider.java diff --git a/http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java b/http-api/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java rename to http-api/src/main/java/io/split/android/client/network/CertificateCheckerHelper.java diff --git a/http-domain/src/main/java/io/split/android/client/network/CertificatePin.java b/http-api/src/main/java/io/split/android/client/network/CertificatePin.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/CertificatePin.java rename to http-api/src/main/java/io/split/android/client/network/CertificatePin.java diff --git a/http-domain/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java b/http-api/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java rename to http-api/src/main/java/io/split/android/client/network/CertificatePinningConfiguration.java diff --git a/http-domain/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java b/http-api/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java rename to http-api/src/main/java/io/split/android/client/network/CertificatePinningFailureListener.java diff --git a/http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java b/http-api/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java rename to http-api/src/main/java/io/split/android/client/network/DefaultBase64Decoder.java diff --git a/http-domain/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java b/http-api/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java rename to http-api/src/main/java/io/split/android/client/network/DevelopmentSslConfig.java diff --git a/http-domain/src/main/java/io/split/android/client/network/HttpClientConfiguration.java b/http-api/src/main/java/io/split/android/client/network/HttpClientConfiguration.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/HttpClientConfiguration.java rename to http-api/src/main/java/io/split/android/client/network/HttpClientConfiguration.java diff --git a/http-domain/src/main/java/io/split/android/client/network/HttpProxy.java b/http-api/src/main/java/io/split/android/client/network/HttpProxy.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/HttpProxy.java rename to http-api/src/main/java/io/split/android/client/network/HttpProxy.java diff --git a/http-domain/src/main/java/io/split/android/client/network/PinEncoder.java b/http-api/src/main/java/io/split/android/client/network/PinEncoder.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/PinEncoder.java rename to http-api/src/main/java/io/split/android/client/network/PinEncoder.java diff --git a/http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java b/http-api/src/main/java/io/split/android/client/network/PinEncoderImpl.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/PinEncoderImpl.java rename to http-api/src/main/java/io/split/android/client/network/PinEncoderImpl.java diff --git a/http-domain/src/main/java/io/split/android/client/network/ProxyConfiguration.java b/http-api/src/main/java/io/split/android/client/network/ProxyConfiguration.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/ProxyConfiguration.java rename to http-api/src/main/java/io/split/android/client/network/ProxyConfiguration.java diff --git a/http-domain/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java b/http-api/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java rename to http-api/src/main/java/io/split/android/client/network/ProxyCredentialsProvider.java diff --git a/http-domain/src/main/java/io/split/android/client/network/SplitAuthenticator.java b/http-api/src/main/java/io/split/android/client/network/SplitAuthenticator.java similarity index 100% rename from http-domain/src/main/java/io/split/android/client/network/SplitAuthenticator.java rename to http-api/src/main/java/io/split/android/client/network/SplitAuthenticator.java diff --git a/http-domain/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java b/http-api/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java similarity index 100% rename from http-domain/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java rename to http-api/src/test/java/io/split/android/client/network/CertificateCheckerHelperTest.java diff --git a/http-domain/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java b/http-api/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java similarity index 100% rename from http-domain/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java rename to http-api/src/test/java/io/split/android/client/network/CertificatePinningConfigurationTest.java diff --git a/http-domain/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java b/http-api/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java similarity index 100% rename from http-domain/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java rename to http-api/src/test/java/io/split/android/client/network/HttpClientConfigurationTest.java diff --git a/http-domain/src/test/java/io/split/android/client/network/PinEncoderImplTest.java b/http-api/src/test/java/io/split/android/client/network/PinEncoderImplTest.java similarity index 100% rename from http-domain/src/test/java/io/split/android/client/network/PinEncoderImplTest.java rename to http-api/src/test/java/io/split/android/client/network/PinEncoderImplTest.java diff --git a/http/README.md b/http/README.md index 19f3a3374..12e59f39f 100644 --- a/http/README.md +++ b/http/README.md @@ -15,7 +15,7 @@ HttpClient client = new HttpClientImpl.Builder() ### With `HttpClientConfiguration` (preferred) -Bundle all settings into a single config object from `:http-domain`: +Bundle all settings into a single config object from `:http-api`: ```java HttpClientConfiguration config = HttpClientConfiguration.builder() diff --git a/http/build.gradle b/http/build.gradle index f613652b7..a7367b06e 100644 --- a/http/build.gradle +++ b/http/build.gradle @@ -16,7 +16,7 @@ android { dependencies { implementation libs.annotation implementation project(':logger') - api project(':http-domain') + api project(':http-api') testImplementation libs.junit4 testImplementation libs.mockitoCore diff --git a/main/build.gradle b/main/build.gradle index 70d932be8..63f91db29 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -52,7 +52,7 @@ dependencies { // Public api modules api project(':logger') api project(':api') - api project(':http-domain') + api project(':http-api') // Internal module dependencies implementation project(':http') implementation project(':events-domain') diff --git a/settings.gradle b/settings.gradle index 7eaafe2d3..9ddd50403 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,7 +2,7 @@ rootProject.name = 'android-client' include ':api' include ':logger' -include ':http-domain' +include ':http-api' include ':http' include ':main' include ':events' diff --git a/sonar-project.properties b/sonar-project.properties index a3cc5e80d..a8542a151 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,16 +3,16 @@ sonar.projectKey=splitio_android-client sonar.projectName=android-client # Path to source directories (multi-module) -# Root project contains modules: main, events, logger, http-domain, http -sonar.sources=main/src/main/java,events/src/main/java,logger/src/main/java,http-domain/src/main/java,http/src/main/java +# Root project contains modules: main, events, logger, http-api, http +sonar.sources=main/src/main/java,events/src/main/java,logger/src/main/java,http-api/src/main/java,http/src/main/java # Path to compiled classes (multi-module) -# Include binary paths for all modules: main, events, logger, http-domain, http +# Include binary paths for all modules: main, events, logger, http-api, http sonar.java.binaries=\ main/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ events/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ logger/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ - http-domain/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ + http-api/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ http/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes # Path to dependency/libraries jars (multi-module) @@ -29,10 +29,10 @@ sonar.java.libraries=\ logger/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ logger/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ logger/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar,\ - http-domain/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ - http-domain/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ - http-domain/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ - http-domain/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar,\ + http-api/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ + http-api/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ + http-api/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ + http-api/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar,\ http/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ http/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ http/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ @@ -40,7 +40,7 @@ sonar.java.libraries=\ # Path to test directories (multi-module) # Only include test source folders that are guaranteed to exist in all environments -sonar.tests=main/src/test/java,main/src/androidTest/java,main/src/sharedTest/java,events/src/test/java,logger/src/test/java,http-domain/src/test/java,http/src/test/java +sonar.tests=main/src/test/java,main/src/androidTest/java,main/src/sharedTest/java,events/src/test/java,logger/src/test/java,http-api/src/test/java,http/src/test/java # Encoding of the source code sonar.sourceEncoding=UTF-8 From 17cde55d4d4b35e8c5b12242de49acd54f5581a0 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Feb 2026 10:18:35 -0300 Subject: [PATCH 11/14] Fix Sonar scan --- .github/workflows/sonarqube.yml | 3 --- sonar-project.properties | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index c2f306a64..94a0bf3c3 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -9,9 +9,6 @@ on: pull_request: branches: - '*' - push: - branches: - - master concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/sonar-project.properties b/sonar-project.properties index a8542a151..85a95779d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,12 +3,14 @@ sonar.projectKey=splitio_android-client sonar.projectName=android-client # Path to source directories (multi-module) -# Root project contains modules: main, events, logger, http-api, http -sonar.sources=main/src/main/java,events/src/main/java,logger/src/main/java,http-api/src/main/java,http/src/main/java +# Root project contains modules: api, events-domain, main, events, logger, http-api, http +sonar.sources=api/src/main/java,events-domain/src/main/java,main/src/main/java,events/src/main/java,logger/src/main/java,http-api/src/main/java,http/src/main/java # Path to compiled classes (multi-module) -# Include binary paths for all modules: main, events, logger, http-api, http +# Include binary paths for all modules: api, events-domain, main, events, logger, http-api, http sonar.java.binaries=\ + api/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ + events-domain/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ main/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ events/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ logger/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,\ @@ -29,6 +31,14 @@ sonar.java.libraries=\ logger/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ logger/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ logger/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar,\ + api/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ + api/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ + api/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ + api/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar,\ + events-domain/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ + events-domain/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ + events-domain/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ + events-domain/build/intermediates/compile_and_runtime_r_class_jar/debugUnitTest/generateDebugUnitTestStubRFile/R.jar,\ http-api/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar,\ http-api/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar,\ http-api/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar,\ @@ -40,7 +50,7 @@ sonar.java.libraries=\ # Path to test directories (multi-module) # Only include test source folders that are guaranteed to exist in all environments -sonar.tests=main/src/test/java,main/src/androidTest/java,main/src/sharedTest/java,events/src/test/java,logger/src/test/java,http-api/src/test/java,http/src/test/java +sonar.tests=api/src/test/java,events-domain/src/test/java,main/src/test/java,main/src/androidTest/java,main/src/sharedTest/java,events/src/test/java,logger/src/test/java,http-api/src/test/java,http/src/test/java # Encoding of the source code sonar.sourceEncoding=UTF-8 From 5a33696c6d69a5cf640654207ef22b8b0fcce210 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Feb 2026 10:58:24 -0300 Subject: [PATCH 12/14] Add tests --- .../client/network/HttpClientImpl.java | 34 ++++ ...ttpClientImplBuilderConfigurationTest.java | 160 ++++++++++++++++++ .../android/client/SplitFactoryImpl.java | 24 ++- .../SplitFactoryImplConfigMappingTest.java | 118 +++++++++++++ 4 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 http/src/test/java/io/split/android/client/network/HttpClientImplBuilderConfigurationTest.java create mode 100644 main/src/test/java/io/split/android/client/SplitFactoryImplConfigMappingTest.java diff --git a/http/src/main/java/io/split/android/client/network/HttpClientImpl.java b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java index dde0450e4..0ffb2ac95 100644 --- a/http/src/main/java/io/split/android/client/network/HttpClientImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java @@ -161,6 +161,40 @@ SSLSocketFactory getSslSocketFactory() { return mSslSocketFactory; } + @VisibleForTesting + long getReadTimeout() { + return mReadTimeout; + } + + @VisibleForTesting + long getConnectionTimeout() { + return mConnectionTimeout; + } + + @VisibleForTesting + @Nullable + HttpProxy getHttpProxy() { + return mHttpProxy; + } + + @VisibleForTesting + @Nullable + SplitUrlConnectionAuthenticator getProxyAuthenticator() { + return mProxyAuthenticator; + } + + @VisibleForTesting + @Nullable + DevelopmentSslConfig getDevelopmentSslConfig() { + return mDevelopmentSslConfig; + } + + @VisibleForTesting + @Nullable + CertificateChecker getCertificateChecker() { + return mCertificateChecker; + } + private Proxy initializeProxy(HttpProxy proxy) { if (proxy != null) { return new Proxy( diff --git a/http/src/test/java/io/split/android/client/network/HttpClientImplBuilderConfigurationTest.java b/http/src/test/java/io/split/android/client/network/HttpClientImplBuilderConfigurationTest.java new file mode 100644 index 000000000..d85907743 --- /dev/null +++ b/http/src/test/java/io/split/android/client/network/HttpClientImplBuilderConfigurationTest.java @@ -0,0 +1,160 @@ +package io.split.android.client.network; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.junit.Test; + +public class HttpClientImplBuilderConfigurationTest { + + @Test + public void configurationAppliesAllValuesWhenBuilderHasDefaults() { + HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080).build(); + SplitAuthenticator authenticator = new SplitAuthenticator() { + @Nullable + @Override + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { + return request; + } + }; + CertificatePinningConfiguration pinConfig = mock(CertificatePinningConfiguration.class); + DevelopmentSslConfig devSsl = mock(DevelopmentSslConfig.class); + + HttpClientConfiguration config = HttpClientConfiguration.builder() + .connectionTimeout(5000) + .readTimeout(10000) + .proxy(proxy) + .proxyAuthenticator(authenticator) + .certificatePinningConfiguration(pinConfig) + .developmentSslConfig(devSsl) + .build(); + + HttpClientImpl client = (HttpClientImpl) new HttpClientImpl.Builder() + .setConfiguration(config) + .build(); + + assertEquals(5000, client.getConnectionTimeout()); + assertEquals(10000, client.getReadTimeout()); + assertNotNull(client.getHttpProxy()); + assertEquals("proxy.example.com", client.getHttpProxy().getHost()); + assertEquals(8080, client.getHttpProxy().getPort()); + assertNotNull(client.getProxyAuthenticator()); + assertNotNull(client.getCertificateChecker()); + assertNotNull(client.getDevelopmentSslConfig()); + } + + @Test + public void builderValuesTakePrecedenceOverConfiguration() { + HttpProxy configProxy = HttpProxy.newBuilder("config.proxy.com", 9090).build(); + HttpProxy builderProxy = HttpProxy.newBuilder("builder.proxy.com", 7070).build(); + + HttpClientConfiguration config = HttpClientConfiguration.builder() + .connectionTimeout(5000) + .readTimeout(10000) + .proxy(configProxy) + .build(); + + HttpClientImpl client = (HttpClientImpl) new HttpClientImpl.Builder() + .setConnectionTimeout(1000) + .setReadTimeout(2000) + .setProxy(builderProxy) + .setConfiguration(config) + .build(); + + // Builder values should win + assertEquals(1000, client.getConnectionTimeout()); + assertEquals(2000, client.getReadTimeout()); + assertEquals("builder.proxy.com", client.getHttpProxy().getHost()); + assertEquals(7070, client.getHttpProxy().getPort()); + } + + @Test + public void configurationWithNullOptionalFieldsDoesNotOverrideBuilderDefaults() { + HttpClientConfiguration config = HttpClientConfiguration.builder() + .connectionTimeout(3000) + .readTimeout(6000) + .build(); + + HttpClientImpl client = (HttpClientImpl) new HttpClientImpl.Builder() + .setConfiguration(config) + .build(); + + assertEquals(3000, client.getConnectionTimeout()); + assertEquals(6000, client.getReadTimeout()); + assertNull(client.getHttpProxy()); + assertNull(client.getProxyAuthenticator()); + assertNull(client.getCertificateChecker()); + assertNull(client.getDevelopmentSslConfig()); + } + + @Test + public void buildWithoutConfigurationUsesBuilderDefaults() { + HttpClientImpl client = (HttpClientImpl) new HttpClientImpl.Builder() + .setConnectionTimeout(4000) + .setReadTimeout(8000) + .build(); + + assertEquals(4000, client.getConnectionTimeout()); + assertEquals(8000, client.getReadTimeout()); + assertNull(client.getHttpProxy()); + assertNull(client.getProxyAuthenticator()); + assertNull(client.getCertificateChecker()); + assertNull(client.getDevelopmentSslConfig()); + } + + @Test + public void builderAuthenticatorTakesPrecedenceOverConfiguration() { + SplitAuthenticator configAuth = new SplitAuthenticator() { + @Nullable + @Override + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { + request.setHeader("Source", "config"); + return request; + } + }; + SplitAuthenticator builderAuth = new SplitAuthenticator() { + @Nullable + @Override + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { + request.setHeader("Source", "builder"); + return request; + } + }; + + HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080).build(); + + HttpClientConfiguration config = HttpClientConfiguration.builder() + .proxy(proxy) + .proxyAuthenticator(configAuth) + .build(); + + HttpClientImpl client = (HttpClientImpl) new HttpClientImpl.Builder() + .setProxy(proxy) + .setProxyAuthenticator(builderAuth) + .setConfiguration(config) + .build(); + + // Builder authenticator should win — proxy authenticator should not be null + assertNotNull(client.getProxyAuthenticator()); + } + + @Test + public void configurationWithNullProxyDoesNotSetProxy() { + HttpClientConfiguration config = HttpClientConfiguration.builder() + .connectionTimeout(1000) + .readTimeout(2000) + .proxy(null) + .build(); + + HttpClientImpl client = (HttpClientImpl) new HttpClientImpl.Builder() + .setConfiguration(config) + .build(); + + assertNull(client.getHttpProxy()); + } +} diff --git a/main/src/main/java/io/split/android/client/SplitFactoryImpl.java b/main/src/main/java/io/split/android/client/SplitFactoryImpl.java index 898a7c37d..3cd4f501b 100644 --- a/main/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/main/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -20,6 +20,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; +import androidx.annotation.VisibleForTesting; + import io.split.android.client.main.BuildConfig; import io.split.android.client.api.Key; import io.split.android.client.common.CompressionUtilProvider; @@ -383,14 +385,7 @@ private static HttpClient getHttpClient(@NonNull String apiToken, @Nullable GeneralInfoStorage generalInfoStorage) { HttpClient defaultHttpClient; if (httpClient == null) { - HttpClientConfiguration httpConfig = HttpClientConfiguration.builder() - .connectionTimeout(config.connectionTimeout()) - .readTimeout(config.readTimeout()) - .developmentSslConfig(config.developmentSslConfig()) - .proxy(config.proxy()) - .certificatePinningConfiguration(config.certificatePinningConfiguration()) - .proxyAuthenticator(config.authenticator()) - .build(); + HttpClientConfiguration httpConfig = buildHttpClientConfiguration(config); defaultHttpClient = new HttpClientImpl.Builder() .setConfiguration(httpConfig) @@ -412,6 +407,19 @@ private static HttpClient getHttpClient(@NonNull String apiToken, return defaultHttpClient; } + @VisibleForTesting + @NonNull + static HttpClientConfiguration buildHttpClientConfiguration(@NonNull SplitClientConfig config) { + return HttpClientConfiguration.builder() + .connectionTimeout(config.connectionTimeout()) + .readTimeout(config.readTimeout()) + .developmentSslConfig(config.developmentSslConfig()) + .proxy(config.proxy()) + .certificatePinningConfiguration(config.certificatePinningConfiguration()) + .proxyAuthenticator(config.authenticator()) + .build(); + } + private static String getFlagsSpec(@Nullable TestingConfig testingConfig) { if (testingConfig == null) { return BuildConfig.FLAGS_SPEC; diff --git a/main/src/test/java/io/split/android/client/SplitFactoryImplConfigMappingTest.java b/main/src/test/java/io/split/android/client/SplitFactoryImplConfigMappingTest.java new file mode 100644 index 000000000..900f67f10 --- /dev/null +++ b/main/src/test/java/io/split/android/client/SplitFactoryImplConfigMappingTest.java @@ -0,0 +1,118 @@ +package io.split.android.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.junit.Test; + +import io.split.android.client.network.AuthenticatedRequest; +import io.split.android.client.network.CertificatePinningConfiguration; +import io.split.android.client.network.DevelopmentSslConfig; +import io.split.android.client.network.HttpClientConfiguration; +import io.split.android.client.network.HttpProxy; +import io.split.android.client.network.SplitAuthenticator; + +public class SplitFactoryImplConfigMappingTest { + + @Test + public void buildHttpClientConfigurationMapsAllFields() { + SplitClientConfig splitConfig = mock(SplitClientConfig.class); + HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080).build(); + CertificatePinningConfiguration pinConfig = mock(CertificatePinningConfiguration.class); + DevelopmentSslConfig devSsl = mock(DevelopmentSslConfig.class); + SplitAuthenticator authenticator = new SplitAuthenticator() { + @Nullable + @Override + public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) { + return request; + } + }; + + when(splitConfig.connectionTimeout()).thenReturn(5000); + when(splitConfig.readTimeout()).thenReturn(10000); + when(splitConfig.proxy()).thenReturn(proxy); + when(splitConfig.certificatePinningConfiguration()).thenReturn(pinConfig); + when(splitConfig.developmentSslConfig()).thenReturn(devSsl); + when(splitConfig.authenticator()).thenReturn(authenticator); + + HttpClientConfiguration result = SplitFactoryImpl.buildHttpClientConfiguration(splitConfig); + + assertEquals(5000, result.getConnectionTimeout()); + assertEquals(10000, result.getReadTimeout()); + assertNotNull(result.getProxy()); + assertEquals("proxy.example.com", result.getProxy().getHost()); + assertEquals(8080, result.getProxy().getPort()); + assertSame(pinConfig, result.getCertificatePinningConfiguration()); + assertSame(devSsl, result.getDevelopmentSslConfig()); + assertSame(authenticator, result.getProxyAuthenticator()); + } + + @Test + public void buildHttpClientConfigurationWithNullOptionals() { + SplitClientConfig splitConfig = mock(SplitClientConfig.class); + + when(splitConfig.connectionTimeout()).thenReturn(3000); + when(splitConfig.readTimeout()).thenReturn(6000); + when(splitConfig.proxy()).thenReturn(null); + when(splitConfig.certificatePinningConfiguration()).thenReturn(null); + when(splitConfig.developmentSslConfig()).thenReturn(null); + when(splitConfig.authenticator()).thenReturn(null); + + HttpClientConfiguration result = SplitFactoryImpl.buildHttpClientConfiguration(splitConfig); + + assertEquals(3000, result.getConnectionTimeout()); + assertEquals(6000, result.getReadTimeout()); + assertNull(result.getProxy()); + assertNull(result.getCertificatePinningConfiguration()); + assertNull(result.getDevelopmentSslConfig()); + assertNull(result.getProxyAuthenticator()); + } + + @Test + public void buildHttpClientConfigurationWithZeroTimeouts() { + SplitClientConfig splitConfig = mock(SplitClientConfig.class); + + when(splitConfig.connectionTimeout()).thenReturn(0); + when(splitConfig.readTimeout()).thenReturn(0); + when(splitConfig.proxy()).thenReturn(null); + when(splitConfig.certificatePinningConfiguration()).thenReturn(null); + when(splitConfig.developmentSslConfig()).thenReturn(null); + when(splitConfig.authenticator()).thenReturn(null); + + HttpClientConfiguration result = SplitFactoryImpl.buildHttpClientConfiguration(splitConfig); + + assertEquals(0, result.getConnectionTimeout()); + assertEquals(0, result.getReadTimeout()); + } + + @Test + public void buildHttpClientConfigurationWithOnlyProxy() { + SplitClientConfig splitConfig = mock(SplitClientConfig.class); + HttpProxy proxy = HttpProxy.newBuilder("myproxy.local", 3128).build(); + + when(splitConfig.connectionTimeout()).thenReturn(15000); + when(splitConfig.readTimeout()).thenReturn(15000); + when(splitConfig.proxy()).thenReturn(proxy); + when(splitConfig.certificatePinningConfiguration()).thenReturn(null); + when(splitConfig.developmentSslConfig()).thenReturn(null); + when(splitConfig.authenticator()).thenReturn(null); + + HttpClientConfiguration result = SplitFactoryImpl.buildHttpClientConfiguration(splitConfig); + + assertEquals(15000, result.getConnectionTimeout()); + assertEquals(15000, result.getReadTimeout()); + assertNotNull(result.getProxy()); + assertEquals("myproxy.local", result.getProxy().getHost()); + assertEquals(3128, result.getProxy().getPort()); + assertNull(result.getCertificatePinningConfiguration()); + assertNull(result.getDevelopmentSslConfig()); + assertNull(result.getProxyAuthenticator()); + } +} From 5ea978ee18d5161c3bfa75ad00f0640582797ecc Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Feb 2026 13:19:06 -0300 Subject: [PATCH 13/14] Small fixes --- .../java/io/split/android/client/network/HttpProxy.java | 8 ++++---- .../io/split/android/client/network/HttpClientImpl.java | 4 +++- .../java/io/split/android/client/network/UrlEscapers.java | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/http-api/src/main/java/io/split/android/client/network/HttpProxy.java b/http-api/src/main/java/io/split/android/client/network/HttpProxy.java index a6dc011fa..969f69176 100644 --- a/http-api/src/main/java/io/split/android/client/network/HttpProxy.java +++ b/http-api/src/main/java/io/split/android/client/network/HttpProxy.java @@ -29,7 +29,7 @@ private HttpProxy(Builder builder, boolean isLegacy) { mIsLegacy = isLegacy; } - public @Nullable String getHost() { + public @NonNull String getHost() { return mHost; } @@ -61,7 +61,7 @@ public int getPort() { return mCredentialsProvider; } - public static Builder newBuilder(@Nullable String host, int port) { + public static Builder newBuilder(@NonNull String host, int port) { return new Builder(host, port); } @@ -70,7 +70,7 @@ public boolean isLegacy() { } public static class Builder { - private final @Nullable String mHost; + private final @NonNull String mHost; private final int mPort; private @Nullable String mUsername; private @Nullable String mPassword; @@ -80,7 +80,7 @@ public static class Builder { @Nullable private ProxyCredentialsProvider mCredentialsProvider; - private Builder(@Nullable String host, int port) { + private Builder(@NonNull String host, int port) { mHost = host; mPort = port; } diff --git a/http/src/main/java/io/split/android/client/network/HttpClientImpl.java b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java index 0ffb2ac95..8fbff1270 100644 --- a/http/src/main/java/io/split/android/client/network/HttpClientImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java @@ -244,7 +244,7 @@ public Builder setTlsUpdater(@Nullable TlsUpdater tlsUpdater) { return this; } - public Builder setProxy(HttpProxy proxy) { + public Builder setProxy(@NonNull HttpProxy proxy) { mProxy = proxy; mProxyCredentialsProvider = proxy.getCredentialsProvider(); return this; @@ -355,6 +355,8 @@ public HttpClient build() { certificateChecker); } + // Configuration timeout values of 0 or less are intentionally ignored by + // setConnectionTimeout / setReadTimeout, leaving the platform default in place. private void applyConfiguration(@NonNull HttpClientConfiguration configuration) { if (mConnectionTimeout == -1) { setConnectionTimeout(configuration.getConnectionTimeout()); diff --git a/http/src/main/java/io/split/android/client/network/UrlEscapers.java b/http/src/main/java/io/split/android/client/network/UrlEscapers.java index d12a6f995..11098b8e0 100644 --- a/http/src/main/java/io/split/android/client/network/UrlEscapers.java +++ b/http/src/main/java/io/split/android/client/network/UrlEscapers.java @@ -3,7 +3,7 @@ /** * Based on Guava UrlEscapers */ -final class UrlEscapers { +public final class UrlEscapers { private UrlEscapers() {} private static final String URL_PATH_OTHER_SAFE_CHARS_LACKING_PLUS = From 17a8df640a960f220cd94a53d260abfa92452ace Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 10 Feb 2026 15:01:20 -0300 Subject: [PATCH 14/14] Fix cert pin serialization --- .../network/CertificatePinSerializer.java | 67 +++++++++ ...rtificatePinningConfigurationProvider.java | 21 +-- .../io/split/android/client/utils/Json.java | 3 + .../network/CertificatePinSerializerTest.java | 129 ++++++++++++++++++ 4 files changed, 203 insertions(+), 17 deletions(-) create mode 100644 main/src/main/java/io/split/android/client/network/CertificatePinSerializer.java create mode 100644 main/src/test/java/io/split/android/client/network/CertificatePinSerializerTest.java diff --git a/main/src/main/java/io/split/android/client/network/CertificatePinSerializer.java b/main/src/main/java/io/split/android/client/network/CertificatePinSerializer.java new file mode 100644 index 000000000..494ed6177 --- /dev/null +++ b/main/src/main/java/io/split/android/client/network/CertificatePinSerializer.java @@ -0,0 +1,67 @@ +package io.split.android.client.network; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +/** + * Custom Gson {@link TypeAdapter} for {@link CertificatePin} that uses + * {@code "algo"} and {@code "pin"} as JSON keys instead of the raw field names. + */ +public class CertificatePinSerializer extends TypeAdapter { + + @Override + public void write(JsonWriter out, CertificatePin src) throws IOException { + out.beginObject(); + out.name("algo").value(src.getAlgorithm()); + out.name("pin"); + out.beginArray(); + for (byte b : src.getPin()) { + out.value(b); + } + out.endArray(); + out.endObject(); + } + + @Override + public CertificatePin read(JsonReader in) throws IOException { + String algorithm = null; + byte[] pin = null; + + in.beginObject(); + while (in.hasNext()) { + String name = in.nextName(); + switch (name) { + case "algo": + algorithm = in.nextString(); + break; + case "pin": + pin = readByteArray(in); + break; + default: + in.skipValue(); + break; + } + } + in.endObject(); + + return new CertificatePin(pin, algorithm); + } + + private static byte[] readByteArray(JsonReader in) throws IOException { + java.util.List bytes = new java.util.ArrayList<>(); + in.beginArray(); + while (in.hasNext()) { + bytes.add((byte) in.nextInt()); + } + in.endArray(); + + byte[] result = new byte[bytes.size()]; + for (int i = 0; i < bytes.size(); i++) { + result[i] = bytes.get(i); + } + return result; + } +} diff --git a/main/src/main/java/io/split/android/client/network/CertificatePinningConfigurationProvider.java b/main/src/main/java/io/split/android/client/network/CertificatePinningConfigurationProvider.java index 801baa909..aa640fbc5 100644 --- a/main/src/main/java/io/split/android/client/network/CertificatePinningConfigurationProvider.java +++ b/main/src/main/java/io/split/android/client/network/CertificatePinningConfigurationProvider.java @@ -1,10 +1,8 @@ package io.split.android.client.network; -import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -15,18 +13,14 @@ public class CertificatePinningConfigurationProvider { public static CertificatePinningConfiguration getCertificatePinningConfiguration(String pinsJson) { try { - Type type = new TypeToken>>() { + Type type = new TypeToken>>() { }.getType(); - Map> certificatePins = Json.fromJson(pinsJson, type); + Map> certificatePins = Json.fromJson(pinsJson, type); if (certificatePins != null && !certificatePins.isEmpty()) { CertificatePinningConfiguration.Builder builder = CertificatePinningConfiguration.builder(); - for (Map.Entry> entry : certificatePins.entrySet()) { - Set pins = new HashSet<>(); - for (CertificatePinDto dto : entry.getValue()) { - pins.add(new CertificatePin(dto.pin, dto.algorithm)); - } - builder.addPins(entry.getKey(), pins); + for (Map.Entry> entry : certificatePins.entrySet()) { + builder.addPins(entry.getKey(), entry.getValue()); } return builder @@ -38,11 +32,4 @@ public static CertificatePinningConfiguration getCertificatePinningConfiguration return null; } - - private static class CertificatePinDto { - @SerializedName("pin") - byte[] pin; - @SerializedName("algo") - String algorithm; - } } diff --git a/main/src/main/java/io/split/android/client/utils/Json.java b/main/src/main/java/io/split/android/client/utils/Json.java index a4c4e2e9c..bb97eea95 100644 --- a/main/src/main/java/io/split/android/client/utils/Json.java +++ b/main/src/main/java/io/split/android/client/utils/Json.java @@ -15,6 +15,8 @@ import java.util.Set; import io.split.android.client.dtos.KeyImpression; +import io.split.android.client.network.CertificatePin; +import io.split.android.client.network.CertificatePinSerializer; import io.split.android.client.service.impressions.KeyImpressionSerializer; import io.split.android.client.utils.serializer.DoubleSerializer; @@ -24,6 +26,7 @@ public class Json { .serializeNulls() .registerTypeAdapter(Double.class, new DoubleSerializer()) .registerTypeAdapter(KeyImpression.class, new KeyImpressionSerializer()) + .registerTypeAdapter(CertificatePin.class, new CertificatePinSerializer()) .create(); private static volatile Gson mNonNullJson; diff --git a/main/src/test/java/io/split/android/client/network/CertificatePinSerializerTest.java b/main/src/test/java/io/split/android/client/network/CertificatePinSerializerTest.java new file mode 100644 index 000000000..0dfc1aad5 --- /dev/null +++ b/main/src/test/java/io/split/android/client/network/CertificatePinSerializerTest.java @@ -0,0 +1,129 @@ +package io.split.android.client.network; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Set; + +public class CertificatePinSerializerTest { + + private Gson mGson; + + @Before + public void setUp() { + mGson = new GsonBuilder() + .registerTypeAdapter(CertificatePin.class, new CertificatePinSerializer()) + .create(); + } + + @Test + public void serializeSinglePin() { + CertificatePin pin = new CertificatePin(new byte[]{1, 2, 3}, "sha256"); + + String json = mGson.toJson(pin); + + assertEquals("{\"algo\":\"sha256\",\"pin\":[1,2,3]}", json); + } + + @Test + public void serializeNegativeByteValues() { + CertificatePin pin = new CertificatePin(new byte[]{-80, 50, -99, -126, 11}, "sha256"); + + String json = mGson.toJson(pin); + + assertEquals("{\"algo\":\"sha256\",\"pin\":[-80,50,-99,-126,11]}", json); + } + + @Test + public void deserializeSinglePin() { + String json = "{\"algo\":\"sha1\",\"pin\":[-116,-73,-94,-80,55]}"; + + CertificatePin pin = mGson.fromJson(json, CertificatePin.class); + + assertNotNull(pin); + assertEquals("sha1", pin.getAlgorithm()); + assertArrayEquals(new byte[]{-116, -73, -94, -80, 55}, pin.getPin()); + } + + @Test + public void roundTripPreservesData() { + CertificatePin original = new CertificatePin(new byte[]{-116, -123, 30, -25}, "sha256"); + + String json = mGson.toJson(original); + CertificatePin deserialized = mGson.fromJson(json, CertificatePin.class); + + assertNotNull(deserialized); + assertEquals(original.getAlgorithm(), deserialized.getAlgorithm()); + assertArrayEquals(original.getPin(), deserialized.getPin()); + } + + @Test + public void roundTripMapOfSets() { + String expectedJson = "{\"events.split.io\":[{\"algo\":\"sha256\",\"pin\":[-80,50,-99,-126,11]},{\"algo\":\"sha1\",\"pin\":[-116,-73,-94,-80,55]}],\"sdk.split.io\":[{\"algo\":\"sha256\",\"pin\":[-116,-123,30,-25]}]}"; + + Type type = new TypeToken>>() { + }.getType(); + Map> deserialized = mGson.fromJson(expectedJson, type); + + assertNotNull(deserialized); + assertEquals(2, deserialized.size()); + assertEquals(2, deserialized.get("events.split.io").size()); + assertEquals(1, deserialized.get("sdk.split.io").size()); + + // Re-serialize and deserialize + String reserialized = mGson.toJson(deserialized, type); + Map> roundTripped = mGson.fromJson(reserialized, type); + + assertNotNull(roundTripped); + assertEquals(deserialized.size(), roundTripped.size()); + for (Map.Entry> entry : deserialized.entrySet()) { + Set originalPins = entry.getValue(); + Set roundTrippedPins = roundTripped.get(entry.getKey()); + assertNotNull(roundTrippedPins); + assertEquals(originalPins.size(), roundTrippedPins.size()); + assertEquals(originalPins, roundTrippedPins); + } + } + + @Test + public void deserializeWithUnknownFieldsIsIgnored() { + String json = "{\"algo\":\"sha256\",\"pin\":[1,2],\"extra\":\"ignored\"}"; + + CertificatePin pin = mGson.fromJson(json, CertificatePin.class); + + assertNotNull(pin); + assertEquals("sha256", pin.getAlgorithm()); + assertArrayEquals(new byte[]{1, 2}, pin.getPin()); + } + + @Test + public void deserializeMissingFieldsResultsInNulls() { + String json = "{}"; + + CertificatePin pin = mGson.fromJson(json, CertificatePin.class); + + assertNotNull(pin); + assertNull(pin.getAlgorithm()); + assertNull(pin.getPin()); + } + + @Test + public void serializeEmptyPinArray() { + CertificatePin pin = new CertificatePin(new byte[]{}, "sha256"); + + String json = mGson.toJson(pin); + + assertEquals("{\"algo\":\"sha256\",\"pin\":[]}", json); + } +}