diff --git a/src/main/java/com/mindee/input/URLInputSource.java b/src/main/java/com/mindee/input/URLInputSource.java
index 352bc2834..0d5bae111 100644
--- a/src/main/java/com/mindee/input/URLInputSource.java
+++ b/src/main/java/com/mindee/input/URLInputSource.java
@@ -7,18 +7,13 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
-import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
-import java.util.Locale;
import lombok.Getter;
/**
@@ -62,78 +57,13 @@ public static Builder builder(URL url) {
* Ensures the URL can be safely sent to the Mindee server.
*
*
- * Rejects any URL that could be used for Server-Side Request Forgery (SSRF):
- *
- * - non-HTTPS schemes,
- * - embedded userinfo (e.g. {@code https://user:pass@host}),
- * - loopback hostnames ({@code localhost}, {@code *.localhost}),
- * - hosts that resolve to loopback, link-local, site-local (RFC 1918),
- * any-local ({@code 0.0.0.0}), multicast, IPv6 unique-local
- * ({@code fc00::/7}) or carrier-grade NAT ({@code 100.64.0.0/10})
- * addresses.
- *
+ * Rejects any URL that don't follow an HTTPS scheme.
+ *
*/
public void validateSecure() {
if (!"https".equalsIgnoreCase(this.url.getProtocol())) {
throw new MindeeException("Only HTTPS source URLs are allowed");
}
- String userInfo = this.url.getUserInfo();
- if (userInfo != null && !userInfo.isEmpty()) {
- throw new MindeeException("Source URLs must not embed user credentials");
- }
- String host = this.url.getHost();
- if (host == null || host.isEmpty()) {
- throw new MindeeException("Source URL is missing a host");
- }
- String lowerHost = host.toLowerCase(Locale.ROOT);
- if (
- "localhost".equals(lowerHost)
- || lowerHost.endsWith(".localhost")
- || "ip6-localhost".equals(lowerHost)
- || "ip6-loopback".equals(lowerHost)
- ) {
- throw new MindeeException("Loopback hostnames are not allowed: " + host);
- }
-
- InetAddress[] addresses;
- try {
- addresses = InetAddress.getAllByName(host);
- } catch (UnknownHostException e) {
- throw new MindeeException("Unable to resolve source URL host: " + host, e);
- }
- for (InetAddress addr : addresses) {
- if (isDisallowedAddress(addr)) {
- throw new MindeeException(
- "Source URL host resolves to a disallowed address: " + addr.getHostAddress()
- );
- }
- }
- }
-
- private static boolean isDisallowedAddress(InetAddress addr) {
- return addr.isLoopbackAddress()
- || addr.isLinkLocalAddress()
- || addr.isSiteLocalAddress()
- || addr.isAnyLocalAddress()
- || addr.isMulticastAddress()
- || isUniqueLocalIpv6(addr)
- || isCarrierGradeNat(addr);
- }
-
- private static boolean isUniqueLocalIpv6(InetAddress addr) {
- if (!(addr instanceof Inet6Address)) {
- return false;
- }
- byte[] raw = addr.getAddress();
- return (raw[0] & 0xFE) == 0xFC;
- }
-
- private static boolean isCarrierGradeNat(InetAddress addr) {
- if (!(addr instanceof Inet4Address)) {
- return false;
- }
- byte[] raw = addr.getAddress();
- return (raw[0] & 0xFF) == 100 && (raw[1] & 0xC0) == 0x40;
}
/**
@@ -191,7 +121,7 @@ private HttpURLConnection handleRedirects(HttpURLConnection connection) throws I
connection.disconnect();
HttpURLConnection newConnection = createConnection(new URL(newUrl));
- return handleRedirects(newConnection); // Recursive call to handle multiple redirects
+ return handleRedirects(newConnection);
}
return connection;
}
diff --git a/src/main/java/com/mindee/v2/MindeeClient.java b/src/main/java/com/mindee/v2/MindeeClient.java
index 10f1972aa..4b2bda9be 100644
--- a/src/main/java/com/mindee/v2/MindeeClient.java
+++ b/src/main/java/com/mindee/v2/MindeeClient.java
@@ -9,7 +9,6 @@
import com.mindee.v2.http.MindeeHttpExceptionV2;
import com.mindee.v2.parsing.CommonResponse;
import com.mindee.v2.parsing.JobResponse;
-import com.mindee.v2.parsing.JobStatus;
import com.mindee.v2.parsing.error.ErrorResponse;
import com.mindee.v2.parsing.search.SearchResponse;
import com.mindee.v2.product.extraction.ExtractionResponse;
@@ -244,10 +243,10 @@ private TResponse pollAndFetch(
interruptibleSleep((long) (currentIntervalSec * 1000), pollingOptions);
resp = getJob(initialJob.getJob().getId());
- if (resp.getJob().getStatusEnum() == JobStatus.Failed) {
+ if (resp.getJob().getStatus().equals("Failed")) {
attempts = max;
}
- if (resp.getJob().getStatusEnum() == JobStatus.Processed) {
+ if (resp.getJob().getStatus().equals("Processed")) {
return getResult(responseClass, resp.getJob().getId());
}
currentIntervalSec = Math.min(currentIntervalSec * backoffMultiplier, maxIntervalSec);
diff --git a/src/main/java/com/mindee/v2/parsing/Job.java b/src/main/java/com/mindee/v2/parsing/Job.java
index 56cb0bf20..3b67ee6d0 100644
--- a/src/main/java/com/mindee/v2/parsing/Job.java
+++ b/src/main/java/com/mindee/v2/parsing/Job.java
@@ -48,14 +48,6 @@ public final class Job {
@JsonProperty("status")
private String status;
- /**
- * Typed view of {@link #status}. Returns {@link JobStatus#Unknown} for values
- * the SDK doesn't recognise, or {@code null} if the raw status is {@code null}.
- */
- public JobStatus getStatusEnum() {
- return JobStatus.fromValue(status);
- }
-
/**
* Status of the job.
*/
diff --git a/src/main/java/com/mindee/v2/parsing/JobStatus.java b/src/main/java/com/mindee/v2/parsing/JobStatus.java
deleted file mode 100644
index b90897df7..000000000
--- a/src/main/java/com/mindee/v2/parsing/JobStatus.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.mindee.v2.parsing;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-/**
- * Possible statuses returned by the API for an asynchronous {@link Job}.
- */
-public enum JobStatus {
- Processing("Processing"),
- Processed("Processed"),
- Failed("Failed"),
- Unknown("Unknown");
-
- private final String value;
-
- JobStatus(String value) {
- this.value = value;
- }
-
- @JsonValue
- public String getValue() {
- return value;
- }
-
- /**
- * Deserializes a status string coming from the API. Unknown values fall back to
- * {@link #Unknown} rather than failing so newly added statuses don't break clients.
- */
- @JsonCreator
- public static JobStatus fromValue(String value) {
- if (value == null) {
- return null;
- }
- for (JobStatus status : values()) {
- if (status.value.equalsIgnoreCase(value)) {
- return status;
- }
- }
- return Unknown;
- }
-}
diff --git a/src/test/java/com/mindee/input/URLInputSourceTest.java b/src/test/java/com/mindee/input/URLInputSourceTest.java
index 5845ec613..7db6053f0 100644
--- a/src/test/java/com/mindee/input/URLInputSourceTest.java
+++ b/src/test/java/com/mindee/input/URLInputSourceTest.java
@@ -101,77 +101,6 @@ void httpScheme_isRejected() throws MalformedURLException {
MindeeException e = assertThrows(MindeeException.class, src::validateSecure);
assertTrue(e.getMessage().contains("HTTPS"));
}
-
- @Test
- void userInfo_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://user:pass@example.com/file.pdf").build();
- MindeeException e = assertThrows(MindeeException.class, src::validateSecure);
- assertTrue(e.getMessage().contains("credentials"));
- }
-
- @Test
- void loopbackHostname_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://localhost/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
-
- @Test
- void subLocalhostHostname_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://foo.localhost/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
-
- @Test
- void loopbackIpv4_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://127.0.0.1/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
-
- @Test
- void loopbackIpv6_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://[::1]/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
-
- @Test
- void anyLocalIpv4_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://0.0.0.0/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
-
- @Test
- void privateRfc1918_isRejected() throws MalformedURLException {
- for (String host : new String[] { "10.0.0.1", "172.16.0.1", "192.168.1.1" }) {
- var src = URLInputSource.builder("https://" + host + "/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure, "expected rejection for " + host);
- }
- }
-
- @Test
- void linkLocalIpv4_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://169.254.169.254/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
-
- @Test
- void cgnat_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://100.64.0.1/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
-
- @Test
- void uniqueLocalIpv6_isRejected() throws MalformedURLException {
- var src = URLInputSource.builder("https://[fd00::1]/file.pdf").build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
-
- @Test
- void unresolvableHost_isRejected() throws MalformedURLException {
- var src = URLInputSource
- .builder("https://this-host-should-not-exist.invalid/file.pdf")
- .build();
- assertThrows(MindeeException.class, src::validateSecure);
- }
}
static class TestableURLInputSource extends URLInputSource {
diff --git a/src/test/java/com/mindee/v2/MindeeClientIT.java b/src/test/java/com/mindee/v2/MindeeClientIT.java
index c0e069ed2..c8386c921 100644
--- a/src/test/java/com/mindee/v2/MindeeClientIT.java
+++ b/src/test/java/com/mindee/v2/MindeeClientIT.java
@@ -251,7 +251,7 @@ void getResultFromUrl_mustSucceed() throws IOException, InterruptedException {
for (int i = 0; i < 80 && resultUrl == null; i++) {
Thread.sleep(1500);
var poll = mindeeClient.getJob(jobId);
- if (poll.getJob().getStatusEnum() == com.mindee.v2.parsing.JobStatus.Processed) {
+ if (poll.getJob().getStatus().equals("Processed")) {
resultUrl = poll.getJob().getResultUrl();
}
}
diff --git a/src/test/java/com/mindee/v2/parsing/JobTest.java b/src/test/java/com/mindee/v2/parsing/JobTest.java
index 6a3f3f880..30996ee1a 100644
--- a/src/test/java/com/mindee/v2/parsing/JobTest.java
+++ b/src/test/java/com/mindee/v2/parsing/JobTest.java
@@ -28,7 +28,6 @@ void whenProcessing_mustHaveValidProperties() throws IOException {
var job = response.getJob();
assertNotNull(job);
assertEquals("Processing", job.getStatus());
- assertEquals(JobStatus.Processing, job.getStatusEnum());
assertNotNull(job.getCreatedAt());
assertNull(job.getCompletedAt());
assertNull(job.getResultUrl());
@@ -46,7 +45,6 @@ void whenProcessing_mustHaveValidProperties() throws IOException {
var job = response.getJob();
assertNotNull(job);
assertEquals("Processed", job.getStatus());
- assertEquals(JobStatus.Processed, job.getStatusEnum());
assertNotNull(job.getCreatedAt());
assertNotNull(job.getCompletedAt());
assertNotNull(job.getResultUrl());