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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
## ZeroBounce Java SDK


## Security

- Keep API keys on a trusted server. Do not embed them in mobile apps or browser JavaScript that untrusted users can inspect.
- Custom API base URLs (when supported) must use `https://`. Do not pass end-user-controlled hosts into those settings.
- Request URLs include `api_key` as a query parameter (ZeroBounce API contract). Do not log full request URLs or enable payload debug logging in production.

[![Maven Central](https://img.shields.io/maven-central/v/com.zerobounce.java/zerobouncesdk?label=Latest%20release)](https://mvnrepository.com/artifact/com.zerobounce.java/zerobouncesdk)

This SDK contains methods for interacting easily with ZeroBounce API.
Expand Down
15 changes: 13 additions & 2 deletions zero-bounce-sdk/src/main/java/com/zerobounce/ZeroBounceSDK.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public static void setLogPayloads(boolean enabled) {
logPayloads = enabled;
}

private static final int DEFAULT_TIMEOUT_MILLIS = 120_000;

String apiBaseUrl = "https://api.zerobounce.net/v2";
private final String bulkApiBaseUrl = "https://bulkapi.zerobounce.net/v2";
private final String bulkApiScoringBaseUrl = "https://bulkapi.zerobounce.net/v2/scoring";
Expand All @@ -97,13 +99,13 @@ private ZeroBounceSDK() {
/**
* Initializes the SDK.
* <p>
* [timeoutInMillis] is set to 0 (no timeout by default)
* [timeoutInMillis] is set to 120000 (120 seconds) by default
*
* @param apiKey the API key
*/
public void initialize(String apiKey) {
this.apiKey = apiKey;
timeoutInMillis = 0;
timeoutInMillis = DEFAULT_TIMEOUT_MILLIS;
}

/**
Expand All @@ -125,11 +127,19 @@ public void initialize(String apiKey, int timeoutInMillis) {
*/
public void initialize(String apiKey, @Nullable String apiBaseUrl) {
this.apiKey = apiKey;
timeoutInMillis = DEFAULT_TIMEOUT_MILLIS;
if (apiBaseUrl != null) {
requireHttpsUrl(apiBaseUrl);
this.apiBaseUrl = apiBaseUrl;
}
}

static void requireHttpsUrl(String url) {
if (url == null || !url.regionMatches(true, 0, "https://", 0, 8)) {
throw new IllegalArgumentException("apiBaseUrl must be an https:// URL");
}
}

/**
* Validates the given [email].
*
Expand Down Expand Up @@ -1181,6 +1191,7 @@ private <T> void sendRequest(
logDebug("ZeroBounceSDK::sendRequest executing " + httpMethod + " " + urlPath);

con.setConnectTimeout(timeoutInMillis);
con.setReadTimeout(timeoutInMillis);

int status = con.getResponseCode();
logDebug("ZeroBounceSDK::sendRequest status: " + status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ public void setUp() {
zeroBounceSDK.initialize(API_KEY);
}

@Test
public void requireHttpsUrl_rejectsHttp() {
assertThrows(IllegalArgumentException.class, () -> ZeroBounceSDK.requireHttpsUrl("http://example.com"));
assertThrows(IllegalArgumentException.class, () -> ZeroBounceSDK.requireHttpsUrl("file:///etc/passwd"));
ZeroBounceSDK.requireHttpsUrl("https://api.zerobounce.net/v2");
}

@Test
public void initialize_rejectsNonHttpsBaseUrl() {
assertThrows(IllegalArgumentException.class, () -> zeroBounceSDK.initialize(API_KEY, "http://evil.example"));
}

@Test
public void validateEmail_ReturnsSuccess() throws Exception {
// Prepare mock response and add it to the server
Expand Down