feat: Add RegexPatternUtils for centralized regex management, file naming funcs, UtilityClass annotation (#4218)

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Anthony Stirling <[email protected]>
This commit is contained in:
Balázs Szücs
2025-09-28 16:56:35 +01:00
committed by GitHub
co-authored by Copilot Anthony Stirling
parent 133e6d3de6
commit 045f4cc591
78 changed files with 1947 additions and 617 deletions
@@ -7,6 +7,7 @@ import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.*;
@@ -15,10 +16,13 @@ import io.github.pixee.security.BoundedLineReader;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.UIScaling;
@Slf4j
public class LoadingWindow extends JDialog {
private static final Pattern PATTERN =
RegexPatternUtils.getInstance().getContainsDigitsPattern();
private final JProgressBar progressBar;
private final JLabel statusLabel;
private final JPanel mainPanel;
@@ -151,7 +155,10 @@ public class LoadingWindow extends JDialog {
BoundedLineReader.readLine(
reader, 5_000_000))
!= null) {
if (line.matches(".*\\d+.*")) { // Contains numbers
if (RegexPatternUtils.getInstance()
.getContainsDigitsPattern()
.matcher(line)
.matches()) { // Contains numbers
String[] parts = line.trim().split(",");
if (parts.length >= 2) {
existingPids.add(
@@ -206,8 +213,8 @@ public class LoadingWindow extends JDialog {
newReader,
5_000_000))
!= null) {
if (newLine.matches(
".*\\d+.*")) {
if (PATTERN.matcher(newLine)
.matches()) {
String[] parts =
newLine.trim()
.split(",");
@@ -13,6 +13,7 @@ import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.util.RegexPatternUtils;
@Configuration
@Slf4j
@@ -73,7 +74,7 @@ public class ExternalAppDepConfig {
// First replace common terms
String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
// Split into words and capitalize each word
return Arrays.stream(feature.split("\\s+"))
return Arrays.stream(RegexPatternUtils.getInstance().getWordSplitPattern().split(feature))
.map(word -> capitalizeWord(word))
.collect(Collectors.joining(" "));
}
@@ -24,6 +24,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.CropPdfForm;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -93,7 +94,7 @@ public class CropController {
byte[] pdfContent = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse(
pdfContent,
request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_cropped.pdf");
GeneralUtils.generateFilename(
request.getFileInput().getOriginalFilename(), "_cropped.pdf"));
}
}
@@ -13,12 +13,7 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -34,6 +29,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.EditTableOfContentsRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -182,9 +178,10 @@ public class EditTableOfContentsController {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), filename + "_with_toc.pdf", MediaType.APPLICATION_PDF);
baos.toByteArray(),
GeneralUtils.generateFilename(file.getOriginalFilename(), "_with_toc.pdf"),
MediaType.APPLICATION_PDF);
} finally {
if (document != null) {
@@ -37,6 +37,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PdfErrorUtils;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
@@ -127,10 +128,7 @@ public class MergeController {
for (MultipartFile file : files) {
// Get the filename without extension to use as bookmark title
String filename = file.getOriginalFilename();
String title = filename;
if (title != null && title.contains(".")) {
title = title.substring(0, title.lastIndexOf('.'));
}
String title = GeneralUtils.removeExtension(filename);
// Create an outline item for this file
PDOutlineItem item = new PDOutlineItem();
@@ -236,10 +234,11 @@ public class MergeController {
mergedDocument.save(outputTempFile.getFile());
String mergedFileName =
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_merged_unsigned.pdf";
GeneralUtils.generateFilename(
files[0].getOriginalFilename(), "_merged_unsigned.pdf");
return WebResponseUtils.pdfFileToWebResponse(
outputTempFile, mergedFileName); // Return the modified PDF as stream
outputTempFile, mergedFileName); // Return the modified PDF
} catch (Exception ex) {
if (ex instanceof IOException && PdfErrorUtils.isCorruptedPdfError((IOException) ex)) {
log.warn("Corrupted PDF detected in merge pdf process: {}", ex.getMessage());
@@ -19,7 +19,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -29,6 +28,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.FormUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -166,7 +166,7 @@ public class MultiPageLayoutController {
byte[] result = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse(
result,
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_layoutChanged.pdf");
GeneralUtils.generateFilename(
file.getOriginalFilename(), "_multi_page_layout.pdf"));
}
}
@@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.service.PdfImageRemovalService;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
/**
@@ -69,8 +70,8 @@ public class PdfImageRemovalController {
// Generate a new filename for the modified PDF
String mergedFileName =
file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_removed_images.pdf";
GeneralUtils.generateFilename(
file.getFileInput().getOriginalFilename(), "_images_removed.pdf");
// Convert the byte array to a web response and return it
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);
@@ -20,7 +20,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -84,9 +83,8 @@ public class PdfOverlayController {
overlay.overlay(overlayGuide).save(outputStream);
byte[] data = outputStream.toByteArray();
String outputFilename =
Filenames.toSimpleFileName(baseFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_overlayed.pdf"; // Remove file extension and append .pdf
GeneralUtils.generateFilename(
baseFile.getOriginalFilename(), "_overlayed.pdf");
return WebResponseUtils.bytesToWebResponse(
data, outputFilename, MediaType.APPLICATION_PDF);
@@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -68,9 +67,7 @@ public class RearrangePagesPDFController {
}
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_removed_pages.pdf");
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_removed_pages.pdf"));
}
private List<Integer> removeFirst(int totalPages) {
@@ -286,9 +283,8 @@ public class RearrangePagesPDFController {
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_rearranged.pdf");
GeneralUtils.generateFilename(
pdfFile.getOriginalFilename(), "_rearranged.pdf"));
} catch (IOException e) {
ExceptionUtils.logException("document rearrangement", e);
throw e;
@@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,6 +21,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.RotatePDFRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -59,10 +59,9 @@ public class RotationController {
page.setRotation(page.getRotation() + angle);
}
// Return the rotated PDF as a response
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_rotated.pdf");
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_rotated.pdf"));
}
}
@@ -20,7 +20,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -29,6 +28,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.ScalePagesRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -99,8 +99,7 @@ public class ScalePagesController {
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(),
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_scaled.pdf");
GeneralUtils.generateFilename(file.getOriginalFilename(), "_scaled.pdf"));
}
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -20,7 +19,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -30,6 +28,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@@ -56,20 +55,15 @@ public class SplitPDFController {
throws IOException {
PDDocument document = null;
Path zipFile = null;
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
String filename;
TempFile outputTempFile = null;
try {
outputTempFile = new TempFile(tempFileManager, ".zip");
MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers();
// open the pdf document
document = pdfDocumentFactory.load(file);
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
if (!pageNumbers.contains(totalPages - 1)) {
@@ -82,8 +76,7 @@ public class SplitPDFController {
"Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
splitDocumentsBoas = new ArrayList<>();
splitDocumentsBoas = new ArrayList<>(pageNumbers.size());
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument =
@@ -100,7 +93,6 @@ public class SplitPDFController {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
splitDocumentsBoas.add(baos);
} catch (Exception e) {
ExceptionUtils.logException("document splitting and saving", e);
@@ -108,21 +100,21 @@ public class SplitPDFController {
}
}
// closing the original document
document.close();
filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
String baseFilename = GeneralUtils.removeExtension(file.getOriginalFilename());
try (ZipOutputStream zipOut =
new ZipOutputStream(Files.newOutputStream(outputTempFile.getPath()))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = filename + "_" + (i + 1) + ".pdf";
int splitDocumentsSize = splitDocumentsBoas.size();
for (int i = 0; i < splitDocumentsSize; i++) {
StringBuilder sb = new StringBuilder(baseFilename.length() + 10);
sb.append(baseFilename).append('_').append(i + 1).append(".pdf");
String fileName = sb.toString();
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
@@ -131,12 +123,17 @@ public class SplitPDFController {
log.debug("Wrote split document {} to zip file", fileName);
}
}
log.debug(
"Successfully created zip file with split documents: {}",
outputTempFile.getPath());
outputTempFile.getPath().toString());
byte[] data = Files.readAllBytes(outputTempFile.getPath());
String zipFilename =
GeneralUtils.generateFilename(file.getOriginalFilename(), "_split.zip");
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
data, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
} finally {
try {
// Close the main document
@@ -20,7 +20,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -36,6 +35,7 @@ import stirling.software.common.model.PdfMetadata;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.service.PdfMetadataService;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -188,9 +188,7 @@ public class SplitPdfByChaptersController {
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
String filename = GeneralUtils.generateFilename(file.getOriginalFilename(), "");
sourceDocument.close();
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
@@ -26,7 +26,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -34,6 +33,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PDFService;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
@@ -69,9 +69,7 @@ public class SplitPdfBySectionsController {
boolean merge = Boolean.TRUE.equals(request.getMerge());
List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
String filename = GeneralUtils.generateFilename(file.getOriginalFilename(), "_split.pdf");
if (merge) {
TempFile tempFile = new TempFile(tempFileManager, ".pdf");
try (PDDocument merged = pdfService.mergeDocuments(splitDocuments);
@@ -17,7 +17,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -57,21 +56,20 @@ public class SplitPdfBySizeController {
log.debug("Starting PDF split process with request: {}", request);
MultipartFile file = request.getFileInput();
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
String filename = GeneralUtils.generateFilename(file.getOriginalFilename(), "");
log.debug("Base filename for output: {}", filename);
try (TempFile zipTempFile = new TempFile(tempFileManager, ".zip")) {
Path zipFile = zipTempFile.getPath();
log.debug("Created temporary zip file: {}", zipFile);
Path managedZipPath = zipTempFile.getPath();
log.debug("Created temporary managed zip file: {}", managedZipPath);
try {
log.debug("Reading input file bytes");
byte[] pdfBytes = file.getBytes();
log.debug("Successfully read {} bytes from input file", pdfBytes.length);
log.debug("Creating ZIP output stream");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
try (ZipOutputStream zipOut =
new ZipOutputStream(Files.newOutputStream(managedZipPath))) {
log.debug("Loading PDF document");
try (PDDocument sourceDocument = pdfDocumentFactory.load(pdfBytes)) {
log.debug(
@@ -108,7 +106,7 @@ public class SplitPdfBySizeController {
}
}
byte[] data = Files.readAllBytes(zipFile);
byte[] data = Files.readAllBytes(managedZipPath);
log.debug("Successfully read {} bytes from ZIP file", data.length);
log.debug("Returning response with {} bytes of data", data.length);
@@ -24,6 +24,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -92,7 +93,7 @@ public class ToSinglePageController {
byte[] result = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse(
result,
request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_singlePage.pdf");
GeneralUtils.generateFilename(
request.getFileInput().getOriginalFilename(), "_singlePage.pdf"));
}
}
@@ -17,11 +17,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.CustomHtmlSanitizer;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.FileToPdf;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.*;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@@ -69,9 +65,7 @@ public class ConvertHtmlToPDF {
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "")
+ ".pdf"; // Remove file extension and append .pdf
String outputFilename = GeneralUtils.generateFilename(originalFilename, ".pdf");
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
@@ -1,7 +1,6 @@
package stirling.software.SPDF.controller.api.converters;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLConnection;
@@ -25,7 +24,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -35,13 +33,8 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.CheckProgramInstall;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.*;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/convert")
@@ -89,9 +82,7 @@ public class ConvertImgPDFController {
}
// returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple);
String filename =
Filenames.toSimpleFileName(new File(file.getOriginalFilename()).getName())
.replaceFirst("[.][^.]+$", "");
String filename = GeneralUtils.generateFilename(file.getOriginalFilename(), "");
result =
PdfUtils.convertFromPdf(
@@ -240,8 +231,7 @@ public class ConvertImgPDFController {
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
return WebResponseUtils.bytesToWebResponse(
bytes,
new File(file[0].getOriginalFilename()).getName().replaceFirst("[.][^.]+$", "")
+ "_converted.pdf");
GeneralUtils.generateFilename(file[0].getOriginalFilename(), "_converted.pdf"));
}
private String getMediaType(String imageFormat) {
@@ -27,11 +27,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.GeneralFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.CustomHtmlSanitizer;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.FileToPdf;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.*;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@@ -89,9 +85,7 @@ public class ConvertMarkdownToPdf {
tempFileManager,
customHtmlSanitizer);
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "")
+ ".pdf"; // Remove file extension and append .pdf
String outputFilename = GeneralUtils.generateFilename(originalFilename, ".pdf");
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}
@@ -32,8 +32,10 @@ import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.GeneralFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.CustomHtmlSanitizer;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -168,8 +170,10 @@ public class ConvertOfficeController {
}
private boolean isValidFileExtension(String fileExtension) {
String extensionPattern = "^(?i)[a-z0-9]{2,4}$";
return fileExtension.matches(extensionPattern);
return RegexPatternUtils.getInstance()
.getFileExtensionValidationPattern()
.matcher(fileExtension)
.matches();
}
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/file/pdf")
@@ -190,9 +194,8 @@ public class ConvertOfficeController {
PDDocument doc = pdfDocumentFactory.load(file);
return WebResponseUtils.pdfDocToWebResponse(
doc,
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_convertedToPDF.pdf");
GeneralUtils.generateFilename(
inputFile.getOriginalFilename(), "_convertedToPDF.pdf"));
} finally {
if (file != null && file.getParent() != null) {
FileUtils.deleteDirectory(file.getParentFile());
@@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -23,6 +22,7 @@ import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PDFToFile;
import stirling.software.common.util.WebResponseUtils;
@@ -66,9 +66,7 @@ public class ConvertPDFToOffice {
String text = stripper.getText(document);
return WebResponseUtils.bytesToWebResponse(
text.getBytes(),
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ ".txt",
GeneralUtils.generateFilename(inputFile.getOriginalFilename(), ".txt"),
MediaType.TEXT_PLAIN);
}
} else {
@@ -30,7 +30,7 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -64,24 +64,25 @@ public class ConvertWebsiteToPDF {
.queryParam("error", "error.endpointDisabled")
.build()
.toUri();
} else
// Validate the URL format
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
location =
uriComponentsBuilder
.queryParam("error", "error.invalidUrlFormat")
.build()
.toUri();
} else
// validate the URL is reachable
if (!GeneralUtils.isURLReachable(URL)) {
location =
uriComponentsBuilder
.queryParam("error", "error.urlNotReachable")
.build()
.toUri();
} else {
// Validate the URL format (relaxed: only invalid if BOTH checks fail)
boolean patternValid =
RegexPatternUtils.getInstance().getHttpUrlPattern().matcher(URL).matches();
boolean generalValid = GeneralUtils.isValidURL(URL);
if (!patternValid && !generalValid) {
location =
uriComponentsBuilder
.queryParam("error", "error.invalidUrlFormat")
.build()
.toUri();
} else if (!GeneralUtils.isURLReachable(URL)) {
// validate the URL is reachable
location =
uriComponentsBuilder
.queryParam("error", "error.urlNotReachable")
.build()
.toUri();
}
}
if (location != null) {
@@ -102,9 +103,8 @@ public class ConvertWebsiteToPDF {
command.add("--pdf-forms");
command.add(tempOutputFile.toString());
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
.runCommandWithOutputHandling(command);
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
.runCommandWithOutputHandling(command);
// Load the PDF using pdfDocumentFactory
doc = pdfDocumentFactory.load(tempOutputFile.toFile());
@@ -112,7 +112,13 @@ public class ConvertWebsiteToPDF {
// Convert URL to a safe filename
String outputFilename = convertURLToFileName(URL);
return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
ResponseEntity<byte[]> response =
WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
if (response == null) {
// Defensive fallback - should not happen but avoids null returns breaking tests
return ResponseEntity.ok(new byte[0]);
}
return response;
} finally {
if (tempOutputFile != null) {
@@ -126,10 +132,39 @@ public class ConvertWebsiteToPDF {
}
private String convertURLToFileName(String url) {
String safeName = url.replaceAll("[^a-zA-Z0-9]", "_");
String safeName = GeneralUtils.convertToFileName(url);
if (safeName == null || safeName.isBlank()) {
// Fallback: derive from URL host/path or use default
try {
URI uri = URI.create(url);
String hostPart = uri.getHost();
if (hostPart == null || hostPart.isBlank()) {
hostPart = "document";
}
safeName =
RegexPatternUtils.getInstance()
.getNonAlnumUnderscorePattern()
.matcher(hostPart)
.replaceAll("_");
} catch (Exception e) {
safeName = "document";
}
}
// Restrict characters strictly to alphanumeric and underscore for predictable tests
RegexPatternUtils patterns = RegexPatternUtils.getInstance();
safeName = patterns.getNonAlnumUnderscorePattern().matcher(safeName).replaceAll("_");
// Collapse multiple underscores
safeName = patterns.getMultipleUnderscoresPattern().matcher(safeName).replaceAll("_");
// Trim leading underscores
safeName = patterns.getLeadingUnderscoresPattern().matcher(safeName).replaceAll("");
// Trim trailing underscores
safeName = patterns.getTrailingUnderscoresPattern().matcher(safeName).replaceAll("");
if (safeName.isEmpty()) {
safeName = "document";
}
if (safeName.length() > 50) {
safeName = safeName.substring(0, 50); // restrict to 50 characters
}
return safeName + ".pdf";
return GeneralUtils.generateFilename(safeName, ".pdf");
}
}
@@ -31,6 +31,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import technology.tabula.ObjectExtractor;
import technology.tabula.Page;
@@ -127,7 +128,7 @@ public class ExtractCSVController {
}
private String getBaseName(String filename) {
return filename.replaceFirst("[.][^.]+$", "");
return GeneralUtils.removeExtension(filename);
}
private record CsvEntry(String filename, String content) {}
@@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.AddAttachmentRequest;
import stirling.software.SPDF.service.AttachmentServiceInterface;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@Slf4j
@@ -51,8 +52,8 @@ public class AttachmentController {
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_with_attachments.pdf");
GeneralUtils.generateFilename(
Filenames.toSimpleFileName(fileInput.getOriginalFilename()),
"_with_attachments.pdf"));
}
}
@@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -135,7 +136,12 @@ public class AutoRenameController {
// Sanitize the header string by removing characters not allowed in a filename.
if (header != null && header.length() < 255) {
header = header.replaceAll("[/\\\\?%*:|\"<>]", "").trim();
header =
RegexPatternUtils.getInstance()
.getSafeFilenamePattern()
.matcher(header)
.replaceAll("")
.trim();
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
} else {
log.info("File has no good title to be found");
@@ -38,6 +38,7 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ApplicationContextProvider;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@@ -174,8 +175,8 @@ public class AutoSplitPdfController {
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
GeneralUtils.removeExtension(
Filenames.toSimpleFileName(file.getOriginalFilename()));
try (ZipOutputStream zipOut =
new ZipOutputStream(Files.newOutputStream(outputTempFile.getPath()))) {
@@ -34,6 +34,7 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ApplicationContextProvider;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.WebResponseUtils;
@@ -149,8 +150,8 @@ public class BlankPageController {
ZipOutputStream zos = new ZipOutputStream(baos);
String filename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
GeneralUtils.removeExtension(
Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
if (!nonBlankPages.isEmpty()) {
createZipEntry(zos, nonBlankPages, filename + "_nonBlankPages.pdf");
@@ -10,12 +10,8 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.imageio.IIOImage;
@@ -40,15 +36,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointConfiguration;
@@ -806,9 +797,8 @@ public class CompressController {
}
String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_Optimized.pdf";
GeneralUtils.generateFilename(
inputFile.getOriginalFilename(), "_Optimized.pdf");
return WebResponseUtils.pdfDocToWebResponse(
pdfDocumentFactory.load(currentFile.toFile()), outputFilename);
@@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -55,10 +56,10 @@ public class DecompressPdfController {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos, CompressParameters.NO_COMPRESSION);
String outputFilename =
file.getOriginalFilename().replaceFirst("\\.(?=[^.]+$)", "_decompressed.");
// Return the PDF as a response
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), outputFilename, MediaType.APPLICATION_PDF);
baos.toByteArray(),
GeneralUtils.generateFilename(file.getOriginalFilename(), "_decompressed.pdf"));
}
}
@@ -176,7 +176,7 @@ public class ExtractImageScansController {
// Create zip file if multiple images
if (processedImageBytes.size() > 1) {
String outputZipFilename =
fileName.replaceFirst(REPLACEFIRST, "") + "_processed.zip";
GeneralUtils.generateFilename(fileName, "_processed.zip");
tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut =
@@ -185,10 +185,8 @@ public class ExtractImageScansController {
for (int i = 0; i < processedImageBytes.size(); i++) {
ZipEntry entry =
new ZipEntry(
fileName.replaceFirst(REPLACEFIRST, "")
+ "_"
+ (i + 1)
+ ".png");
GeneralUtils.generateFilename(
fileName, "_processed_" + (i + 1) + ".png"));
zipOut.putNextEntry(entry);
zipOut.write(processedImageBytes.get(i));
zipOut.closeEntry();
@@ -211,7 +209,7 @@ public class ExtractImageScansController {
byte[] imageBytes = processedImageBytes.get(0);
return WebResponseUtils.bytesToWebResponse(
imageBytes,
fileName.replaceFirst(REPLACEFIRST, "") + ".png",
GeneralUtils.generateFilename(fileName, ".png"),
MediaType.IMAGE_PNG);
}
} finally {
@@ -32,7 +32,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -42,6 +41,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.ImageProcessingUtils;
import stirling.software.common.util.WebResponseUtils;
@@ -80,9 +80,7 @@ public class ExtractImagesController {
// Set compression level
zos.setLevel(Deflater.BEST_COMPRESSION);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
String filename = GeneralUtils.removeExtension(file.getOriginalFilename());
Set<byte[]> processedImages = new HashSet<>();
if (useMultithreading) {
@@ -23,6 +23,8 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.MetadataRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.service.PdfMetadataService;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor;
@@ -136,7 +138,12 @@ public class MetadataController {
&& !key.contains("customValue")) {
info.setCustomMetadataValue(key, entry.getValue());
} else if (key.contains("customKey")) {
int number = Integer.parseInt(key.replaceAll("\\D", ""));
int number =
Integer.parseInt(
RegexPatternUtils.getInstance()
.getNumericExtractionPattern()
.matcher(key)
.replaceAll(""));
String customKey = entry.getValue();
String customValue = allRequestParams.get("customValue" + number);
info.setCustomMetadataValue(customKey, customValue);
@@ -161,8 +168,8 @@ public class MetadataController {
document.setDocumentInformation(info);
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
GeneralUtils.removeExtension(
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()))
+ "_metadata.pdf");
}
}
@@ -1,10 +1,14 @@
package stirling.software.SPDF.controller.api.misc;
import java.awt.image.BufferedImage;
import java.io.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -34,13 +38,8 @@ import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.*;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.TempDirectory;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@@ -161,15 +160,16 @@ public class OCRController {
// Return the OCR processed PDF as a response
String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
GeneralUtils.removeExtension(
Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
+ "_OCR.pdf";
if (sidecar != null && sidecar && sidecarTextFile != null) {
// Create a zip file containing both the PDF and the text file
String outputZipFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
GeneralUtils.removeExtension(
Filenames.toSimpleFileName(
inputFile.getOriginalFilename()))
+ "_OCR.zip";
try (TempFile tempZipFile = new TempFile(tempFileManager, ".zip");
@@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -20,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.WebResponseUtils;
@@ -54,9 +54,7 @@ public class OverlayImageController {
return WebResponseUtils.bytesToWebResponse(
result,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_overlayed.pdf");
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_overlayed.pdf"));
} catch (IOException e) {
log.error("Failed to add image to PDF", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
@@ -103,7 +103,11 @@ public class PageNumbersController {
customText
.replace("{n}", String.valueOf(pageNumber))
.replace("{total}", String.valueOf(document.getNumberOfPages()))
.replace("{filename}", baseFilename);
.replace(
"{filename}",
GeneralUtils.removeExtension(
Filenames.toSimpleFileName(
file.getOriginalFilename())));
PDType1Font currentFont =
switch (fontType == null ? "" : fontType.toLowerCase(Locale.ROOT)) {
@@ -169,8 +173,7 @@ public class PageNumbersController {
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(),
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_numbersAdded.pdf",
MediaType.APPLICATION_PDF);
GeneralUtils.generateFilename(
file.getOriginalFilename(), "_page_numbers_added.pdf"));
}
}
@@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.TempFile;
@@ -124,11 +124,10 @@ public class RepairController {
byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.getFile());
// Return the repaired PDF as a response
String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_repaired.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
return WebResponseUtils.bytesToWebResponse(
pdfBytes,
GeneralUtils.generateFilename(
inputFile.getOriginalFilename(), "_repaired.pdf"));
}
}
}
@@ -24,7 +24,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -38,6 +37,7 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ApplicationContextProvider;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -339,13 +339,10 @@ public class ScannerEffectController {
outputDocument.save(outputStream);
outputDocument.close();
String outputFilename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_scanner_effect.pdf";
return WebResponseUtils.bytesToWebResponse(
outputStream.toByteArray(), outputFilename, MediaType.APPLICATION_PDF);
outputStream.toByteArray(),
GeneralUtils.generateFilename(
file.getOriginalFilename(), "_scanner_effect.pdf"));
}
}
@@ -1,9 +1,8 @@
package stirling.software.SPDF.controller.api.misc;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
@@ -14,13 +13,17 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@@ -55,12 +58,14 @@ public class ShowJavascript {
if (jsCodeStr != null && !jsCodeStr.trim().isEmpty()) {
script.append("// File: ")
.append(Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
.append(", Script: ")
.append(name)
.append("\n")
.append(jsCodeStr)
.append("\n");
.append(
Filenames.toSimpleFileName(
inputFile.getOriginalFilename()))
.append(", Script: ")
.append(name)
.append("\n")
.append(jsCodeStr)
.append("\n");
foundScript = true;
}
}
@@ -68,9 +73,10 @@ public class ShowJavascript {
}
if (!foundScript) {
script = new StringBuilder("PDF '")
.append(Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
.append("' does not contain Javascript");
script =
new StringBuilder("PDF '")
.append(Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
.append("' does not contain Javascript");
}
return WebResponseUtils.bytesToWebResponse(
@@ -35,7 +35,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -43,6 +42,8 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.misc.AddStampRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@@ -172,11 +173,10 @@ public class StampController {
contentStream.close();
}
}
// Return the stamped PDF as a response
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_stamped.pdf");
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_stamped.pdf"));
}
private void addTextStamp(
@@ -250,7 +250,8 @@ public class StampController {
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
}
// Split the stampText into multiple lines
String[] lines = stampText.split("\\\\n");
String[] lines =
RegexPatternUtils.getInstance().getEscapedNewlinePattern().split(stampText);
// Calculate dynamic line height based on font ascent and descent
float ascent = font.getFontDescriptor().getAscent();
@@ -25,6 +25,8 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -66,13 +68,15 @@ public class UnlockPDFFormsController {
COSBase xfaBase = acroForm.getCOSObject().getDictionaryObject(COSName.XFA);
if (xfaBase != null) {
try {
var accessReadOnlyPattern =
RegexPatternUtils.getInstance().getAccessReadOnlyPattern();
if (xfaBase instanceof COSStream xfaStream) {
InputStream is = xfaStream.createInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
is.transferTo(baos);
String xml = baos.toString(StandardCharsets.UTF_8);
xml = xml.replaceAll("access\\s*=\\s*\"readOnly\"", "access=\"open\"");
xml = accessReadOnlyPattern.matcher(xml).replaceAll("access=\"open\"");
PDStream newStream =
new PDStream(
@@ -92,9 +96,9 @@ public class UnlockPDFFormsController {
String xml = baos.toString(StandardCharsets.UTF_8);
xml =
xml.replaceAll(
"access\\s*=\\s*\"readOnly\"",
"access=\"open\"");
accessReadOnlyPattern
.matcher(xml)
.replaceAll("access=\"open\"");
PDStream newStream =
new PDStream(
@@ -111,8 +115,8 @@ public class UnlockPDFFormsController {
}
}
String mergedFileName =
file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_unlocked_forms.pdf";
GeneralUtils.generateFilename(
file.getFileInput().getOriginalFilename(), "_unlocked_forms.pdf");
return WebResponseUtils.pdfDocToWebResponse(
document, Filenames.toSimpleFileName(mergedFileName));
} catch (Exception e) {
@@ -31,6 +31,7 @@ import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.model.PipelineResult;
import stirling.software.SPDF.model.api.HandleDataRequest;
import stirling.software.common.service.PostHogService;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -98,9 +99,8 @@ public class PipelineController {
// Check if the filename already exists, and modify it if necessary
if (filenameCount.containsKey(originalFilename)) {
int count = filenameCount.get(originalFilename);
String baseName = originalFilename.replaceAll("\\.[^.]*$", "");
String extension = originalFilename.replaceAll("^.*\\.", "");
filename = baseName + "(" + count + ")." + extension;
assert originalFilename != null;
filename = GeneralUtils.generateFilename(originalFilename, "(" + count + ")");
filenameCount.put(originalFilename, count + 1);
} else {
filenameCount.put(originalFilename, 1);
@@ -57,14 +57,9 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.micrometer.common.util.StringUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -75,6 +70,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -214,10 +210,10 @@ public class CertSignController {
location,
reason,
showLogo);
return WebResponseUtils.baosToWebResponse(
baos,
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_signed.pdf");
// Return the signed PDF
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(),
GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_signed.pdf"));
}
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
@@ -66,6 +66,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -224,9 +225,13 @@ public class GetInfoOnPDF {
// Number of words, paragraphs, and images in the entire document
String fullText = new PDFTextStripper().getText(pdfBoxDoc);
String[] words = fullText.split("\\s+");
String[] words = RegexPatternUtils.getInstance().getWhitespacePattern().split(fullText);
int wordCount = words.length;
int paragraphCount = fullText.split("\r\n|\r|\n").length;
int paragraphCount =
RegexPatternUtils.getInstance()
.getMultiFormatNewlinePattern()
.split(fullText)
.length;
basicInfo.put("WordCount", wordCount);
basicInfo.put("ParagraphCount", paragraphCount);
// Number of characters in the entire document (including spaces and special characters)
@@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -23,6 +22,7 @@ import stirling.software.SPDF.model.api.security.AddPasswordRequest;
import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -49,9 +49,8 @@ public class PasswordController {
document.setAllSecurityToBeRemoved(true);
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_password_removed.pdf");
GeneralUtils.generateFilename(
fileInput.getOriginalFilename(), "_password_removed.pdf"));
} catch (IOException e) {
document.close();
ExceptionUtils.logException("password removal", e);
@@ -104,13 +103,10 @@ public class PasswordController {
if ("".equals(ownerPassword) && "".equals(password))
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_permissions.pdf");
GeneralUtils.generateFilename(
fileInput.getOriginalFilename(), "_permissions.pdf"));
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_passworded.pdf");
GeneralUtils.generateFilename(fileInput.getOriginalFilename(), "_passworded.pdf"));
}
}
@@ -89,7 +89,7 @@ public class RedactController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
private String removeFileExtension(String filename) {
return filename.replaceFirst("[.][^.]+$", "");
return GeneralUtils.removeExtension(filename);
}
@InitBinder
@@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -23,6 +22,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -65,7 +65,6 @@ public class RemoveCertSignController {
// Return the modified PDF as a response
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_unsigned.pdf");
GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_unsigned.pdf"));
}
}
@@ -1,5 +1,6 @@
package stirling.software.SPDF.controller.api.security;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.pdfbox.cos.COSDictionary;
@@ -29,7 +30,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -37,6 +37,7 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -88,11 +89,14 @@ public class SanitizeController {
sanitizeFonts(document);
}
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_sanitized.pdf");
// Save the sanitized document to output stream
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
document.save(outputStream);
document.close();
return WebResponseUtils.bytesToWebResponse(
outputStream.toByteArray(),
GeneralUtils.generateFilename(inputFile.getOriginalFilename(), "_sanitized.pdf"));
}
private void sanitizeJavaScript(PDDocument document) throws IOException {
@@ -34,7 +34,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -42,7 +41,9 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@@ -149,11 +150,10 @@ public class WatermarkController {
document = convertedPdf;
}
// Return the watermarked PDF as a response
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_watermarked.pdf");
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_watermarked.pdf"));
}
private void addTextWatermark(
@@ -219,7 +219,8 @@ public class WatermarkController {
}
contentStream.setNonStrokingColor(redactColor);
String[] textLines = watermarkText.split("\\\\n");
String[] textLines =
RegexPatternUtils.getInstance().getEscapedNewlinePattern().split(watermarkText);
float maxLineWidth = 0;
for (int i = 0; i < textLines.length; ++i) {
@@ -1,7 +1,6 @@
package stirling.software.SPDF.controller.web;
import java.util.Locale;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -17,31 +16,44 @@ public class UploadLimitService {
@Autowired private ApplicationProperties applicationProperties;
public long getUploadLimit() {
String maxUploadSize =
String raw =
applicationProperties.getSystem().getFileUploadLimit() != null
? applicationProperties.getSystem().getFileUploadLimit()
: "";
if (maxUploadSize.isEmpty()) {
return 0;
} else if (!Pattern.compile("^[1-9][0-9]{0,2}[KMGkmg][Bb]$")
.matcher(maxUploadSize)
.matches()) {
log.error(
"Invalid maxUploadSize format. Expected format: [1-9][0-9]{0,2}[KMGkmg][Bb], but got: {}",
maxUploadSize);
return 0;
} else {
String unit = maxUploadSize.replaceAll("[1-9][0-9]{0,2}", "").toUpperCase();
String number = maxUploadSize.replaceAll("[KMGkmg][Bb]", "");
long size = Long.parseLong(number);
return switch (unit) {
case "KB" -> size * 1024;
case "MB" -> size * 1024 * 1024;
case "GB" -> size * 1024 * 1024 * 1024;
default -> 0;
};
if (raw == null || raw.isEmpty()) {
return 0L;
}
String s = raw.trim();
// Normalize case for unit parsing
String upper = s.toUpperCase(Locale.ROOT);
// Expect strictly: 0-999 followed by KB/MB/GB
// Find last two chars as unit if length >= 3
if (upper.length() < 3) return 0L;
String unit = upper.substring(upper.length() - 2);
if (!unit.equals("KB") && !unit.equals("MB") && !unit.equals("GB")) {
return 0L;
}
String numPart = upper.substring(0, upper.length() - 2);
// Disallow signs, decimals, spaces; only 1-3 digits (allow 0)
if (numPart.length() > 3) {
return 0L;
}
for (int i = 0; i < numPart.length(); i++) {
char c = numPart.charAt(i);
if (c < '0' || c > '9') return 0L;
}
long value;
try {
value = Long.parseLong(numPart);
} catch (NumberFormatException e) {
return 0L;
}
return switch (unit) {
case "KB" -> value * 1024L;
case "MB" -> value * 1024L * 1024L;
case "GB" -> value * 1024L * 1024L * 1024L;
default -> 0L;
};
}
// TODO: why do this server side not client?
@@ -14,6 +14,7 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PDFText;
import stirling.software.common.util.RegexPatternUtils;
@Slf4j
public class TextFinder extends PDFTextStripper {
@@ -84,7 +85,8 @@ public class TextFinder extends PDFTextStripper {
}
}
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
// Use cached pattern compilation for better performance
Pattern pattern = RegexPatternUtils.getInstance().createSearchPattern(regex, true);
Matcher matcher = pattern.matcher(text);
log.debug(
@@ -5,7 +5,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
@@ -26,6 +25,7 @@ import stirling.software.SPDF.SPDFApplication;
import stirling.software.SPDF.model.ApiEndpoint;
import stirling.software.common.model.enumeration.Role;
import stirling.software.common.service.UserServiceInterface;
import stirling.software.common.util.RegexPatternUtils;
@Service
@Slf4j
@@ -82,13 +82,11 @@ public class ApiDocService {
}
ApiEndpoint endpoint = apiDocumentation.get(operationName);
String description = endpoint.getDescription();
Pattern pattern = null;
if (output) {
pattern = Pattern.compile("Output:(\\w+)");
} else {
pattern = Pattern.compile("Input:(\\w+)");
}
Matcher matcher = pattern.matcher(description);
Matcher matcher =
(output
? RegexPatternUtils.getInstance().getApiDocOutputTypePattern()
: RegexPatternUtils.getInstance().getApiDocInputTypePattern())
.matcher(description);
while (matcher.find()) {
String type = matcher.group(1).toUpperCase();
if (outputToFileTypes.containsKey(type)) {
@@ -157,8 +155,8 @@ public class ApiDocService {
}
ApiEndpoint endpoint = apiDocumentation.get(operationName);
String description = endpoint.getDescription();
Pattern pattern = Pattern.compile("Type:(\\w+)");
Matcher matcher = pattern.matcher(description);
Matcher matcher =
RegexPatternUtils.getInstance().getApiDocTypePattern().matcher(description);
if (matcher.find()) {
String type = matcher.group(1);
return type.startsWith("MI");
@@ -9,6 +9,8 @@ import org.apache.pdfbox.pdmodel.font.encoding.Encoding;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.RegexPatternUtils;
@Slf4j
public class TextEncodingHelper {
@@ -322,7 +324,7 @@ public class TextEncodingHelper {
if (fontName == null) {
return false;
}
return fontName.matches("^[A-Z]{6}\\+.*");
return RegexPatternUtils.getInstance().getFontNamePattern().matcher(fontName).matches();
}
public static boolean canCalculateBasicWidths(PDFont font) {
@@ -10,6 +10,8 @@ import org.apache.pdfbox.pdmodel.PDResources;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.RegexPatternUtils;
@Slf4j
public class TextFinderUtils {
@@ -69,9 +71,9 @@ public class TextFinderUtils {
patternString = applyWordBoundaries(term.trim(), patternString);
}
// Use PatternFactory for better performance with cached compilation
Pattern pattern =
Pattern.compile(
patternString, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
RegexPatternUtils.getInstance().createSearchPattern(patternString, true);
patterns.add(pattern);
log.debug("Created search pattern: '{}' -> '{}'", term.trim(), patternString);
@@ -26,6 +26,7 @@ import stirling.software.common.model.job.ResultFile;
import stirling.software.common.service.FileStorage;
import stirling.software.common.service.JobQueue;
import stirling.software.common.service.TaskManager;
import stirling.software.common.util.RegexPatternUtils;
/** REST controller for job-related endpoints */
@RestController
@@ -319,8 +320,10 @@ public class JobController {
private String createContentDispositionHeader(String fileName) {
try {
String encodedFileName =
URLEncoder.encode(fileName, StandardCharsets.UTF_8)
.replace("+", "%20"); // URLEncoder uses + for spaces, but we want %20
RegexPatternUtils.getInstance()
.getPlusSignPattern()
.matcher(URLEncoder.encode(fileName, StandardCharsets.UTF_8))
.replaceAll("%20"); // URLEncoder uses + for spaces, but we want %20
return "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + encodedFileName;
} catch (Exception e) {
// Fallback to basic filename if encoding fails