Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/main/java/io/split/android/client/SplitClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,9 @@ private HttpProxy parseProxyHost(String proxyUri) {
}
}
String host = String.format("%s%s", uri.getHost(), uri.getPath());
return new HttpProxy(host, port, username, password);
return HttpProxy.newBuilder(host, port)
.basicAuth(username, password)
.build();
} catch (IllegalArgumentException e) {
Logger.e("Proxy URI not valid: " + e.getLocalizedMessage());
throw new IllegalArgumentException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URI;
Expand All @@ -28,7 +29,11 @@ public class HttpClientImpl implements HttpClient {
@Nullable
private final Proxy mProxy;
@Nullable
private final HttpProxy mHttpProxy;
@Nullable
private final SplitUrlConnectionAuthenticator mProxyAuthenticator;
@Nullable
private final ProxyCredentialsProvider mProxyCredentialsProvider;
private final long mReadTimeout;
private final long mConnectionTimeout;
@Nullable
Expand All @@ -42,14 +47,17 @@ public class HttpClientImpl implements HttpClient {

HttpClientImpl(@Nullable HttpProxy proxy,
@Nullable SplitAuthenticator proxyAuthenticator,
@Nullable ProxyCredentialsProvider proxyCredentialsProvider,
long readTimeout,
long connectionTimeout,
@Nullable DevelopmentSslConfig developmentSslConfig,
@Nullable SSLSocketFactory sslSocketFactory,
@NonNull UrlSanitizer urlSanitizer,
@Nullable CertificateChecker certificateChecker) {
mHttpProxy = proxy;
mProxy = initializeProxy(proxy);
mProxyAuthenticator = initializeProxyAuthenticator(proxy, proxyAuthenticator);
mProxyCredentialsProvider = proxyCredentialsProvider;
mReadTimeout = readTimeout;
mConnectionTimeout = connectionTimeout;
mDevelopmentSslConfig = developmentSslConfig;
Expand All @@ -73,7 +81,9 @@ public HttpRequest request(URI uri, HttpMethod requestMethod, String body, Map<S
body,
newHeaders,
mProxy,
mHttpProxy,
mProxyAuthenticator,
mProxyCredentialsProvider,
mReadTimeout,
mConnectionTimeout,
mDevelopmentSslConfig,
Expand Down Expand Up @@ -101,7 +111,9 @@ public HttpStreamRequest streamRequest(URI uri) {
mDevelopmentSslConfig,
mSslSocketFactory,
mUrlSanitizer,
mCertificateChecker);
mCertificateChecker,
mHttpProxy,
mProxyCredentialsProvider);
}

@Override
Expand Down Expand Up @@ -177,7 +189,9 @@ public String encode(byte[] bytes) {
}

public static class Builder {

private SplitAuthenticator mProxyAuthenticator;
private ProxyCredentialsProvider mProxyCredentialsProvider;
private HttpProxy mProxy;
private long mReadTimeout = -1;
private long mConnectionTimeout = -1;
Expand All @@ -187,6 +201,7 @@ public static class Builder {
private UrlSanitizer mUrlSanitizer;
private CertificatePinningConfiguration mCertificatePinningConfiguration;
private CertificateChecker mCertificateChecker;
private Base64Decoder mBase64Decoder = new DefaultBase64Decoder();

public Builder setContext(Context context) {
mHostAppContext = context;
Expand Down Expand Up @@ -235,16 +250,32 @@ public Builder setCertificatePinningConfiguration(CertificatePinningConfiguratio
return this;
}

public Builder setProxyCredentialsProvider(@NonNull ProxyCredentialsProvider proxyCredentialsProvider) {
mProxyCredentialsProvider = proxyCredentialsProvider;
return this;
}

@VisibleForTesting
Builder setCertificateChecker(CertificateChecker certificateChecker) {
mCertificateChecker = certificateChecker;
return this;
}

@VisibleForTesting
Builder setBase64Decoder(Base64Decoder base64Decoder) {
mBase64Decoder = base64Decoder;
return this;
}

public HttpClient build() {
if (mDevelopmentSslConfig == null) {
if (LegacyTlsUpdater.couldBeOld()) {
LegacyTlsUpdater.update(mHostAppContext);
}

if (mProxy != null) {
mSslSocketFactory = createSslSocketFactoryFromProxy();
} else {
try {
mSslSocketFactory = new Tls12OnlySocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
Expand All @@ -271,12 +302,34 @@ public HttpClient build() {
return new HttpClientImpl(
mProxy,
mProxyAuthenticator,
mProxyCredentialsProvider,
mReadTimeout,
mConnectionTimeout,
mDevelopmentSslConfig,
mSslSocketFactory,
(mUrlSanitizer == null) ? new UrlSanitizerImpl() : mUrlSanitizer,
certificateChecker);
}

private SSLSocketFactory createSslSocketFactoryFromProxy() {
ProxySslSocketFactoryProviderImpl factoryProvider = new ProxySslSocketFactoryProviderImpl(mBase64Decoder);
try {
if (mProxy.getClientCertStream() != null && mProxy.getClientKeyStream() != null) {
try (InputStream caInput = mProxy.getCaCertStream();
InputStream certInput = mProxy.getClientCertStream();
InputStream keyInput = mProxy.getClientKeyStream()) {
Logger.v("Custom proxy CA cert and client cert/key loaded for proxy: " + mProxy.getHost());
return factoryProvider.create(caInput, certInput, keyInput);
}
} else if (mProxy.getCaCertStream() != null) {
try (InputStream caInput = mProxy.getCaCertStream()) {
return factoryProvider.create(caInput);
}
}
} catch (Exception e) {
Logger.e("Failed to create SSLSocketFactory for proxy: " + mProxy.getHost() + ", error: " + e.getMessage());
}
return null;
}
}
}
95 changes: 73 additions & 22 deletions src/main/java/io/split/android/client/network/HttpProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,95 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.InputStream;

public class HttpProxy {

final private String host;
final private int port;
final private String username;
final private String password;
private final @NonNull String mHost;
private final int mPort;
private final @Nullable String mUsername;
private final @Nullable String mPassword;
private final @Nullable InputStream mClientCertStream;
private final @Nullable InputStream mClientKeyStream;
private final @Nullable InputStream mCaCertStream;

public HttpProxy(@NonNull String host, int port) {
this(host, port, null, null);
private HttpProxy(Builder builder) {
mHost = builder.mHost;
mPort = builder.mPort;
mUsername = builder.mUsername;
mPassword = builder.mPassword;
mClientCertStream = builder.mClientCertStream;
mClientKeyStream = builder.mClientKeyStream;
mCaCertStream = builder.mCaCertStream;
}

public HttpProxy(@NonNull String host, int port, @Nullable String username, @Nullable String password) {
checkNotNull(host);
public @NonNull String getHost() {
return mHost;
}

this.host = host;
this.port = port;
this.username = username;
this.password = password;
public int getPort() {
return mPort;
}

public String getHost() {
return host;
public @Nullable String getUsername() {
return mUsername;
}

public int getPort() {
return port;
public @Nullable String getPassword() {
return mPassword;
}

public @Nullable InputStream getClientCertStream() {
return mClientCertStream;
}

public @Nullable InputStream getClientKeyStream() {
return mClientKeyStream;
}

public String getUsername() {
return username;
public @Nullable InputStream getCaCertStream() {
return mCaCertStream;
}

public String getPassword() {
return password;
public static Builder newBuilder(@NonNull String host, int port) {
return new Builder(host, port);
}

public boolean usesCredentials() {
return username == null;
public static class Builder {
private final @NonNull String mHost;
private final int mPort;
private @Nullable String mUsername;
private @Nullable String mPassword;
private @Nullable InputStream mClientCertStream;
private @Nullable InputStream mClientKeyStream;
private @Nullable InputStream mCaCertStream;

private Builder(@NonNull String host, int port) {
checkNotNull(host);
mHost = host;
mPort = port;
}

public Builder basicAuth(@NonNull String username, @NonNull String password) {
mUsername = username;
mPassword = password;
return this;
}

public Builder proxyCacert(@NonNull InputStream caCertStream) {
mCaCertStream = caCertStream;
return this;
}

public Builder mtlsAuth(@NonNull InputStream certStream, @NonNull InputStream keyStream, @NonNull InputStream caCertStream) {
mClientCertStream = certStream;
mClientKeyStream = keyStream;
mCaCertStream = caCertStream;
return this;
}

public HttpProxy build() {
return new HttpProxy(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,54 @@

class HttpRequestHelper {

static HttpURLConnection openConnection(@Nullable Proxy proxy,
@Nullable SplitUrlConnectionAuthenticator proxyAuthenticator,
@NonNull URL url,
@NonNull HttpMethod method,
@NonNull Map<String, String> headers,
boolean useProxyAuthentication) throws IOException {
private static final ProxyCacertConnectionHandler mConnectionHandler = new ProxyCacertConnectionHandler();

static HttpURLConnection createConnection(@NonNull URL url,
@Nullable Proxy proxy,
@Nullable HttpProxy httpProxy,
@Nullable SplitUrlConnectionAuthenticator proxyAuthenticator,
@NonNull HttpMethod method,
@NonNull Map<String, String> headers,
boolean useProxyAuthentication,
@Nullable SSLSocketFactory sslSocketFactory,
@Nullable ProxyCredentialsProvider proxyCredentialsProvider,
@Nullable String body) throws IOException {

if (httpProxy != null && sslSocketFactory != null && (httpProxy.getCaCertStream() != null || httpProxy.getClientCertStream() != null)) {
try {
HttpResponse response = mConnectionHandler.executeRequest(
httpProxy,
url,
method,
headers,
body,
sslSocketFactory,
proxyCredentialsProvider
);

return new HttpResponseConnectionAdapter(url, response, response.getServerCertificates());
} catch (UnsupportedOperationException e) {
// Fall through to standard handling
}
}

return openConnection(proxy, httpProxy, proxyAuthenticator, url, method, headers, useProxyAuthentication);
}

private static HttpURLConnection openConnection(@Nullable Proxy proxy,
@Nullable HttpProxy httpProxy,
@Nullable SplitUrlConnectionAuthenticator proxyAuthenticator,
@NonNull URL url,
@NonNull HttpMethod method,
@NonNull Map<String, String> headers,
boolean useProxyAuthentication) throws IOException {

// Check if we need custom SSL proxy handling
if (httpProxy != null && (httpProxy.getCaCertStream() != null || httpProxy.getClientCertStream() != null)) {
throw new IOException("SSL proxy scenarios require custom handling - use executeRequest method instead");
}

// Standard HttpURLConnection proxy handling
HttpURLConnection connection;
if (proxy != null) {
connection = (HttpURLConnection) url.openConnection(proxy);
Expand Down Expand Up @@ -84,7 +126,7 @@ static void checkPins(HttpURLConnection connection, @Nullable CertificateChecker

private static void addHeaders(HttpURLConnection request, Map<String, String> headers) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
if (entry == null) {
if (entry == null || entry.getKey() == null) {
continue;
}

Expand Down
Loading
Loading