mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Regenerate tool models and require an allowlisted model URL
This commit is contained in:
+3
-3
@@ -170,9 +170,9 @@ public class FormDetectionModelManager {
|
||||
throw new IllegalStateException(
|
||||
"Model '" + modelId + "' has no download URL/checksum configured yet");
|
||||
}
|
||||
String scheme = URI.create(url).getScheme();
|
||||
if (!"https".equalsIgnoreCase(scheme) && !"http".equalsIgnoreCase(scheme)) {
|
||||
throw new IllegalArgumentException("Model URL must be http(s): " + url);
|
||||
// Same allowlist the download enforces, so a bad catalog entry fails here, not mid-install.
|
||||
if (!url.startsWith(ALLOWED_MODEL_URL_PREFIX)) {
|
||||
throw new IllegalArgumentException("Model URL is not on the allowlist: " + url);
|
||||
}
|
||||
if (!SHA256_HEX.matcher(sha).matches()) {
|
||||
throw new IllegalArgumentException("Checksum must be a 64-char hex SHA-256");
|
||||
|
||||
+20
-6
@@ -38,6 +38,9 @@ import stirling.software.proprietary.formdetection.model.ModelCatalogEntry;
|
||||
|
||||
class FormDetectionModelManagerTest {
|
||||
|
||||
/** Catalog URLs must satisfy the production allowlist; the stub below serves them locally. */
|
||||
private static final String ALLOWED_URL = "https://huggingface.co";
|
||||
|
||||
private HttpServer server;
|
||||
private byte[] modelBytes;
|
||||
private String modelSha;
|
||||
@@ -103,8 +106,9 @@ class FormDetectionModelManagerTest {
|
||||
return new FormDetectionModelManager(paths, catalog, props, ep, noEngine()) {
|
||||
@Override
|
||||
HttpURLConnection openModelDownload(String url) throws IOException {
|
||||
String local = "http://127.0.0.1:" + port + URI.create(url).getPath();
|
||||
HttpURLConnection conn =
|
||||
(HttpURLConnection) URI.create(url).toURL().openConnection();
|
||||
(HttpURLConnection) URI.create(local).toURL().openConnection();
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(10000);
|
||||
return conn;
|
||||
@@ -128,7 +132,7 @@ class FormDetectionModelManagerTest {
|
||||
void installsDownloadsVerifiesAndPublishesAtomically(@TempDir Path dir) throws Exception {
|
||||
EndpointConfiguration ep = Mockito.mock(EndpointConfiguration.class);
|
||||
FormDetectionModelManager m =
|
||||
manager(dir, entry("http://127.0.0.1:" + port + "/model.onnx", modelSha), ep);
|
||||
manager(dir, entry(ALLOWED_URL + "/model.onnx", modelSha), ep);
|
||||
|
||||
m.startInstall("test-model");
|
||||
awaitState(m, "ready", 5000);
|
||||
@@ -145,7 +149,7 @@ class FormDetectionModelManagerTest {
|
||||
void rejectsChecksumMismatchAndLeavesNoFile(@TempDir Path dir) throws Exception {
|
||||
EndpointConfiguration ep = Mockito.mock(EndpointConfiguration.class);
|
||||
FormDetectionModelManager m =
|
||||
manager(dir, entry("http://127.0.0.1:" + port + "/model.onnx", "0".repeat(64)), ep);
|
||||
manager(dir, entry(ALLOWED_URL + "/model.onnx", "0".repeat(64)), ep);
|
||||
|
||||
m.startInstall("test-model");
|
||||
awaitState(m, "failed", 5000);
|
||||
@@ -174,7 +178,7 @@ class FormDetectionModelManagerTest {
|
||||
FormDetectionModelManager m =
|
||||
manager(
|
||||
dir,
|
||||
entry("http://127.0.0.1:" + port + "/gated.onnx", modelSha),
|
||||
entry(ALLOWED_URL + "/gated.onnx", modelSha),
|
||||
Mockito.mock(EndpointConfiguration.class));
|
||||
|
||||
m.startInstall("test-model"); // begins, blocks in handler
|
||||
@@ -191,6 +195,16 @@ class FormDetectionModelManagerTest {
|
||||
assertThrows(IllegalStateException.class, () -> m.startInstall("test-model"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCatalogUrlOutsideAllowlist(@TempDir Path dir) {
|
||||
FormDetectionModelManager m =
|
||||
manager(
|
||||
dir,
|
||||
entry("http://127.0.0.1:" + port + "/model.onnx", modelSha),
|
||||
Mockito.mock(EndpointConfiguration.class));
|
||||
assertThrows(IllegalArgumentException.class, () -> m.startInstall("test-model"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDownloadUrlOutsideAllowlist(@TempDir Path dir) {
|
||||
RuntimePathConfig paths = Mockito.mock(RuntimePathConfig.class);
|
||||
@@ -216,7 +230,7 @@ class FormDetectionModelManagerTest {
|
||||
FormDetectionModelManager m =
|
||||
manager(
|
||||
dir,
|
||||
entry("http://127.0.0.1:" + port + "/model.onnx", modelSha),
|
||||
entry(ALLOWED_URL + "/model.onnx", modelSha),
|
||||
Mockito.mock(EndpointConfiguration.class));
|
||||
assertThrows(IllegalArgumentException.class, () -> m.startInstall("unknown"));
|
||||
}
|
||||
@@ -227,7 +241,7 @@ class FormDetectionModelManagerTest {
|
||||
Path preDir = root.resolve("preinstalled");
|
||||
Files.createDirectories(preDir);
|
||||
Files.write(preDir.resolve("test-model.onnx"), modelBytes);
|
||||
ModelCatalogEntry e = entry("http://127.0.0.1:" + port + "/model.onnx", modelSha);
|
||||
ModelCatalogEntry e = entry(ALLOWED_URL + "/model.onnx", modelSha);
|
||||
|
||||
ApplicationProperties props = new ApplicationProperties();
|
||||
props.getFormDetection().setPreinstalledModelDir(preDir.toString());
|
||||
|
||||
@@ -725,6 +725,9 @@ class OcrPdfParams(ApiModel):
|
||||
)
|
||||
ocr_type: OcrType = Field(..., description="Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'")
|
||||
remove_images_after: bool | None = Field(None, description="Remove images from the output PDF if set to true")
|
||||
rotate_pages: bool | None = Field(
|
||||
None, description="Auto-correct page orientation (90/180/270) using Tesseract OSD if set to true"
|
||||
)
|
||||
sidecar: bool | None = Field(None, description="Include OCR text in a sidecar text file if set to true")
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ export async function applyFields(
|
||||
const cb = form.createCheckBox(name);
|
||||
cb.addToPage(page, { x: r.x, y: r.y, width: r.w, height: r.h });
|
||||
} else {
|
||||
// pdf-lib has no first-class signature widget; a text field keeps it fillable.
|
||||
// Signature and radio both land here: FormUtils.addFields coerces them to text too,
|
||||
// so a document is identical whichever side applied the fields.
|
||||
const tf = form.createTextField(name);
|
||||
tf.addToPage(page, { x: r.x, y: r.y, width: r.w, height: r.h });
|
||||
}
|
||||
|
||||
@@ -1022,6 +1022,10 @@ export interface ProcessPdfWithOcrRequest {
|
||||
* Remove images from the output PDF if set to true
|
||||
*/
|
||||
removeImagesAfter?: boolean;
|
||||
/**
|
||||
* Auto-correct page orientation (90/180/270) using Tesseract OSD if set to true
|
||||
*/
|
||||
rotatePages?: boolean;
|
||||
/**
|
||||
* Include OCR text in a sidecar text file if set to true
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user