This commit is contained in:
Anthony Stirling 2025-07-01 13:04:45 +01:00
parent 5591a655d0
commit b2a0868f5b
4 changed files with 129 additions and 39 deletions

View File

@ -36,13 +36,14 @@ public class EndpointConfiguration {
public void enableEndpoint(String endpoint) { public void enableEndpoint(String endpoint) {
endpointStatuses.put(endpoint, true); endpointStatuses.put(endpoint, true);
log.debug("Enabled endpoint: {}", endpoint);
} }
public void disableEndpoint(String endpoint) { public void disableEndpoint(String endpoint) {
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { if (!Boolean.FALSE.equals(endpointStatuses.get(endpoint))) {
log.debug("Disabling {}", endpoint); log.debug("Disabling endpoint: {}", endpoint);
endpointStatuses.put(endpoint, false);
} }
endpointStatuses.put(endpoint, false);
} }
public Map<String, Boolean> getEndpointStatuses() { public Map<String, Boolean> getEndpointStatuses() {
@ -50,45 +51,82 @@ public class EndpointConfiguration {
} }
public boolean isEndpointEnabled(String endpoint) { public boolean isEndpointEnabled(String endpoint) {
String original = endpoint;
if (endpoint.startsWith("/")) { if (endpoint.startsWith("/")) {
endpoint = endpoint.substring(1); endpoint = endpoint.substring(1);
} }
// Check if endpoint has alternatives (multiple tools can handle it) // Rule 1: Explicit flag wins - if disabled via disableEndpoint(), stay disabled
Set<String> alternatives = endpointAlternatives.get(endpoint); Boolean explicitStatus = endpointStatuses.get(endpoint);
if (alternatives != null && !alternatives.isEmpty()) { if (Boolean.FALSE.equals(explicitStatus)) {
// Endpoint is enabled if ANY of its alternative tools are enabled log.debug("isEndpointEnabled('{}') -> false (explicitly disabled)", original);
for (String toolGroup : alternatives) { return false;
if (isGroupEnabled(toolGroup)) {
return true;
}
}
return false; // All alternative tools are disabled
} }
// Fallback to standard endpoint status check // Rule 2: Functional-group override - check if endpoint belongs to any disabled functional
return endpointStatuses.getOrDefault(endpoint, true); // group
for (String group : endpointGroups.keySet()) {
if (disabledGroups.contains(group) && endpointGroups.get(group).contains(endpoint)) {
// Skip tool groups (qpdf, OCRmyPDF, Ghostscript, LibreOffice, etc.)
if (!isToolGroup(group)) {
log.debug(
"isEndpointEnabled('{}') -> false (functional group '{}' disabled)",
original,
group);
return false;
}
}
}
// Rule 3: Tool-group fallback - check if at least one alternative tool group is enabled
Set<String> alternatives = endpointAlternatives.get(endpoint);
if (alternatives != null && !alternatives.isEmpty()) {
boolean hasEnabledToolGroup =
alternatives.stream()
.anyMatch(toolGroup -> !disabledGroups.contains(toolGroup));
log.debug(
"isEndpointEnabled('{}') -> {} (tool groups check)",
original,
hasEnabledToolGroup);
return hasEnabledToolGroup;
}
// Default: enabled if not explicitly disabled
boolean enabled = !Boolean.FALSE.equals(explicitStatus);
log.debug("isEndpointEnabled('{}') -> {} (default)", original, enabled);
return enabled;
} }
public boolean isGroupEnabled(String group) { public boolean isGroupEnabled(String group) {
// Check if group is explicitly disabled first // Rule 1: If group is explicitly disabled, it stays disabled
if (disabledGroups.contains(group)) { if (disabledGroups.contains(group)) {
log.debug("isGroupEnabled('{}') -> false (explicitly disabled)", group);
return false; return false;
} }
Set<String> endpoints = endpointGroups.get(group); Set<String> endpoints = endpointGroups.get(group);
if (endpoints == null || endpoints.isEmpty()) { if (endpoints == null || endpoints.isEmpty()) {
log.debug("Group '{}' does not exist or has no endpoints", group); log.debug("isGroupEnabled('{}') -> false (no endpoints)", group);
return false; return false;
} }
// Additional check: if all endpoints in group are disabled, consider group disabled // Rule 2: For functional groups, check if all endpoints are enabled
// Rule 3: For tool groups, they're enabled unless explicitly disabled (handled above)
if (isToolGroup(group)) {
log.debug("isGroupEnabled('{}') -> true (tool group not disabled)", group);
return true;
}
// For functional groups, check each endpoint individually
for (String endpoint : endpoints) { for (String endpoint : endpoints) {
if (!isEndpointEnabled(endpoint)) { if (!isEndpointEnabledDirectly(endpoint)) {
log.debug(
"isGroupEnabled('{}') -> false (endpoint '{}' disabled)", group, endpoint);
return false; return false;
} }
} }
log.debug("isGroupEnabled('{}') -> true (all endpoints enabled)", group);
return true; return true;
} }
@ -101,22 +139,22 @@ public class EndpointConfiguration {
} }
public void disableGroup(String group) { public void disableGroup(String group) {
disabledGroups.add(group); if (disabledGroups.add(group)) {
log.debug("Disabling group: {}", group);
}
Set<String> endpoints = endpointGroups.get(group); Set<String> endpoints = endpointGroups.get(group);
if (endpoints != null) { if (endpoints != null) {
for (String endpoint : endpoints) { endpoints.forEach(this::disableEndpoint);
disableEndpoint(endpoint);
}
} }
} }
public void enableGroup(String group) { public void enableGroup(String group) {
disabledGroups.remove(group); if (disabledGroups.remove(group)) {
log.debug("Enabling group: {}", group);
}
Set<String> endpoints = endpointGroups.get(group); Set<String> endpoints = endpointGroups.get(group);
if (endpoints != null) { if (endpoints != null) {
for (String endpoint : endpoints) { endpoints.forEach(this::enableEndpoint);
enableEndpoint(endpoint);
}
} }
} }
@ -127,8 +165,7 @@ public class EndpointConfiguration {
public void logDisabledEndpointsSummary() { public void logDisabledEndpointsSummary() {
List<String> disabledList = List<String> disabledList =
endpointStatuses.entrySet().stream() endpointStatuses.entrySet().stream()
.filter(entry -> !entry.getValue()) // only get disabled endpoints (value .filter(entry -> Boolean.FALSE.equals(entry.getValue()))
// is false)
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.sorted() .sorted()
.toList(); .toList();
@ -250,7 +287,6 @@ public class EndpointConfiguration {
// Unoconvert // Unoconvert
addEndpointToGroup("Unoconvert", "file-to-pdf"); addEndpointToGroup("Unoconvert", "file-to-pdf");
// Java // Java
addEndpointToGroup("Java", "merge-pdfs"); addEndpointToGroup("Java", "merge-pdfs");
addEndpointToGroup("Java", "remove-pages"); addEndpointToGroup("Java", "remove-pages");
@ -298,6 +334,20 @@ public class EndpointConfiguration {
addEndpointToGroup("Javascript", "compare"); addEndpointToGroup("Javascript", "compare");
addEndpointToGroup("Javascript", "adjust-contrast"); addEndpointToGroup("Javascript", "adjust-contrast");
/* qpdf */
addEndpointToGroup("qpdf", "repair");
addEndpointToGroup("qpdf", "compress-pdf");
/* Ghostscript */
addEndpointToGroup("Ghostscript", "repair");
addEndpointToGroup("Ghostscript", "compress-pdf");
/* tesseract */
addEndpointToGroup("tesseract", "ocr-pdf");
/* OCRmyPDF */
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
// Multi-tool endpoints - endpoints that can be handled by multiple tools // Multi-tool endpoints - endpoints that can be handled by multiple tools
addEndpointAlternative("repair", "qpdf"); addEndpointAlternative("repair", "qpdf");
addEndpointAlternative("repair", "Ghostscript"); addEndpointAlternative("repair", "Ghostscript");
@ -346,4 +396,43 @@ public class EndpointConfiguration {
public Set<String> getEndpointsForGroup(String group) { public Set<String> getEndpointsForGroup(String group) {
return endpointGroups.getOrDefault(group, new HashSet<>()); return endpointGroups.getOrDefault(group, new HashSet<>());
} }
private boolean isToolGroup(String group) {
return "qpdf".equals(group)
|| "OCRmyPDF".equals(group)
|| "Ghostscript".equals(group)
|| "LibreOffice".equals(group)
|| "tesseract".equals(group)
|| "CLI".equals(group)
|| "Python".equals(group)
|| "OpenCV".equals(group)
|| "Unoconvert".equals(group)
|| "Java".equals(group)
|| "Javascript".equals(group)
|| "Weasyprint".equals(group)
|| "Pdftohtml".equals(group);
}
private boolean isEndpointEnabledDirectly(String endpoint) {
if (endpoint.startsWith("/")) {
endpoint = endpoint.substring(1);
}
// Check explicit disable flag
Boolean explicitStatus = endpointStatuses.get(endpoint);
if (Boolean.FALSE.equals(explicitStatus)) {
return false;
}
// Check if endpoint belongs to any disabled functional group
for (String group : endpointGroups.keySet()) {
if (disabledGroups.contains(group) && endpointGroups.get(group).contains(endpoint)) {
if (!isToolGroup(group)) {
return false;
}
}
}
return true;
}
} }

View File

@ -14,8 +14,10 @@ import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@ -83,8 +85,7 @@ public class RepairController {
} }
} catch (Exception e) { } catch (Exception e) {
// Log and continue to QPDF fallback // Log and continue to QPDF fallback
log.warn( log.warn("Ghostscript repair failed, trying QPDF fallback: ", e);
"Ghostscript repair failed, trying QPDF fallback: ", e);
} }
} }