mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
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:
co-authored by
Copilot
Anthony Stirling
parent
133e6d3de6
commit
045f4cc591
@@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.common.model.job.JobResponse;
|
import stirling.software.common.model.job.JobResponse;
|
||||||
import stirling.software.common.util.ExecutorFactory;
|
import stirling.software.common.util.ExecutorFactory;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
/** Service for executing jobs asynchronously or synchronously */
|
/** Service for executing jobs asynchronously or synchronously */
|
||||||
@Service
|
@Service
|
||||||
@@ -426,8 +427,16 @@ public class JobExecutorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String value = timeout.replaceAll("[^\\d.]", "");
|
String value =
|
||||||
String unit = timeout.replaceAll("[\\d.]", "");
|
RegexPatternUtils.getInstance()
|
||||||
|
.getNonDigitDotPattern()
|
||||||
|
.matcher(timeout)
|
||||||
|
.replaceAll("");
|
||||||
|
String unit =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getDigitDotPattern()
|
||||||
|
.matcher(timeout)
|
||||||
|
.replaceAll("");
|
||||||
|
|
||||||
double numericValue = Double.parseDouble(value);
|
double numericValue = Double.parseDouble(value);
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -22,8 +23,9 @@ public class SsrfProtectionService {
|
|||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private static final Pattern DATA_URL_PATTERN =
|
private static final Pattern DATA_URL_PATTERN =
|
||||||
Pattern.compile("^data:.*", Pattern.CASE_INSENSITIVE);
|
RegexPatternUtils.getInstance().getPattern("^data:.*", Pattern.CASE_INSENSITIVE);
|
||||||
private static final Pattern FRAGMENT_PATTERN = Pattern.compile("^#.*");
|
private static final Pattern FRAGMENT_PATTERN =
|
||||||
|
RegexPatternUtils.getInstance().getPattern("^#.*");
|
||||||
|
|
||||||
public enum SsrfProtectionLevel {
|
public enum SsrfProtectionLevel {
|
||||||
OFF, // No SSRF protection - allows all URLs
|
OFF, // No SSRF protection - allows all URLs
|
||||||
|
|||||||
+9
-2
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.common.util.GeneralUtils;
|
import stirling.software.common.util.GeneralUtils;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.TempFileManager;
|
import stirling.software.common.util.TempFileManager;
|
||||||
import stirling.software.common.util.TempFileRegistry;
|
import stirling.software.common.util.TempFileRegistry;
|
||||||
|
|
||||||
@@ -61,8 +62,14 @@ public class TempFileCleanupService {
|
|||||||
// File patterns that identify common system temp files
|
// File patterns that identify common system temp files
|
||||||
private static final Predicate<String> IS_SYSTEM_TEMP_FILE =
|
private static final Predicate<String> IS_SYSTEM_TEMP_FILE =
|
||||||
fileName ->
|
fileName ->
|
||||||
fileName.matches("lu\\d+[a-z0-9]*\\.tmp")
|
RegexPatternUtils.getInstance()
|
||||||
|| fileName.matches("ocr_process\\d+")
|
.getSystemTempFile1Pattern()
|
||||||
|
.matcher(fileName)
|
||||||
|
.matches()
|
||||||
|
|| RegexPatternUtils.getInstance()
|
||||||
|
.getSystemTempFile2Pattern()
|
||||||
|
.matcher(fileName)
|
||||||
|
.matches()
|
||||||
|| (fileName.startsWith("tmp") && !fileName.contains("jetty"))
|
|| (fileName.startsWith("tmp") && !fileName.contains("jetty"))
|
||||||
|| fileName.startsWith("OSL_PIPE_")
|
|| fileName.startsWith("OSL_PIPE_")
|
||||||
|| (fileName.endsWith(".tmp") && !fileName.contains("jetty"));
|
|| (fileName.endsWith(".tmp") && !fileName.contains("jetty"));
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class EmlParser {
|
|||||||
private static volatile boolean mimeUtilityChecked = false;
|
private static volatile boolean mimeUtilityChecked = false;
|
||||||
|
|
||||||
private static final Pattern MIME_ENCODED_PATTERN =
|
private static final Pattern MIME_ENCODED_PATTERN =
|
||||||
Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
|
RegexPatternUtils.getInstance().getMimeEncodedWordPattern();
|
||||||
|
|
||||||
private static final String DISPOSITION_ATTACHMENT = "attachment";
|
private static final String DISPOSITION_ATTACHMENT = "attachment";
|
||||||
private static final String TEXT_PLAIN = MediaType.TEXT_PLAIN_VALUE;
|
private static final String TEXT_PLAIN = MediaType.TEXT_PLAIN_VALUE;
|
||||||
@@ -357,7 +357,11 @@ public class EmlParser {
|
|||||||
for (String contentIdHeader : contentIdHeaders) {
|
for (String contentIdHeader : contentIdHeaders) {
|
||||||
if (contentIdHeader != null && !contentIdHeader.trim().isEmpty()) {
|
if (contentIdHeader != null && !contentIdHeader.trim().isEmpty()) {
|
||||||
attachment.setEmbedded(true);
|
attachment.setEmbedded(true);
|
||||||
String contentId = contentIdHeader.trim().replaceAll("[<>]", "");
|
String contentId =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getAngleBracketsPattern()
|
||||||
|
.matcher(contentIdHeader.trim())
|
||||||
|
.replaceAll("");
|
||||||
attachment.setContentId(contentId);
|
attachment.setContentId(contentId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -414,7 +418,8 @@ public class EmlParser {
|
|||||||
|
|
||||||
private static String extractBasicHeader(String emlContent, String headerName) {
|
private static String extractBasicHeader(String emlContent, String headerName) {
|
||||||
try {
|
try {
|
||||||
String[] lines = emlContent.split("\r?\n");
|
String[] lines =
|
||||||
|
RegexPatternUtils.getInstance().getNewlineSplitPattern().split(emlContent);
|
||||||
for (int i = 0; i < lines.length; i++) {
|
for (int i = 0; i < lines.length; i++) {
|
||||||
String line = lines[i];
|
String line = lines[i];
|
||||||
if (line.toLowerCase().startsWith(headerName.toLowerCase())) {
|
if (line.toLowerCase().startsWith(headerName.toLowerCase())) {
|
||||||
@@ -485,7 +490,10 @@ public class EmlParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int findPartEnd(String content, int start) {
|
private static int findPartEnd(String content, int start) {
|
||||||
String[] lines = content.substring(start).split("\r?\n");
|
String[] lines =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getNewlineSplitPattern()
|
||||||
|
.split(content.substring(start));
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
for (String line : lines) {
|
for (String line : lines) {
|
||||||
@@ -499,7 +507,8 @@ public class EmlParser {
|
|||||||
private static List<EmailAttachment> extractAttachmentsBasic(String emlContent) {
|
private static List<EmailAttachment> extractAttachmentsBasic(String emlContent) {
|
||||||
List<EmailAttachment> attachments = new ArrayList<>();
|
List<EmailAttachment> attachments = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
String[] lines = emlContent.split("\r?\n");
|
String[] lines =
|
||||||
|
RegexPatternUtils.getInstance().getNewlineSplitPattern().split(emlContent);
|
||||||
boolean inHeaders = true;
|
boolean inHeaders = true;
|
||||||
String currentContentType = "";
|
String currentContentType = "";
|
||||||
String currentDisposition = "";
|
String currentDisposition = "";
|
||||||
@@ -562,7 +571,11 @@ public class EmlParser {
|
|||||||
if (filenameStarEnd == -1) filenameStarEnd = disposition.length();
|
if (filenameStarEnd == -1) filenameStarEnd = disposition.length();
|
||||||
String extendedFilename =
|
String extendedFilename =
|
||||||
disposition.substring(filenameStarStart, filenameStarEnd).trim();
|
disposition.substring(filenameStarStart, filenameStarEnd).trim();
|
||||||
extendedFilename = extendedFilename.replaceAll("^\"|\"$", "");
|
extendedFilename =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getQuotesRemovalPattern()
|
||||||
|
.matcher(extendedFilename)
|
||||||
|
.replaceAll("");
|
||||||
|
|
||||||
if (extendedFilename.contains("'")) {
|
if (extendedFilename.contains("'")) {
|
||||||
String[] parts = extendedFilename.split("'", 3);
|
String[] parts = extendedFilename.split("'", 3);
|
||||||
@@ -577,7 +590,11 @@ public class EmlParser {
|
|||||||
int filenameEnd = disposition.indexOf(";", filenameStart);
|
int filenameEnd = disposition.indexOf(";", filenameStart);
|
||||||
if (filenameEnd == -1) filenameEnd = disposition.length();
|
if (filenameEnd == -1) filenameEnd = disposition.length();
|
||||||
String filename = disposition.substring(filenameStart, filenameEnd).trim();
|
String filename = disposition.substring(filenameStart, filenameEnd).trim();
|
||||||
filename = filename.replaceAll("^\"|\"$", "");
|
filename =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getQuotesRemovalPattern()
|
||||||
|
.matcher(filename)
|
||||||
|
.replaceAll("");
|
||||||
return safeMimeDecode(filename);
|
return safeMimeDecode(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,11 +647,23 @@ public class EmlParser {
|
|||||||
private List<EmailAttachment> attachments = new ArrayList<>();
|
private List<EmailAttachment> attachments = new ArrayList<>();
|
||||||
|
|
||||||
public void setHtmlBody(String htmlBody) {
|
public void setHtmlBody(String htmlBody) {
|
||||||
this.htmlBody = htmlBody != null ? htmlBody.replaceAll("\r", "") : null;
|
this.htmlBody =
|
||||||
|
htmlBody != null
|
||||||
|
? RegexPatternUtils.getInstance()
|
||||||
|
.getCarriageReturnPattern()
|
||||||
|
.matcher(htmlBody)
|
||||||
|
.replaceAll("")
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTextBody(String textBody) {
|
public void setTextBody(String textBody) {
|
||||||
this.textBody = textBody != null ? textBody.replaceAll("\r", "") : null;
|
this.textBody =
|
||||||
|
textBody != null
|
||||||
|
? RegexPatternUtils.getInstance()
|
||||||
|
.getCarriageReturnPattern()
|
||||||
|
.matcher(textBody)
|
||||||
|
.replaceAll("")
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -199,8 +199,16 @@ public class EmlProcessingUtils {
|
|||||||
String processed =
|
String processed =
|
||||||
customHtmlSanitizer != null ? customHtmlSanitizer.sanitize(htmlBody) : htmlBody;
|
customHtmlSanitizer != null ? customHtmlSanitizer.sanitize(htmlBody) : htmlBody;
|
||||||
|
|
||||||
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*fixed[^;]*;?", "");
|
processed =
|
||||||
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*absolute[^;]*;?", "");
|
RegexPatternUtils.getInstance()
|
||||||
|
.getFixedPositionCssPattern()
|
||||||
|
.matcher(processed)
|
||||||
|
.replaceAll("");
|
||||||
|
processed =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getAbsolutePositionCssPattern()
|
||||||
|
.matcher(processed)
|
||||||
|
.replaceAll("");
|
||||||
|
|
||||||
if (emailContent != null && !emailContent.getAttachments().isEmpty()) {
|
if (emailContent != null && !emailContent.getAttachments().isEmpty()) {
|
||||||
processed = PdfAttachmentHandler.processInlineImages(processed, emailContent);
|
processed = PdfAttachmentHandler.processInlineImages(processed, emailContent);
|
||||||
@@ -222,13 +230,17 @@ public class EmlProcessingUtils {
|
|||||||
html = html.replace("\n", "<br>\n");
|
html = html.replace("\n", "<br>\n");
|
||||||
|
|
||||||
html =
|
html =
|
||||||
html.replaceAll(
|
RegexPatternUtils.getInstance()
|
||||||
"(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)",
|
.getUrlLinkPattern()
|
||||||
|
.matcher(html)
|
||||||
|
.replaceAll(
|
||||||
"<a href=\"$1\" style=\"color: #1a73e8; text-decoration: underline;\">$1</a>");
|
"<a href=\"$1\" style=\"color: #1a73e8; text-decoration: underline;\">$1</a>");
|
||||||
|
|
||||||
html =
|
html =
|
||||||
html.replaceAll(
|
RegexPatternUtils.getInstance()
|
||||||
"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,63})",
|
.getEmailLinkPattern()
|
||||||
|
.matcher(html)
|
||||||
|
.replaceAll(
|
||||||
"<a href=\"mailto:$1\" style=\"color: #1a73e8; text-decoration: underline;\">$1</a>");
|
"<a href=\"mailto:$1\" style=\"color: #1a73e8; text-decoration: underline;\">$1</a>");
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
@@ -490,9 +502,13 @@ public class EmlProcessingUtils {
|
|||||||
Matcher concatenatedMatcher = concatenatedPattern.matcher(encodedText);
|
Matcher concatenatedMatcher = concatenatedPattern.matcher(encodedText);
|
||||||
String processedText =
|
String processedText =
|
||||||
concatenatedMatcher.replaceAll(
|
concatenatedMatcher.replaceAll(
|
||||||
match -> match.group().replaceAll("\\s+(?==\\?)", ""));
|
match ->
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getMimeHeaderWhitespacePattern()
|
||||||
|
.matcher(match.group())
|
||||||
|
.replaceAll(""));
|
||||||
|
|
||||||
Pattern mimePattern = Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
|
Pattern mimePattern = RegexPatternUtils.getInstance().getMimeEncodedWordPattern();
|
||||||
Matcher matcher = mimePattern.matcher(processedText);
|
Matcher matcher = mimePattern.matcher(processedText);
|
||||||
int lastEnd = 0;
|
int lastEnd = 0;
|
||||||
|
|
||||||
@@ -507,7 +523,11 @@ public class EmlProcessingUtils {
|
|||||||
String decodedValue =
|
String decodedValue =
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case "B" -> {
|
case "B" -> {
|
||||||
String cleanBase64 = encodedValue.replaceAll("\\s", "");
|
String cleanBase64 =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getWhitespacePattern()
|
||||||
|
.matcher(encodedValue)
|
||||||
|
.replaceAll("");
|
||||||
byte[] decodedBytes = Base64.getDecoder().decode(cleanBase64);
|
byte[] decodedBytes = Base64.getDecoder().decode(cleanBase64);
|
||||||
Charset targetCharset;
|
Charset targetCharset;
|
||||||
try {
|
try {
|
||||||
@@ -596,8 +616,16 @@ public class EmlProcessingUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String simplifyHtmlContent(String htmlContent) {
|
public static String simplifyHtmlContent(String htmlContent) {
|
||||||
String simplified = htmlContent.replaceAll("(?i)<script[^>]*>.*?</script>", "");
|
String simplified =
|
||||||
simplified = simplified.replaceAll("(?i)<style[^>]*>.*?</style>", "");
|
RegexPatternUtils.getInstance()
|
||||||
|
.getScriptTagPattern()
|
||||||
|
.matcher(htmlContent)
|
||||||
|
.replaceAll("");
|
||||||
|
simplified =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getStyleTagPattern()
|
||||||
|
.matcher(simplified)
|
||||||
|
.replaceAll("");
|
||||||
return simplified;
|
return simplified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package stirling.software.common.util;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -205,15 +208,27 @@ public class FileToPdf {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
// Remove any drive letters (e.g., "C:\") and leading forward/backslashes
|
// Remove any drive letters (e.g., "C:\") and leading forward/backslashes
|
||||||
entryName = entryName.replaceAll("^[a-zA-Z]:[\\\\/]+", "");
|
entryName =
|
||||||
entryName = entryName.replaceAll("^[\\\\/]+", "");
|
RegexPatternUtils.getInstance()
|
||||||
|
.getDriveLetterPattern()
|
||||||
|
.matcher(entryName)
|
||||||
|
.replaceAll("");
|
||||||
|
entryName =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getLeadingSlashesPattern()
|
||||||
|
.matcher(entryName)
|
||||||
|
.replaceAll("");
|
||||||
|
|
||||||
// Recursively remove path traversal sequences
|
// Recursively remove path traversal sequences
|
||||||
while (entryName.contains("../") || entryName.contains("..\\")) {
|
while (entryName.contains("../") || entryName.contains("..\\")) {
|
||||||
entryName = entryName.replace("../", "").replace("..\\", "");
|
entryName = entryName.replace("../", "").replace("..\\", "");
|
||||||
}
|
}
|
||||||
// Normalize all backslashes to forward slashes
|
// Normalize all backslashes to forward slashes
|
||||||
entryName = entryName.replaceAll("\\\\", "/");
|
entryName =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getBackslashPattern()
|
||||||
|
.matcher(entryName)
|
||||||
|
.replaceAll("/");
|
||||||
return entryName;
|
return entryName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,9 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
import java.util.regex.Matcher;
|
||||||
import java.util.Enumeration;
|
import java.util.regex.Pattern;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@@ -28,25 +24,37 @@ import com.fathzer.soft.javaluator.DoubleEvaluator;
|
|||||||
import io.github.pixee.security.HostValidator;
|
import io.github.pixee.security.HostValidator;
|
||||||
import io.github.pixee.security.Urls;
|
import io.github.pixee.security.Urls;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.configuration.InstallationPathConfig;
|
import stirling.software.common.configuration.InstallationPathConfig;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@UtilityClass
|
||||||
public class GeneralUtils {
|
public class GeneralUtils {
|
||||||
|
|
||||||
private static final Set<String> DEFAULT_VALID_SCRIPTS =
|
private final Set<String> DEFAULT_VALID_SCRIPTS = Set.of("png_to_webp.py", "split_photos.py");
|
||||||
Set.of("png_to_webp.py", "split_photos.py");
|
private final Set<String> DEFAULT_VALID_PIPELINE =
|
||||||
private static final Set<String> DEFAULT_VALID_PIPELINE =
|
|
||||||
Set.of(
|
Set.of(
|
||||||
"OCR images.json",
|
"OCR images.json",
|
||||||
"Prepare-pdfs-for-email.json",
|
"Prepare-pdfs-for-email.json",
|
||||||
"split-rotate-auto-rename.json");
|
"split-rotate-auto-rename.json");
|
||||||
|
|
||||||
private static final String DEFAULT_WEBUI_CONFIGS_DIR = "defaultWebUIConfigs";
|
private final String DEFAULT_WEBUI_CONFIGS_DIR = "defaultWebUIConfigs";
|
||||||
private static final String PYTHON_SCRIPTS_DIR = "python";
|
private final String PYTHON_SCRIPTS_DIR = "python";
|
||||||
|
private final RegexPatternUtils patternCache = RegexPatternUtils.getInstance();
|
||||||
|
// Valid size units used for convertSizeToBytes validation and parsing
|
||||||
|
private final Set<String> VALID_SIZE_UNITS = Set.of("B", "KB", "MB", "GB", "TB");
|
||||||
|
|
||||||
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
|
/*
|
||||||
|
* Converts a MultipartFile to a regular File with improved performance and security.
|
||||||
|
*
|
||||||
|
* @param multipartFile the multipart file to convert
|
||||||
|
* @return temporary File containing the multipart file data
|
||||||
|
* @throws IOException if I/O error occurs during conversion
|
||||||
|
* @throws IllegalArgumentException if file exceeds maximum allowed size
|
||||||
|
*/
|
||||||
|
public File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
|
||||||
String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY");
|
String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY");
|
||||||
if (customTempDir == null || customTempDir.isEmpty()) {
|
if (customTempDir == null || customTempDir.isEmpty()) {
|
||||||
customTempDir = System.getProperty("stirling.tempfiles.directory");
|
customTempDir = System.getProperty("stirling.tempfiles.directory");
|
||||||
@@ -81,10 +89,137 @@ public class GeneralUtils {
|
|||||||
return tempFile;
|
return tempFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteDirectory(Path path) throws IOException {
|
/*
|
||||||
|
* Gets the configured temporary directory, creating it if necessary.
|
||||||
|
*
|
||||||
|
* @return Path to the temporary directory
|
||||||
|
* @throws IOException if directory creation fails
|
||||||
|
*/
|
||||||
|
private Path getTempDirectory() throws IOException {
|
||||||
|
String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY");
|
||||||
|
if (customTempDir == null || customTempDir.isEmpty()) {
|
||||||
|
customTempDir = System.getProperty("stirling.tempfiles.directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
Path tempDir;
|
||||||
|
if (customTempDir != null && !customTempDir.isEmpty()) {
|
||||||
|
tempDir = Path.of(customTempDir);
|
||||||
|
} else {
|
||||||
|
tempDir = Path.of(System.getProperty("java.io.tmpdir"), "stirling-pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(tempDir)) {
|
||||||
|
Files.createDirectories(tempDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove file extension
|
||||||
|
*
|
||||||
|
* <p>Uses fast string operations for common cases (valid extensions) and falls back to
|
||||||
|
* optimized regex for edge cases (no extension, hidden files, etc.).
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>String operations avoid regex engine overhead for common cases
|
||||||
|
* <li>Cached pattern compilation eliminates recompilation costs
|
||||||
|
* <li>Fresh Matcher instances ensure thread safety
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param filename the filename to process, may be null
|
||||||
|
* @return filename without extension, or "default" if input is null
|
||||||
|
*/
|
||||||
|
public String removeExtension(String filename) {
|
||||||
|
if (filename == null) {
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dotIndex = filename.lastIndexOf('.');
|
||||||
|
if (dotIndex > 0 && dotIndex < filename.length() - 1) {
|
||||||
|
return filename.substring(0, dotIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dotIndex == 0 || dotIndex == filename.length() - 1 || dotIndex == -1) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern pattern = patternCache.getPattern(RegexPatternUtils.getExtensionRegex());
|
||||||
|
Matcher matcher = pattern.matcher(filename);
|
||||||
|
return matcher.find() ? matcher.replaceFirst("") : filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Append suffix to base name with null safety.
|
||||||
|
*
|
||||||
|
* @param baseName the base filename, null becomes "default"
|
||||||
|
* @param suffix the suffix to append, null becomes empty string
|
||||||
|
* @return concatenated string with null safety
|
||||||
|
*/
|
||||||
|
public String appendSuffix(String baseName, String suffix) {
|
||||||
|
return (baseName == null ? "default" : baseName) + (suffix != null ? suffix : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate a PDF filename by removing extension from first file and adding suffix.
|
||||||
|
*
|
||||||
|
* <p>High-level utility method for common PDF naming scenarios. Handles null safety and uses
|
||||||
|
* extension removal.
|
||||||
|
*
|
||||||
|
* @param firstFilename the filename of the first file being, may be null
|
||||||
|
* @param suffix the suffix to append (e.g., "_merged.pdf")
|
||||||
|
* @return filename with suffix, or default name if input is null
|
||||||
|
*/
|
||||||
|
public String generateFilename(String firstFilename, String suffix) {
|
||||||
|
String baseName = removeExtension(firstFilename);
|
||||||
|
return appendSuffix(baseName, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process a list of filenames by removing extensions and adding suffix.
|
||||||
|
*
|
||||||
|
* <p>Efficiently processes multiple filenames using streaming operations and bulk operations
|
||||||
|
* where possible. Handles null safety for both input list and individual filenames.
|
||||||
|
*
|
||||||
|
* @param filenames the list of filenames to process, may be null
|
||||||
|
* @param suffix the suffix to append to each processed filename
|
||||||
|
* @param processor consumer to handle each processed filename, may be null
|
||||||
|
*/
|
||||||
|
public void processFilenames(
|
||||||
|
List<String> filenames, String suffix, java.util.function.Consumer<String> processor) {
|
||||||
|
if (filenames == null || processor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filenames.stream()
|
||||||
|
.map(filename -> appendSuffix(removeExtension(filename), suffix))
|
||||||
|
.forEach(processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract title from filename by removing extension, with fallback handling.
|
||||||
|
*
|
||||||
|
* <p>Returns "Untitled" for null or empty filenames, otherwise removes the extension using the
|
||||||
|
* optimized removeExtension method.
|
||||||
|
*
|
||||||
|
* @param filename the filename to extract title from, may be null
|
||||||
|
* @return the title without extension, or "Untitled" if input is null/empty
|
||||||
|
*/
|
||||||
|
public String getTitleFromFilename(String filename) {
|
||||||
|
if (filename == null || filename.isEmpty()) {
|
||||||
|
return "Untitled";
|
||||||
|
}
|
||||||
|
return removeExtension(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteDirectory(Path path) throws IOException {
|
||||||
Files.walkFileTree(
|
Files.walkFileTree(
|
||||||
path,
|
path,
|
||||||
new SimpleFileVisitor<Path>() {
|
new SimpleFileVisitor<>() {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@@ -101,8 +236,18 @@ public class GeneralUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String convertToFileName(String name) {
|
public String convertToFileName(String name) {
|
||||||
String safeName = name.replaceAll("[^a-zA-Z0-9]", "_");
|
if (name == null) return "_";
|
||||||
|
StringBuilder safeNameBuilder = new StringBuilder(name.length());
|
||||||
|
for (int i = 0; i < name.length(); i++) {
|
||||||
|
char c = name.charAt(i);
|
||||||
|
if (Character.isLetterOrDigit(c)) {
|
||||||
|
safeNameBuilder.append(c);
|
||||||
|
} else {
|
||||||
|
safeNameBuilder.append('_');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String safeName = safeNameBuilder.toString();
|
||||||
if (safeName.length() > 50) {
|
if (safeName.length() > 50) {
|
||||||
safeName = safeName.substring(0, 50);
|
safeName = safeName.substring(0, 50);
|
||||||
}
|
}
|
||||||
@@ -110,19 +255,20 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get resources from a location pattern
|
// Get resources from a location pattern
|
||||||
public static Resource[] getResourcesFromLocationPattern(
|
public Resource[] getResourcesFromLocationPattern(
|
||||||
String locationPattern, ResourceLoader resourceLoader) throws Exception {
|
String locationPattern, ResourceLoader resourceLoader) throws Exception {
|
||||||
// Normalize the path for file resources
|
// Normalize the path for file resources
|
||||||
if (locationPattern.startsWith("file:")) {
|
String pattern = locationPattern;
|
||||||
String rawPath = locationPattern.substring(5).replace("\\*", "").replace("/*", "");
|
if (pattern.startsWith("file:")) {
|
||||||
|
String rawPath = pattern.substring(5).replace("\\*", "").replace("/*", "");
|
||||||
Path normalizePath = Paths.get(rawPath).normalize();
|
Path normalizePath = Paths.get(rawPath).normalize();
|
||||||
locationPattern = "file:" + normalizePath.toString().replace("\\", "/") + "/*";
|
pattern = "file:" + normalizePath.toString().replace("\\", "/") + "/*";
|
||||||
}
|
}
|
||||||
return ResourcePatternUtils.getResourcePatternResolver(resourceLoader)
|
return ResourcePatternUtils.getResourcePatternResolver(resourceLoader)
|
||||||
.getResources(locationPattern);
|
.getResources(pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidURL(String urlStr) {
|
public boolean isValidURL(String urlStr) {
|
||||||
try {
|
try {
|
||||||
Urls.create(
|
Urls.create(
|
||||||
urlStr, Urls.HTTP_PROTOCOLS, HostValidator.DENY_COMMON_INFRASTRUCTURE_TARGETS);
|
urlStr, Urls.HTTP_PROTOCOLS, HostValidator.DENY_COMMON_INFRASTRUCTURE_TARGETS);
|
||||||
@@ -132,7 +278,25 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isURLReachable(String urlStr) {
|
/*
|
||||||
|
* Checks if a URL is reachable with proper timeout configuration and error handling.
|
||||||
|
*
|
||||||
|
* @param urlStr the URL string to check
|
||||||
|
* @return true if URL is reachable, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isURLReachable(String urlStr) {
|
||||||
|
return isURLReachable(urlStr, 5000, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if a URL is reachable with configurable timeouts.
|
||||||
|
*
|
||||||
|
* @param urlStr the URL string to check
|
||||||
|
* @param connectTimeout connection timeout in milliseconds
|
||||||
|
* @param readTimeout read timeout in milliseconds
|
||||||
|
* @return true if URL is reachable, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isURLReachable(String urlStr, int connectTimeout, int readTimeout) {
|
||||||
try {
|
try {
|
||||||
// Parse the URL
|
// Parse the URL
|
||||||
URL url = URI.create(urlStr).toURL();
|
URL url = URI.create(urlStr).toURL();
|
||||||
@@ -152,16 +316,19 @@ public class GeneralUtils {
|
|||||||
// Check if the URL is reachable
|
// Check if the URL is reachable
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
connection.setRequestMethod("HEAD");
|
connection.setRequestMethod("HEAD");
|
||||||
// connection.setConnectTimeout(5000); // Set connection timeout
|
connection.setConnectTimeout(connectTimeout);
|
||||||
// connection.setReadTimeout(5000); // Set read timeout
|
connection.setReadTimeout(readTimeout);
|
||||||
|
connection.setInstanceFollowRedirects(false); // Security: prevent redirect loops
|
||||||
|
|
||||||
int responseCode = connection.getResponseCode();
|
int responseCode = connection.getResponseCode();
|
||||||
return (200 <= responseCode && responseCode <= 399);
|
return (200 <= responseCode && responseCode <= 399);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
log.debug("URL {} is not reachable: {}", urlStr, e.getMessage());
|
||||||
return false; // Return false in case of any exception
|
return false; // Return false in case of any exception
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isLocalAddress(String host) {
|
private boolean isLocalAddress(String host) {
|
||||||
try {
|
try {
|
||||||
// Resolve DNS to IP address
|
// Resolve DNS to IP address
|
||||||
InetAddress address = InetAddress.getByName(host);
|
InetAddress address = InetAddress.getByName(host);
|
||||||
@@ -181,7 +348,14 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File multipartToFile(MultipartFile multipart) throws IOException {
|
/*
|
||||||
|
* Improved multipart file conversion using the shared helper method.
|
||||||
|
*
|
||||||
|
* @param multipart the multipart file to convert
|
||||||
|
* @return temporary File containing the multipart file data
|
||||||
|
* @throws IOException if I/O error occurs during conversion
|
||||||
|
*/
|
||||||
|
public File multipartToFile(MultipartFile multipart) throws IOException {
|
||||||
Path tempFile = Files.createTempFile("overlay-", ".pdf");
|
Path tempFile = Files.createTempFile("overlay-", ".pdf");
|
||||||
try (InputStream in = multipart.getInputStream();
|
try (InputStream in = multipart.getInputStream();
|
||||||
FileOutputStream out = new FileOutputStream(tempFile.toFile())) {
|
FileOutputStream out = new FileOutputStream(tempFile.toFile())) {
|
||||||
@@ -194,54 +368,105 @@ public class GeneralUtils {
|
|||||||
return tempFile.toFile();
|
return tempFile.toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Long convertSizeToBytes(String sizeStr) {
|
/*
|
||||||
|
* Supports TB/PB units and provides detailed error messages.
|
||||||
|
*
|
||||||
|
* @param sizeStr the size string to convert (e.g., "100MB", "1.5GB")
|
||||||
|
* @param defaultUnit the default unit to assume if none specified ("MB", "GB", etc.)
|
||||||
|
* @return size in bytes, or null if parsing fails
|
||||||
|
* @throws IllegalArgumentException if defaultUnit is invalid
|
||||||
|
*/
|
||||||
|
public Long convertSizeToBytes(String sizeStr, String defaultUnit) {
|
||||||
if (sizeStr == null) {
|
if (sizeStr == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (defaultUnit != null && !isValidSizeUnit(defaultUnit)) {
|
||||||
|
throw new IllegalArgumentException("Invalid default unit: " + defaultUnit);
|
||||||
|
}
|
||||||
|
|
||||||
sizeStr = sizeStr.trim().toUpperCase();
|
sizeStr = sizeStr.trim().toUpperCase();
|
||||||
sizeStr = sizeStr.replace(",", ".").replace(" ", "");
|
sizeStr = sizeStr.replace(",", ".").replace(" ", "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (sizeStr.endsWith("KB")) {
|
if (sizeStr.endsWith("TB")) {
|
||||||
return (long)
|
|
||||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
|
|
||||||
} else if (sizeStr.endsWith("MB")) {
|
|
||||||
return (long)
|
return (long)
|
||||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||||
* 1024
|
* 1024L
|
||||||
* 1024);
|
* 1024L
|
||||||
|
* 1024L
|
||||||
|
* 1024L);
|
||||||
} else if (sizeStr.endsWith("GB")) {
|
} else if (sizeStr.endsWith("GB")) {
|
||||||
return (long)
|
return (long)
|
||||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||||
* 1024
|
* 1024L
|
||||||
* 1024
|
* 1024L
|
||||||
* 1024);
|
* 1024L);
|
||||||
} else if (sizeStr.endsWith("B")) {
|
} else if (sizeStr.endsWith("MB")) {
|
||||||
|
return (long)
|
||||||
|
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||||
|
* 1024L
|
||||||
|
* 1024L);
|
||||||
|
} else if (sizeStr.endsWith("KB")) {
|
||||||
|
return (long)
|
||||||
|
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024L);
|
||||||
|
} else if (!sizeStr.isEmpty() && sizeStr.charAt(sizeStr.length() - 1) == 'B') {
|
||||||
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
||||||
} else {
|
} else {
|
||||||
// Assume MB if no unit is specified
|
// Use provided default unit or fall back to MB
|
||||||
return (long) (Double.parseDouble(sizeStr) * 1024 * 1024);
|
String unit = defaultUnit != null ? defaultUnit.toUpperCase() : "MB";
|
||||||
|
double value = Double.parseDouble(sizeStr);
|
||||||
|
return switch (unit) {
|
||||||
|
case "TB" -> (long) (value * 1024L * 1024L * 1024L * 1024L);
|
||||||
|
case "GB" -> (long) (value * 1024L * 1024L * 1024L);
|
||||||
|
case "MB" -> (long) (value * 1024L * 1024L);
|
||||||
|
case "KB" -> (long) (value * 1024L);
|
||||||
|
case "B" -> (long) value;
|
||||||
|
default -> (long) (value * 1024L * 1024L); // Default to MB
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// The numeric part of the input string cannot be parsed, handle this case
|
log.warn("Failed to parse size string '{}': {}", sizeStr, e.getMessage());
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts size string to bytes using MB as default unit.
|
||||||
|
*
|
||||||
|
* @param sizeStr the size string to convert
|
||||||
|
* @return size in bytes, or null if parsing fails
|
||||||
|
*/
|
||||||
|
public Long convertSizeToBytes(String sizeStr) {
|
||||||
|
return convertSizeToBytes(sizeStr, "MB");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validates if a string represents a valid size unit. */
|
||||||
|
private boolean isValidSizeUnit(String unit) {
|
||||||
|
// Use a precomputed Set for O(1) lookup, normalize using a locale-safe toUpperCase
|
||||||
|
return unit != null && VALID_SIZE_UNITS.contains(unit.toUpperCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced byte formatting with TB/PB support and better precision. */
|
||||||
|
public String formatBytes(long bytes) {
|
||||||
|
if (bytes < 0) {
|
||||||
|
return "Invalid size";
|
||||||
|
}
|
||||||
|
|
||||||
public static String formatBytes(long bytes) {
|
|
||||||
if (bytes < 1024) {
|
if (bytes < 1024) {
|
||||||
return bytes + " B";
|
return bytes + " B";
|
||||||
} else if (bytes < 1024 * 1024) {
|
} else if (bytes < 1024L * 1024L) {
|
||||||
return String.format(Locale.US, "%.2f KB", bytes / 1024.0);
|
return String.format(Locale.US, "%.2f KB", bytes / 1024.0);
|
||||||
} else if (bytes < 1024 * 1024 * 1024) {
|
} else if (bytes < 1024L * 1024L * 1024L) {
|
||||||
return String.format(Locale.US, "%.2f MB", bytes / (1024.0 * 1024.0));
|
return String.format(Locale.US, "%.2f MB", bytes / (1024.0 * 1024.0));
|
||||||
} else {
|
} else if (bytes < 1024L * 1024L * 1024L * 1024L) {
|
||||||
return String.format(Locale.US, "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
return String.format(Locale.US, "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
||||||
|
} else {
|
||||||
|
return String.format(Locale.US, "%.2f TB", bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> parsePageList(String pages, int totalPages, boolean oneBased) {
|
public List<Integer> parsePageList(String pages, int totalPages, boolean oneBased) {
|
||||||
if (pages == null) {
|
if (pages == null) {
|
||||||
return List.of(1); // Default to first page if input is null
|
return List.of(1); // Default to first page if input is null
|
||||||
}
|
}
|
||||||
@@ -252,11 +477,11 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> parsePageList(String[] pages, int totalPages) {
|
public List<Integer> parsePageList(String[] pages, int totalPages) {
|
||||||
return parsePageList(pages, totalPages, false);
|
return parsePageList(pages, totalPages, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> parsePageList(String[] pages, int totalPages, boolean oneBased) {
|
public List<Integer> parsePageList(String[] pages, int totalPages, boolean oneBased) {
|
||||||
List<Integer> result = new ArrayList<>();
|
List<Integer> result = new ArrayList<>();
|
||||||
int offset = oneBased ? 1 : 0;
|
int offset = oneBased ? 1 : 0;
|
||||||
for (String page : pages) {
|
for (String page : pages) {
|
||||||
@@ -278,33 +503,72 @@ public class GeneralUtils {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> evaluateNFunc(String expression, int maxValue) {
|
/*
|
||||||
|
* Enhanced mathematical expression evaluation with bounds checking and timeout protection.
|
||||||
|
*
|
||||||
|
* @param expression the mathematical expression containing 'n'
|
||||||
|
* @param maxValue the maximum value for 'n' and result bounds
|
||||||
|
* @return list of valid page numbers
|
||||||
|
* @throws IllegalArgumentException if expression is invalid or unsafe
|
||||||
|
*/
|
||||||
|
public List<Integer> evaluateNFunc(String expression, int maxValue) {
|
||||||
|
if (expression == null || expression.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Expression cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxValue <= 0 || maxValue > 10000) {
|
||||||
|
throw new IllegalArgumentException("maxValue must be between 1 and 10000 for safety");
|
||||||
|
}
|
||||||
|
|
||||||
List<Integer> results = new ArrayList<>();
|
List<Integer> results = new ArrayList<>();
|
||||||
DoubleEvaluator evaluator = new DoubleEvaluator();
|
DoubleEvaluator evaluator = new DoubleEvaluator();
|
||||||
|
|
||||||
// Validate the expression
|
// Validate the expression format
|
||||||
if (!expression.matches("[0-9n+\\-*/() ]+")) {
|
if (!RegexPatternUtils.getInstance()
|
||||||
throw new IllegalArgumentException("Invalid expression");
|
.getMathExpressionPattern()
|
||||||
|
.matcher(expression.trim())
|
||||||
|
.matches()) {
|
||||||
|
throw new IllegalArgumentException("Invalid expression format: " + expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int n = 1; n <= maxValue; n++) {
|
for (int n = 1; n <= maxValue; n++) {
|
||||||
// Replace 'n' with the current value of n, correctly handling numbers before
|
try {
|
||||||
// 'n'
|
// Replace 'n' with the current value of n, correctly handling numbers before 'n'
|
||||||
String sanitizedExpression = sanitizeNFunction(expression, n);
|
String sanitizedExpression = sanitizeNFunction(expression.trim(), n);
|
||||||
Double result = evaluator.evaluate(sanitizedExpression);
|
Double result = evaluator.evaluate(sanitizedExpression);
|
||||||
|
|
||||||
// Check if the result is null or not within bounds
|
// Check if the result is null or not within bounds
|
||||||
if (result == null) break;
|
if (result == null || !Double.isFinite(result)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.intValue() > 0 && result.intValue() <= maxValue)
|
int intResult = result.intValue();
|
||||||
results.add(result.intValue());
|
if (intResult > 0 && intResult <= maxValue) {
|
||||||
|
results.add(intResult);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug(
|
||||||
|
"Failed to evaluate expression '{}' for n={}: {}",
|
||||||
|
expression,
|
||||||
|
n,
|
||||||
|
e.getMessage());
|
||||||
|
// Continue with next value instead of breaking
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String sanitizeNFunction(String expression, int nValue) {
|
private String sanitizeNFunction(String expression, int nValue) {
|
||||||
String sanitizedExpression = expression.replace(" ", "");
|
// Remove all spaces using a specialized character removal
|
||||||
|
StringBuilder sb = new StringBuilder(expression.length());
|
||||||
|
for (int i = 0; i < expression.length(); i++) {
|
||||||
|
char c = expression.charAt(i);
|
||||||
|
if (c != ' ') {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String sanitizedExpression = sb.toString();
|
||||||
String multiplyByOpeningRoundBracketPattern =
|
String multiplyByOpeningRoundBracketPattern =
|
||||||
"([0-9n)])\\("; // example: n(n-1), 9(n-1), (n-1)(n-2)
|
"([0-9n)])\\("; // example: n(n-1), 9(n-1), (n-1)(n-2)
|
||||||
sanitizedExpression =
|
sanitizedExpression =
|
||||||
@@ -319,23 +583,45 @@ public class GeneralUtils {
|
|||||||
return sanitizedExpression;
|
return sanitizedExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String insertMultiplicationBeforeN(String expression, int nValue) {
|
private String insertMultiplicationBeforeN(String expression, int nValue) {
|
||||||
// Insert multiplication between a number and 'n' (e.g., "4n" becomes "4*n")
|
// Insert multiplication between a number and 'n' (e.g., "4n" becomes "4*n") using a loop
|
||||||
String withMultiplication = expression.replaceAll("(\\d)n", "$1*n");
|
StringBuilder sb = new StringBuilder(expression.length() + 4); // +4 for possible extra '*'
|
||||||
|
for (int i = 0; i < expression.length(); i++) {
|
||||||
|
char c = expression.charAt(i);
|
||||||
|
sb.append(c);
|
||||||
|
if (Character.isDigit(c)
|
||||||
|
&& i + 1 < expression.length()
|
||||||
|
&& expression.charAt(i + 1) == 'n') {
|
||||||
|
sb.append('*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String withMultiplication = sb.toString();
|
||||||
withMultiplication = formatConsecutiveNsForNFunction(withMultiplication);
|
withMultiplication = formatConsecutiveNsForNFunction(withMultiplication);
|
||||||
// Now replace 'n' with its current value
|
// Now replace 'n' with its current value
|
||||||
return withMultiplication.replace("n", String.valueOf(nValue));
|
return withMultiplication.replace("n", String.valueOf(nValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatConsecutiveNsForNFunction(String expression) {
|
private String formatConsecutiveNsForNFunction(String expression) {
|
||||||
String text = expression;
|
String text = expression;
|
||||||
while (text.matches(".*n{2,}.*")) {
|
// Replace all consecutive 'nn' with 'n*n' until no more 'nn' is found
|
||||||
text = text.replaceAll("(?<!n)n{2}", "n*n");
|
while (text.contains("nn")) {
|
||||||
|
StringBuilder sb = new StringBuilder(text.length() + 2); // +2 for possible extra '*'
|
||||||
|
int i = 0;
|
||||||
|
while (i < text.length()) {
|
||||||
|
if (i < text.length() - 1 && text.charAt(i) == 'n' && text.charAt(i + 1) == 'n') {
|
||||||
|
sb.append("n*n");
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
sb.append(text.charAt(i));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = sb.toString();
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Integer> handlePart(String part, int totalPages, int offset) {
|
private List<Integer> handlePart(String part, int totalPages, int offset) {
|
||||||
List<Integer> partResult = new ArrayList<>();
|
List<Integer> partResult = new ArrayList<>();
|
||||||
|
|
||||||
// First check for n-syntax because it should not be processed as a range
|
// First check for n-syntax because it should not be processed as a range
|
||||||
@@ -361,7 +647,7 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// Range is invalid, ignore this part
|
log.debug("Invalid range: {}", part);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is a single page number
|
// This is a single page number
|
||||||
@@ -370,14 +656,14 @@ public class GeneralUtils {
|
|||||||
if (pageNum >= 1 && pageNum <= totalPages) {
|
if (pageNum >= 1 && pageNum <= totalPages) {
|
||||||
partResult.add(pageNum - 1 + offset);
|
partResult.add(pageNum - 1 + offset);
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException e) {
|
||||||
// Ignore invalid numbers
|
log.debug("Invalid page number: {}", part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return partResult;
|
return partResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean createDir(String path) {
|
public boolean createDir(String path) {
|
||||||
Path folder = Paths.get(path);
|
Path folder = Paths.get(path);
|
||||||
if (!Files.exists(folder)) {
|
if (!Files.exists(folder)) {
|
||||||
try {
|
try {
|
||||||
@@ -390,7 +676,7 @@ public class GeneralUtils {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidUUID(String uuid) {
|
public boolean isValidUUID(String uuid) {
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -406,7 +692,7 @@ public class GeneralUtils {
|
|||||||
* Internal Implementation Details *
|
* Internal Implementation Details *
|
||||||
*------------------------------------------------------------------------*/
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
public static void saveKeyToSettings(String key, Object newValue) throws IOException {
|
public void saveKeyToSettings(String key, Object newValue) throws IOException {
|
||||||
String[] keyArray = key.split("\\.");
|
String[] keyArray = key.split("\\.");
|
||||||
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||||
YamlHelper settingsYaml = new YamlHelper(settingsPath);
|
YamlHelper settingsYaml = new YamlHelper(settingsPath);
|
||||||
@@ -414,48 +700,67 @@ public class GeneralUtils {
|
|||||||
settingsYaml.saveOverride(settingsPath);
|
settingsYaml.saveOverride(settingsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generateMachineFingerprint() {
|
/*
|
||||||
|
* Machine fingerprint generation with better error logging and fallbacks.
|
||||||
|
*
|
||||||
|
* @return unique machine fingerprint or "GenericID" if generation fails
|
||||||
|
*/
|
||||||
|
public String generateMachineFingerprint() {
|
||||||
try {
|
try {
|
||||||
// Get the MAC address
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Try to get MAC address from primary network interface
|
||||||
InetAddress ip = InetAddress.getLocalHost();
|
InetAddress ip = InetAddress.getLocalHost();
|
||||||
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
|
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
|
||||||
|
|
||||||
if (network == null) {
|
if (network == null || network.getHardwareAddress() == null) {
|
||||||
|
// Fallback: iterate through all network interfaces
|
||||||
Enumeration<NetworkInterface> networks = NetworkInterface.getNetworkInterfaces();
|
Enumeration<NetworkInterface> networks = NetworkInterface.getNetworkInterfaces();
|
||||||
while (networks.hasMoreElements()) {
|
while (networks.hasMoreElements()) {
|
||||||
NetworkInterface net = networks.nextElement();
|
NetworkInterface net = networks.nextElement();
|
||||||
|
if (net.isUp() && !net.isLoopback() && !net.isVirtual()) {
|
||||||
byte[] mac = net.getHardwareAddress();
|
byte[] mac = net.getHardwareAddress();
|
||||||
if (mac != null) {
|
if (mac != null && mac.length > 0) {
|
||||||
for (int i = 0; i < mac.length; i++) {
|
for (byte b : mac) {
|
||||||
sb.append(String.format("%02X", mac[i]));
|
sb.append(String.format("%02X", b));
|
||||||
|
}
|
||||||
|
break; // Use the first valid network interface
|
||||||
}
|
}
|
||||||
break; // Use the first network interface with a MAC address
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
byte[] mac = network.getHardwareAddress();
|
byte[] mac = network.getHardwareAddress();
|
||||||
if (mac != null) {
|
if (mac != null) {
|
||||||
for (int i = 0; i < mac.length; i++) {
|
for (byte b : mac) {
|
||||||
sb.append(String.format("%02X", mac[i]));
|
sb.append(String.format("%02X", b));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash the MAC address for privacy and consistency
|
// If no MAC address found, use hostname as fallback
|
||||||
|
if (sb.length() == 0) {
|
||||||
|
String hostname = InetAddress.getLocalHost().getHostName();
|
||||||
|
sb.append(hostname != null ? hostname : "unknown-host");
|
||||||
|
log.warn("No MAC address found, using hostname for fingerprint generation");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the collected data for privacy and consistency
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
|
byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
|
||||||
StringBuilder fingerprint = new StringBuilder();
|
StringBuilder fingerprint = new StringBuilder();
|
||||||
for (byte b : hash) {
|
for (byte b : hash) {
|
||||||
fingerprint.append(String.format("%02x", b));
|
fingerprint.append(String.format("%02x", b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug("Successfully generated machine fingerprint");
|
||||||
return fingerprint.toString();
|
return fingerprint.toString();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to generate machine fingerprint: {}", e.getMessage());
|
||||||
return "GenericID";
|
return "GenericID";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Extracts the default pipeline configurations from the classpath to the installation path.
|
* Extracts the default pipeline configurations from the classpath to the installation path.
|
||||||
* Creates directories if needed and copies default JSON files.
|
* Creates directories if needed and copies default JSON files.
|
||||||
*
|
*
|
||||||
@@ -464,7 +769,7 @@ public class GeneralUtils {
|
|||||||
*
|
*
|
||||||
* @throws IOException if an I/O error occurs during file operations
|
* @throws IOException if an I/O error occurs during file operations
|
||||||
*/
|
*/
|
||||||
public static void extractPipeline() throws IOException {
|
public void extractPipeline() throws IOException {
|
||||||
Path pipelineDir =
|
Path pipelineDir =
|
||||||
Paths.get(InstallationPathConfig.getPipelinePath(), DEFAULT_WEBUI_CONFIGS_DIR);
|
Paths.get(InstallationPathConfig.getPipelinePath(), DEFAULT_WEBUI_CONFIGS_DIR);
|
||||||
Files.createDirectories(pipelineDir);
|
Files.createDirectories(pipelineDir);
|
||||||
@@ -486,7 +791,7 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Extracts the specified Python script from the classpath to the installation path. Validates
|
* Extracts the specified Python script from the classpath to the installation path. Validates
|
||||||
* name and copies file atomically when possible, overwriting existing.
|
* name and copies file atomically when possible, overwriting existing.
|
||||||
*
|
*
|
||||||
@@ -497,7 +802,7 @@ public class GeneralUtils {
|
|||||||
* @throws IllegalArgumentException if the script name is invalid or not allowed
|
* @throws IllegalArgumentException if the script name is invalid or not allowed
|
||||||
* @throws IOException if an I/O error occurs
|
* @throws IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public static Path extractScript(String scriptName) throws IOException {
|
public Path extractScript(String scriptName) throws IOException {
|
||||||
// Validate input
|
// Validate input
|
||||||
if (scriptName == null || scriptName.trim().isEmpty()) {
|
if (scriptName == null || scriptName.trim().isEmpty()) {
|
||||||
throw new IllegalArgumentException("scriptName must not be null or empty");
|
throw new IllegalArgumentException("scriptName must not be null or empty");
|
||||||
@@ -530,15 +835,14 @@ public class GeneralUtils {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Copies a resource from the classpath to a specified target file.
|
* Copies a resource from the classpath to a specified target file.
|
||||||
*
|
*
|
||||||
* @param resource the ClassPathResource to copy
|
* @param resource the ClassPathResource to copy
|
||||||
* @param target the target Path where the resource will be copied
|
* @param target the target Path where the resource will be copied
|
||||||
* @throws IOException if an I/O error occurs during the copy operation
|
* @throws IOException if an I/O error occurs during the copy operation
|
||||||
*/
|
*/
|
||||||
private static void copyResourceToFile(ClassPathResource resource, Path target)
|
private void copyResourceToFile(ClassPathResource resource, Path target) throws IOException {
|
||||||
throws IOException {
|
|
||||||
Path dir = target.getParent();
|
Path dir = target.getParent();
|
||||||
Path tmp = Files.createTempFile(dir, target.getFileName().toString(), ".tmp");
|
Path tmp = Files.createTempFile(dir, target.getFileName().toString(), ".tmp");
|
||||||
try (InputStream in = resource.getInputStream()) {
|
try (InputStream in = resource.getInputStream()) {
|
||||||
@@ -573,7 +877,7 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
|
public boolean isVersionHigher(String currentVersion, String compareVersion) {
|
||||||
if (currentVersion == null || compareVersion == null) {
|
if (currentVersion == null || compareVersion == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+131
-125
@@ -258,10 +258,7 @@ public class PdfAttachmentHandler {
|
|||||||
|
|
||||||
if (contentIdMap.isEmpty()) return htmlContent;
|
if (contentIdMap.isEmpty()) return htmlContent;
|
||||||
|
|
||||||
Pattern cidPattern =
|
Pattern cidPattern = RegexPatternUtils.getInstance().getInlineCidImagePattern();
|
||||||
Pattern.compile(
|
|
||||||
"(?i)<img[^>]*\\ssrc\\s*=\\s*['\"]cid:([^'\"]+)['\"][^>]*>",
|
|
||||||
Pattern.CASE_INSENSITIVE);
|
|
||||||
Matcher matcher = cidPattern.matcher(htmlContent);
|
Matcher matcher = cidPattern.matcher(htmlContent);
|
||||||
|
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
@@ -319,121 +316,20 @@ public class PdfAttachmentHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AttachmentMarkerPositionFinder extends PDFTextStripper {
|
private static String normalizeFilename(String filename) {
|
||||||
@Getter private final List<MarkerPosition> positions = new ArrayList<>();
|
if (filename == null) return "";
|
||||||
private int currentPageIndex;
|
String normalized = filename.toLowerCase().trim();
|
||||||
protected boolean sortByPosition;
|
normalized =
|
||||||
private boolean isInAttachmentSection;
|
RegexPatternUtils.getInstance()
|
||||||
private boolean attachmentSectionFound;
|
.getWhitespacePattern()
|
||||||
private final StringBuilder currentText = new StringBuilder();
|
.matcher(normalized)
|
||||||
|
.replaceAll(" ");
|
||||||
private static final Pattern ATTACHMENT_SECTION_PATTERN =
|
normalized =
|
||||||
Pattern.compile("attachments\\s*\\(\\d+\\)", Pattern.CASE_INSENSITIVE);
|
RegexPatternUtils.getInstance()
|
||||||
|
.getPattern("[^a-zA-Z0-9._-]")
|
||||||
private static final Pattern FILENAME_PATTERN =
|
.matcher(normalized)
|
||||||
Pattern.compile("@\\s*([^\\s\\(]+(?:\\.[a-zA-Z0-9]+)?)");
|
.replaceAll("");
|
||||||
|
return normalized;
|
||||||
public AttachmentMarkerPositionFinder() {
|
|
||||||
super();
|
|
||||||
this.currentPageIndex = 0;
|
|
||||||
this.sortByPosition = false; // Disable sorting to preserve document order
|
|
||||||
this.isInAttachmentSection = false;
|
|
||||||
this.attachmentSectionFound = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getText(PDDocument document) throws IOException {
|
|
||||||
super.getText(document);
|
|
||||||
|
|
||||||
if (sortByPosition) {
|
|
||||||
positions.sort(
|
|
||||||
(a, b) -> {
|
|
||||||
int pageCompare = Integer.compare(a.getPageIndex(), b.getPageIndex());
|
|
||||||
if (pageCompare != 0) return pageCompare;
|
|
||||||
return Float.compare(
|
|
||||||
b.getY(), a.getY()); // Descending Y per PDF coordinate system
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""; // Return empty string as we only need positions
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void startPage(PDPage page) throws IOException {
|
|
||||||
super.startPage(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void endPage(PDPage page) throws IOException {
|
|
||||||
currentPageIndex++;
|
|
||||||
super.endPage(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void writeString(String string, List<TextPosition> textPositions)
|
|
||||||
throws IOException {
|
|
||||||
String lowerString = string.toLowerCase();
|
|
||||||
|
|
||||||
if (ATTACHMENT_SECTION_PATTERN.matcher(lowerString).find()) {
|
|
||||||
isInAttachmentSection = true;
|
|
||||||
attachmentSectionFound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInAttachmentSection
|
|
||||||
&& (lowerString.contains("</body>")
|
|
||||||
|| lowerString.contains("</html>")
|
|
||||||
|| (attachmentSectionFound
|
|
||||||
&& lowerString.trim().isEmpty()
|
|
||||||
&& string.length() > 50))) {
|
|
||||||
isInAttachmentSection = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInAttachmentSection) {
|
|
||||||
currentText.append(string);
|
|
||||||
|
|
||||||
for (int i = 0; (i = string.indexOf(ATTACHMENT_MARKER, i)) != -1; i++) {
|
|
||||||
if (i < textPositions.size()) {
|
|
||||||
TextPosition textPosition = textPositions.get(i);
|
|
||||||
|
|
||||||
String filename = extractFilenameAfterMarker(string, i);
|
|
||||||
|
|
||||||
MarkerPosition position =
|
|
||||||
new MarkerPosition(
|
|
||||||
currentPageIndex,
|
|
||||||
textPosition.getXDirAdj(),
|
|
||||||
textPosition.getYDirAdj(),
|
|
||||||
ATTACHMENT_MARKER,
|
|
||||||
filename);
|
|
||||||
positions.add(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.writeString(string, textPositions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSortByPosition(boolean sortByPosition) {
|
|
||||||
this.sortByPosition = sortByPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractFilenameAfterMarker(String text, int markerIndex) {
|
|
||||||
String afterMarker = text.substring(markerIndex + 1);
|
|
||||||
|
|
||||||
Matcher matcher = FILENAME_PATTERN.matcher("@" + afterMarker);
|
|
||||||
if (matcher.find()) {
|
|
||||||
return matcher.group(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] parts = afterMarker.split("[\\s\\(\\)]+");
|
|
||||||
for (String part : parts) {
|
|
||||||
part = part.trim();
|
|
||||||
if (part.length() > 3 && part.contains(".")) {
|
|
||||||
return part;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<Integer, String> addAttachmentsToDocumentWithMapping(
|
private static Map<Integer, String> addAttachmentsToDocumentWithMapping(
|
||||||
@@ -613,12 +509,122 @@ public class PdfAttachmentHandler {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String normalizeFilename(String filename) {
|
public static class AttachmentMarkerPositionFinder extends PDFTextStripper {
|
||||||
if (filename == null) return "";
|
private static final Pattern ATTACHMENT_SECTION_PATTERN =
|
||||||
return filename.toLowerCase()
|
RegexPatternUtils.getInstance().getAttachmentSectionPattern();
|
||||||
.trim()
|
private static final Pattern FILENAME_PATTERN =
|
||||||
.replaceAll("\\s+", " ")
|
RegexPatternUtils.getInstance().getAttachmentFilenamePattern();
|
||||||
.replaceAll("[^a-zA-Z0-9._-]", "");
|
@Getter private final List<MarkerPosition> positions = new ArrayList<>();
|
||||||
|
private final StringBuilder currentText = new StringBuilder();
|
||||||
|
protected boolean sortByPosition;
|
||||||
|
private int currentPageIndex;
|
||||||
|
private boolean isInAttachmentSection;
|
||||||
|
private boolean attachmentSectionFound;
|
||||||
|
|
||||||
|
public AttachmentMarkerPositionFinder() {
|
||||||
|
super();
|
||||||
|
this.currentPageIndex = 0;
|
||||||
|
this.sortByPosition = false; // Disable sorting to preserve document order
|
||||||
|
this.isInAttachmentSection = false;
|
||||||
|
this.attachmentSectionFound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText(PDDocument document) throws IOException {
|
||||||
|
super.getText(document);
|
||||||
|
|
||||||
|
if (sortByPosition) {
|
||||||
|
positions.sort(
|
||||||
|
(a, b) -> {
|
||||||
|
int pageCompare = Integer.compare(a.getPageIndex(), b.getPageIndex());
|
||||||
|
if (pageCompare != 0) return pageCompare;
|
||||||
|
return Float.compare(
|
||||||
|
b.getY(), a.getY()); // Descending Y per PDF coordinate system
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""; // Return empty string as we only need positions
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startPage(PDPage page) throws IOException {
|
||||||
|
super.startPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void endPage(PDPage page) throws IOException {
|
||||||
|
currentPageIndex++;
|
||||||
|
super.endPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeString(String string, List<TextPosition> textPositions)
|
||||||
|
throws IOException {
|
||||||
|
String lowerString = string.toLowerCase();
|
||||||
|
|
||||||
|
if (ATTACHMENT_SECTION_PATTERN.matcher(lowerString).find()) {
|
||||||
|
isInAttachmentSection = true;
|
||||||
|
attachmentSectionFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInAttachmentSection
|
||||||
|
&& (lowerString.contains("</body>")
|
||||||
|
|| lowerString.contains("</html>")
|
||||||
|
|| (attachmentSectionFound
|
||||||
|
&& lowerString.trim().isEmpty()
|
||||||
|
&& string.length() > 50))) {
|
||||||
|
isInAttachmentSection = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInAttachmentSection) {
|
||||||
|
currentText.append(string);
|
||||||
|
|
||||||
|
for (int i = 0; (i = string.indexOf(ATTACHMENT_MARKER, i)) != -1; i++) {
|
||||||
|
if (i < textPositions.size()) {
|
||||||
|
TextPosition textPosition = textPositions.get(i);
|
||||||
|
|
||||||
|
String filename = extractFilenameAfterMarker(string, i);
|
||||||
|
|
||||||
|
MarkerPosition position =
|
||||||
|
new MarkerPosition(
|
||||||
|
currentPageIndex,
|
||||||
|
textPosition.getXDirAdj(),
|
||||||
|
textPosition.getYDirAdj(),
|
||||||
|
ATTACHMENT_MARKER,
|
||||||
|
filename);
|
||||||
|
positions.add(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.writeString(string, textPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSortByPosition(boolean sortByPosition) {
|
||||||
|
this.sortByPosition = sortByPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractFilenameAfterMarker(String text, int markerIndex) {
|
||||||
|
String afterMarker = text.substring(markerIndex + 1);
|
||||||
|
|
||||||
|
Matcher matcher = FILENAME_PATTERN.matcher("@" + afterMarker);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getWhitespaceParenthesesSplitPattern()
|
||||||
|
.split(afterMarker);
|
||||||
|
for (String part : parts) {
|
||||||
|
part = part.trim();
|
||||||
|
if (part.length() > 3 && part.contains(".")) {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addAttachmentAnnotationToPageWithMapping(
|
private static void addAttachmentAnnotationToPageWithMapping(
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
@@ -34,15 +36,20 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
import io.github.pixee.security.Filenames;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@UtilityClass
|
||||||
public class PdfUtils {
|
public class PdfUtils {
|
||||||
|
|
||||||
public static PDRectangle textToPageSize(String size) {
|
private final RegexPatternUtils patternCache = RegexPatternUtils.getInstance();
|
||||||
|
|
||||||
|
public PDRectangle textToPageSize(String size) {
|
||||||
|
|
||||||
switch (size.toUpperCase()) {
|
switch (size.toUpperCase()) {
|
||||||
case "A0" -> {
|
case "A0" -> {
|
||||||
return PDRectangle.A0;
|
return PDRectangle.A0;
|
||||||
@@ -75,7 +82,7 @@ public class PdfUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<RenderedImage> getAllImages(PDResources resources) throws IOException {
|
public List<RenderedImage> getAllImages(PDResources resources) throws IOException {
|
||||||
List<RenderedImage> images = new ArrayList<>();
|
List<RenderedImage> images = new ArrayList<>();
|
||||||
|
|
||||||
for (COSName name : resources.getXObjectNames()) {
|
for (COSName name : resources.getXObjectNames()) {
|
||||||
@@ -92,7 +99,7 @@ public class PdfUtils {
|
|||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
|
public boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
|
||||||
String[] pageOrderArr = pagesToCheck.split(",");
|
String[] pageOrderArr = pagesToCheck.split(",");
|
||||||
List<Integer> pageList =
|
List<Integer> pageList =
|
||||||
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
|
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
|
||||||
@@ -107,7 +114,7 @@ public class PdfUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase)
|
public boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String[] pageOrderArr = pageNumbersToCheck.split(",");
|
String[] pageOrderArr = pageNumbersToCheck.split(",");
|
||||||
List<Integer> pageList =
|
List<Integer> pageList =
|
||||||
@@ -123,11 +130,11 @@ public class PdfUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasImagesOnPage(PDPage page) throws IOException {
|
public boolean hasImagesOnPage(PDPage page) throws IOException {
|
||||||
return getAllImages(page.getResources()).size() > 0;
|
return getAllImages(page.getResources()).size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException {
|
public boolean hasTextOnPage(PDPage page, String phrase) throws IOException {
|
||||||
PDFTextStripper textStripper = new PDFTextStripper();
|
PDFTextStripper textStripper = new PDFTextStripper();
|
||||||
PDDocument tempDoc = new PDDocument();
|
PDDocument tempDoc = new PDDocument();
|
||||||
tempDoc.addPage(page);
|
tempDoc.addPage(page);
|
||||||
@@ -136,7 +143,7 @@ public class PdfUtils {
|
|||||||
return pageText.contains(phrase);
|
return pageText.contains(phrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] convertFromPdf(
|
public byte[] convertFromPdf(
|
||||||
CustomPDFDocumentFactory pdfDocumentFactory,
|
CustomPDFDocumentFactory pdfDocumentFactory,
|
||||||
byte[] inputStream,
|
byte[] inputStream,
|
||||||
String imageType,
|
String imageType,
|
||||||
@@ -379,7 +386,7 @@ public class PdfUtils {
|
|||||||
* @return converted document to PDF-Image
|
* @return converted document to PDF-Image
|
||||||
* @throws IOException if conversion fails
|
* @throws IOException if conversion fails
|
||||||
*/
|
*/
|
||||||
public static PDDocument convertPdfToPdfImage(PDDocument document) throws IOException {
|
public PDDocument convertPdfToPdfImage(PDDocument document) throws IOException {
|
||||||
PDDocument imageDocument = new PDDocument();
|
PDDocument imageDocument = new PDDocument();
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
pdfRenderer.setSubsamplingAllowed(true);
|
pdfRenderer.setSubsamplingAllowed(true);
|
||||||
@@ -428,8 +435,7 @@ public class PdfUtils {
|
|||||||
return imageDocument;
|
return imageDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BufferedImage prepareImageForPdfToImage(
|
private BufferedImage prepareImageForPdfToImage(int maxWidth, int height, String imageType) {
|
||||||
int maxWidth, int height, String imageType) {
|
|
||||||
BufferedImage combined;
|
BufferedImage combined;
|
||||||
if ("png".equalsIgnoreCase(imageType)) {
|
if ("png".equalsIgnoreCase(imageType)) {
|
||||||
combined = new BufferedImage(maxWidth, height, BufferedImage.TYPE_INT_ARGB);
|
combined = new BufferedImage(maxWidth, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
@@ -445,7 +451,7 @@ public class PdfUtils {
|
|||||||
return combined;
|
return combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] imageToPdf(
|
public byte[] imageToPdf(
|
||||||
MultipartFile[] files,
|
MultipartFile[] files,
|
||||||
String fitOption,
|
String fitOption,
|
||||||
boolean autoRotate,
|
boolean autoRotate,
|
||||||
@@ -489,7 +495,7 @@ public class PdfUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addImageToDocument(
|
public void addImageToDocument(
|
||||||
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate)
|
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
boolean imageIsLandscape = image.getWidth() > image.getHeight();
|
boolean imageIsLandscape = image.getWidth() > image.getHeight();
|
||||||
@@ -539,7 +545,7 @@ public class PdfUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] overlayImage(
|
public byte[] overlayImage(
|
||||||
CustomPDFDocumentFactory pdfDocumentFactory,
|
CustomPDFDocumentFactory pdfDocumentFactory,
|
||||||
byte[] pdfBytes,
|
byte[] pdfBytes,
|
||||||
byte[] imageBytes,
|
byte[] imageBytes,
|
||||||
@@ -586,8 +592,11 @@ public class PdfUtils {
|
|||||||
if (pagesToCheck == null || "all".equals(pagesToCheck)) {
|
if (pagesToCheck == null || "all".equals(pagesToCheck)) {
|
||||||
pdfText = new StringBuilder(textStripper.getText(pdfDocument));
|
pdfText = new StringBuilder(textStripper.getText(pdfDocument));
|
||||||
} else {
|
} else {
|
||||||
// remove whitespaces
|
// remove whitespaces using cached pattern
|
||||||
pagesToCheck = pagesToCheck.replaceAll("\\s+", "");
|
Pattern whitespacePattern =
|
||||||
|
patternCache.getPattern(RegexPatternUtils.getWhitespaceRegex());
|
||||||
|
Matcher whitespaceMatcher = whitespacePattern.matcher(pagesToCheck);
|
||||||
|
pagesToCheck = whitespaceMatcher.replaceAll("");
|
||||||
|
|
||||||
String[] splitPoints = pagesToCheck.split(",");
|
String[] splitPoints = pagesToCheck.split(",");
|
||||||
for (String splitPoint : splitPoints) {
|
for (String splitPoint : splitPoints) {
|
||||||
|
|||||||
@@ -0,0 +1,506 @@
|
|||||||
|
package stirling.software.common.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public final class RegexPatternUtils {
|
||||||
|
|
||||||
|
private static final RegexPatternUtils INSTANCE = new RegexPatternUtils();
|
||||||
|
private final ConcurrentHashMap<PatternKey, Pattern> patternCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final String WHITESPACE_REGEX = "\\s++";
|
||||||
|
private static final String EXTENSION_REGEX = "\\.(?:[^.]*+)?$";
|
||||||
|
|
||||||
|
private RegexPatternUtils() {
|
||||||
|
super();
|
||||||
|
// Initialize with commonly used patterns for immediate availability
|
||||||
|
precompileCommonPatterns();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton instance of the pattern cache.
|
||||||
|
*
|
||||||
|
* @return the singleton RegexPatternCache instance
|
||||||
|
*/
|
||||||
|
public static RegexPatternUtils getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a compiled pattern from cache, compiling and caching if not present.
|
||||||
|
*
|
||||||
|
* <p>This method is thread-safe and uses lazy initialization. Multiple threads calling with the
|
||||||
|
* same regex will result in only one compilation, with all threads receiving the same cached
|
||||||
|
* Pattern instance.
|
||||||
|
*
|
||||||
|
* <p>Performance: first call compiles and caches (expensive), subsequent calls return cached
|
||||||
|
* pattern (fast O(1) lookup).
|
||||||
|
*
|
||||||
|
* @param regex the regular expression string to compile
|
||||||
|
* @return compiled Pattern object, never null
|
||||||
|
* @throws PatternSyntaxException if the regex syntax is invalid
|
||||||
|
* @throws IllegalArgumentException if regex is null
|
||||||
|
*/
|
||||||
|
public Pattern getPattern(String regex) {
|
||||||
|
if (regex == null) {
|
||||||
|
throw new IllegalArgumentException("Regex pattern cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return patternCache.computeIfAbsent(new PatternKey(regex, 0), this::compilePattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a compiled pattern with flags.
|
||||||
|
*
|
||||||
|
* <p>Patterns with different flags are cached separately using a composite key. Common flags
|
||||||
|
* include:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link Pattern#CASE_INSENSITIVE} - ignore case differences
|
||||||
|
* <li>{@link Pattern#MULTILINE} - ^ and $ match line boundaries
|
||||||
|
* <li>{@link Pattern#DOTALL} - . matches any character including newlines
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param regex the regular expression string
|
||||||
|
* @param flags pattern flags (e.g., Pattern.CASE_INSENSITIVE)
|
||||||
|
* @return compiled Pattern object with specified flags
|
||||||
|
* @throws PatternSyntaxException if the regex syntax is invalid
|
||||||
|
* @throws IllegalArgumentException if regex is null
|
||||||
|
*/
|
||||||
|
public Pattern getPattern(String regex, int flags) {
|
||||||
|
if (regex == null) {
|
||||||
|
throw new IllegalArgumentException("Regex pattern cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return patternCache.computeIfAbsent(new PatternKey(regex, flags), this::compilePattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a pattern is already cached.
|
||||||
|
*
|
||||||
|
* @param regex the regular expression string
|
||||||
|
* @return true if pattern is cached, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isCached(String regex) {
|
||||||
|
return isCached(regex, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a pattern with flags is already cached.
|
||||||
|
*
|
||||||
|
* @param regex the regular expression string
|
||||||
|
* @param flags pattern flags
|
||||||
|
* @return true if pattern is cached, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isCached(String regex, int flags) {
|
||||||
|
return regex != null && patternCache.containsKey(new PatternKey(regex, flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current cache size (number of cached patterns). Useful for monitoring and debugging.
|
||||||
|
*
|
||||||
|
* @return number of patterns currently cached
|
||||||
|
*/
|
||||||
|
public int getCacheSize() {
|
||||||
|
return patternCache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all cached patterns. Use sparingly as it forces recompilation of all patterns. Mainly
|
||||||
|
* useful for testing or memory cleanup in long-running applications.
|
||||||
|
*/
|
||||||
|
public void clearCache() {
|
||||||
|
patternCache.clear();
|
||||||
|
log.debug("Regex pattern cache cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific pattern from cache.
|
||||||
|
*
|
||||||
|
* @param regex the regular expression string to remove
|
||||||
|
* @return true if pattern was cached and removed, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean removeFromCache(String regex) {
|
||||||
|
return removeFromCache(regex, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific pattern with flags from cache.
|
||||||
|
*
|
||||||
|
* @param regex the regular expression string to remove
|
||||||
|
* @param flags pattern flags
|
||||||
|
* @return true if pattern was cached and removed, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean removeFromCache(String regex, int flags) {
|
||||||
|
if (regex == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PatternKey key = new PatternKey(regex, flags);
|
||||||
|
boolean removed = patternCache.remove(key) != null;
|
||||||
|
if (removed) {
|
||||||
|
log.debug("Removed regex pattern from cache: {} (flags: {})", regex, flags);
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method to compile a pattern and handle errors consistently.
|
||||||
|
*
|
||||||
|
* @return compiled Pattern
|
||||||
|
* @throws PatternSyntaxException if regex is invalid
|
||||||
|
*/
|
||||||
|
private Pattern compilePattern(PatternKey key) {
|
||||||
|
String regex = key.regex;
|
||||||
|
int flags = key.flags;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Pattern pattern = Pattern.compile(regex, flags);
|
||||||
|
log.trace("Compiled and cached regex pattern with flags {}: {}", flags, regex);
|
||||||
|
return pattern;
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
log.error(
|
||||||
|
"Invalid regex pattern: '{}' with flags {} - {}", regex, flags, e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getWhitespaceRegex() {
|
||||||
|
return WHITESPACE_REGEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a case-insensitive pattern for text searching */
|
||||||
|
public Pattern createSearchPattern(String regex, boolean caseInsensitive) {
|
||||||
|
int flags = caseInsensitive ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0;
|
||||||
|
return getPattern(regex, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching trailing slashes (e.g., "/path/to/dir///") */
|
||||||
|
public Pattern getTrailingSlashesPattern() {
|
||||||
|
return getPattern("/+$");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing drive letters from paths */
|
||||||
|
public Pattern getDriveLetterPattern() {
|
||||||
|
return getPattern("^[a-zA-Z]:[\\\\/]+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing leading slashes from paths */
|
||||||
|
public Pattern getLeadingSlashesPattern() {
|
||||||
|
return getPattern("^[\\\\/]+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching backslashes */
|
||||||
|
public Pattern getBackslashPattern() {
|
||||||
|
return getPattern("\\\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for sanitizing filenames by removing problematic characters */
|
||||||
|
public Pattern getSafeFilenamePattern() {
|
||||||
|
return getPattern("[/\\\\?%*:|\"<>]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for sanitizing filenames (keeps only alphanumeric) */
|
||||||
|
public Pattern getFilenameSafePattern() {
|
||||||
|
return getPattern("[^a-zA-Z0-9]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern for replacing non-alphanumeric characters with underscore (explicit underscore
|
||||||
|
* variant)
|
||||||
|
*/
|
||||||
|
public Pattern getNonAlnumUnderscorePattern() {
|
||||||
|
return getPattern("[^A-Za-z0-9_]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for collapsing multiple underscores */
|
||||||
|
public Pattern getMultipleUnderscoresPattern() {
|
||||||
|
return getPattern("_+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for trimming leading underscores */
|
||||||
|
public Pattern getLeadingUnderscoresPattern() {
|
||||||
|
return getPattern("^_+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for trimming trailing underscores */
|
||||||
|
public Pattern getTrailingUnderscoresPattern() {
|
||||||
|
return getPattern("_+$");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching upload/download paths (case insensitive) */
|
||||||
|
public Pattern getUploadDownloadPathPattern() {
|
||||||
|
return getPattern("(?i).*/(upload|download)/.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching one or more whitespace characters */
|
||||||
|
public Pattern getWhitespacePattern() {
|
||||||
|
return getPattern("\\s+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching newlines (Windows and Unix style) */
|
||||||
|
public Pattern getNewlinesPattern() {
|
||||||
|
return getPattern("\\r?\\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for splitting on newlines (Windows and Unix style) */
|
||||||
|
public Pattern getNewlineSplitPattern() {
|
||||||
|
return getPattern("\\r?\\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for splitting text into words */
|
||||||
|
public Pattern getWordSplitPattern() {
|
||||||
|
return getPattern("\\s+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing carriage returns */
|
||||||
|
public Pattern getCarriageReturnPattern() {
|
||||||
|
return getPattern("\\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching newline characters */
|
||||||
|
public Pattern getNewlineCharsPattern() {
|
||||||
|
return getPattern("[\n\r]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for multi-format newline splitting (Windows, Mac, Unix) */
|
||||||
|
public Pattern getMultiFormatNewlinePattern() {
|
||||||
|
return getPattern("\r\n|\r|\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for encoded payload newline removal */
|
||||||
|
public Pattern getEncodedPayloadNewlinePattern() {
|
||||||
|
return getPattern("\\r?\\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for escaped newlines in watermark text */
|
||||||
|
public Pattern getEscapedNewlinePattern() {
|
||||||
|
return getPattern("\\\\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for input sanitization (allows only alphanumeric and spaces) */
|
||||||
|
public Pattern getInputSanitizePattern() {
|
||||||
|
return getPattern("[^a-zA-Z0-9 ]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing angle brackets */
|
||||||
|
public Pattern getAngleBracketsPattern() {
|
||||||
|
return getPattern("[<>]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing leading and trailing quotes */
|
||||||
|
public Pattern getQuotesRemovalPattern() {
|
||||||
|
return getPattern("^\"|\"$");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for plus signs (URL encoding replacement) */
|
||||||
|
public Pattern getPlusSignPattern() {
|
||||||
|
return getPattern("\\+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for username validation */
|
||||||
|
public Pattern getUsernameValidationPattern() {
|
||||||
|
return getPattern("^[a-zA-Z0-9](?!.*[-@._+]{2,})[a-zA-Z0-9@._+-]{1,48}[a-zA-Z0-9]$");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getExtensionRegex() {
|
||||||
|
return EXTENSION_REGEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for extracting non-numeric characters */
|
||||||
|
public Pattern getNumericExtractionPattern() {
|
||||||
|
return getPattern("\\D");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing non-digit/dot characters (for timeout parsing) */
|
||||||
|
public Pattern getNonDigitDotPattern() {
|
||||||
|
return getPattern("[^\\d.]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching digit/dot characters (for timeout parsing) */
|
||||||
|
public Pattern getDigitDotPattern() {
|
||||||
|
return getPattern("[\\d.]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for detecting strings containing digits */
|
||||||
|
public Pattern getContainsDigitsPattern() {
|
||||||
|
return getPattern(".*\\d+.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching 1-3 digit numbers */
|
||||||
|
public Pattern getNumberRangePattern() {
|
||||||
|
return getPattern("[1-9][0-9]{0,2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for validating mathematical expressions */
|
||||||
|
public Pattern getMathExpressionPattern() {
|
||||||
|
return getPattern("[0-9n+\\-*/() ]+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for adding multiplication between numbers and 'n' */
|
||||||
|
public Pattern getNumberBeforeNPattern() {
|
||||||
|
return getPattern("(\\d)n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for detecting consecutive 'n' characters */
|
||||||
|
public Pattern getConsecutiveNPattern() {
|
||||||
|
return getPattern(".*n{2,}.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for replacing consecutive 'n' characters */
|
||||||
|
public Pattern getConsecutiveNReplacementPattern() {
|
||||||
|
return getPattern("(?<!n)n{2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for validating HTTP/HTTPS URLs */
|
||||||
|
public Pattern getHttpUrlPattern() {
|
||||||
|
return getPattern("^https?://.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching URLs in text for link creation */
|
||||||
|
public Pattern getUrlLinkPattern() {
|
||||||
|
return getPattern("(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching email addresses in text for link creation */
|
||||||
|
public Pattern getEmailLinkPattern() {
|
||||||
|
return getPattern("([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,63})");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing script tags from HTML */
|
||||||
|
public Pattern getScriptTagPattern() {
|
||||||
|
return getPattern("(?i)<script[^>]*>.*?</script>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing style tags from HTML */
|
||||||
|
public Pattern getStyleTagPattern() {
|
||||||
|
return getPattern("(?i)<style[^>]*>.*?</style>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing fixed position CSS */
|
||||||
|
public Pattern getFixedPositionCssPattern() {
|
||||||
|
return getPattern("(?i)\\s*position\\s*:\\s*fixed[^;]*;?");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for removing absolute position CSS */
|
||||||
|
public Pattern getAbsolutePositionCssPattern() {
|
||||||
|
return getPattern("(?i)\\s*position\\s*:\\s*absolute[^;]*;?");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching size unit suffixes (KB, MB, GB, etc.) */
|
||||||
|
public Pattern getSizeUnitPattern() {
|
||||||
|
return getPattern("[KMGkmg][Bb]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for system temp file type 1 */
|
||||||
|
public Pattern getSystemTempFile1Pattern() {
|
||||||
|
return getPattern("lu\\d+[a-z0-9]*\\.tmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for system temp file type 2 (OCR processes) */
|
||||||
|
public Pattern getSystemTempFile2Pattern() {
|
||||||
|
return getPattern("ocr_process\\d+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for splitting on whitespace and parentheses */
|
||||||
|
public Pattern getWhitespaceParenthesesSplitPattern() {
|
||||||
|
return getPattern("[\\s\\(\\)]+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for MIME header whitespace cleanup before encoded sequences */
|
||||||
|
public Pattern getMimeHeaderWhitespacePattern() {
|
||||||
|
return getPattern("\\s+(?==\\?)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for font name validation (6 uppercase letters + plus + rest) */
|
||||||
|
public Pattern getFontNamePattern() {
|
||||||
|
return getPattern("^[A-Z]{6}\\+.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching access="readOnly" attribute in XFA XML (with optional whitespace) */
|
||||||
|
public Pattern getAccessReadOnlyPattern() {
|
||||||
|
return getPattern("access\\s*=\\s*\"readOnly\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching MIME encoded-word headers (RFC 2047) Example: =?charset?B?encoded?= */
|
||||||
|
public Pattern getMimeEncodedWordPattern() {
|
||||||
|
return getPattern("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching inline CID images in HTML (case-insensitive) */
|
||||||
|
public Pattern getInlineCidImagePattern() {
|
||||||
|
return getPattern(
|
||||||
|
"(?i)<img[^>]*\\ssrc\\s*=\\s*['\"]cid:([^'\"]+)['\"][^>]*>",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching attachment section headers (case-insensitive) */
|
||||||
|
public Pattern getAttachmentSectionPattern() {
|
||||||
|
return getPattern("attachments\\s*\\(\\d+\\)", Pattern.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for matching filenames in attachment markers */
|
||||||
|
public Pattern getAttachmentFilenamePattern() {
|
||||||
|
return getPattern("@\\s*([^\\s\\(]+(?:\\.[a-zA-Z0-9]+)?)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// API doc parsing patterns
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-compile commonly used patterns for immediate availability. This eliminates first-call
|
||||||
|
* compilation overhead for frequent patterns.
|
||||||
|
*/
|
||||||
|
private void precompileCommonPatterns() {
|
||||||
|
getPattern("\\.(?:[^.]*+)?$"); // Extension removal - possessive, optional, anchored
|
||||||
|
getPattern("\\.[^.]+$"); // Simple extension match - anchored
|
||||||
|
|
||||||
|
getPattern("\\s+"); // One or more whitespace
|
||||||
|
getPattern("\\s*"); // Zero or more whitespace
|
||||||
|
|
||||||
|
getPattern("/+$"); // Trailing slashes
|
||||||
|
getPattern("\\D"); // Non-numeric characters
|
||||||
|
getPattern("[/\\\\?%*:|\"<>]"); // Unsafe filename characters
|
||||||
|
getPattern("[^a-zA-Z0-9 ]"); // Input sanitization
|
||||||
|
getPattern("[^a-zA-Z0-9]"); // Filename sanitization
|
||||||
|
// API doc patterns
|
||||||
|
getPattern("Output:(\\w+)"); // precompiled single-escaped for runtime regex \w
|
||||||
|
getPattern("Input:(\\w+)");
|
||||||
|
getPattern("Type:(\\w+)");
|
||||||
|
log.debug("Pre-compiled {} common regex patterns", patternCache.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pattern for email validation */
|
||||||
|
public Pattern getEmailValidationPattern() {
|
||||||
|
return getPattern(
|
||||||
|
"^(?=.{1,320}$)(?=.{1,64}@)[A-Za-z0-9](?:[A-Za-z0-9_.+-]*[A-Za-z0-9])?@[^-][A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)*(?:\\.[A-Za-z]{2,})$");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pattern for matching Output:<TYPE> in API descriptions */
|
||||||
|
public Pattern getApiDocOutputTypePattern() {
|
||||||
|
return getPattern("Output:(\\w+)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pattern for matching Input:<TYPE> in API descriptions */
|
||||||
|
public Pattern getApiDocInputTypePattern() {
|
||||||
|
return getPattern("Input:(\\w+)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern for matching Type:<CODE> in API descriptions
|
||||||
|
*/
|
||||||
|
public Pattern getApiDocTypePattern() {
|
||||||
|
return getPattern("Type:(\\w+)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pattern for validating file extensions (2-4 alphanumeric, case-insensitive) */
|
||||||
|
public Pattern getFileExtensionValidationPattern() {
|
||||||
|
return getPattern("^[a-zA-Z0-9]{2,4}$", Pattern.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record PatternKey(String regex, int flags) {
|
||||||
|
// Record automatically provides equals, hashCode, and toString
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,7 +50,10 @@ public class WebResponseUtils {
|
|||||||
headers.setContentType(mediaType);
|
headers.setContentType(mediaType);
|
||||||
headers.setContentLength(bytes.length);
|
headers.setContentLength(bytes.length);
|
||||||
String encodedDocName =
|
String encodedDocName =
|
||||||
URLEncoder.encode(docName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
RegexPatternUtils.getInstance()
|
||||||
|
.getPlusSignPattern()
|
||||||
|
.matcher(URLEncoder.encode(docName, StandardCharsets.UTF_8))
|
||||||
|
.replaceAll("%20");
|
||||||
headers.setContentDispositionFormData("attachment", encodedDocName);
|
headers.setContentDispositionFormData("attachment", encodedDocName);
|
||||||
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -14,6 +14,7 @@ import stirling.software.common.model.ApplicationProperties.Driver;
|
|||||||
import stirling.software.common.model.ApplicationProperties.Premium;
|
import stirling.software.common.model.ApplicationProperties.Premium;
|
||||||
import stirling.software.common.model.ApplicationProperties.Security;
|
import stirling.software.common.model.ApplicationProperties.Security;
|
||||||
import stirling.software.common.model.exception.UnsupportedProviderException;
|
import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
class ApplicationPropertiesLogicTest {
|
class ApplicationPropertiesLogicTest {
|
||||||
|
|
||||||
@@ -38,7 +39,10 @@ class ApplicationPropertiesLogicTest {
|
|||||||
new ApplicationProperties.TempFileManagement();
|
new ApplicationProperties.TempFileManagement();
|
||||||
|
|
||||||
String expectedBase =
|
String expectedBase =
|
||||||
java.lang.System.getProperty("java.io.tmpdir").replaceAll("/+$", "")
|
RegexPatternUtils.getInstance()
|
||||||
|
.getTrailingSlashesPattern()
|
||||||
|
.matcher(java.lang.System.getProperty("java.io.tmpdir"))
|
||||||
|
.replaceAll("")
|
||||||
+ "/stirling-pdf";
|
+ "/stirling-pdf";
|
||||||
assertEquals(expectedBase, normalize.apply(tfm.getBaseTmpDir()));
|
assertEquals(expectedBase, normalize.apply(tfm.getBaseTmpDir()));
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
package stirling.software.common.util;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -154,4 +161,223 @@ public class GeneralUtilsTest {
|
|||||||
List<Integer> result = GeneralUtils.parsePageList(new String[] {"1,3,7-8"}, 8, false);
|
List<Integer> result = GeneralUtils.parsePageList(new String[] {"1,3,7-8"}, 8, false);
|
||||||
assertEquals(List.of(0, 2, 6, 7), result, "Range should be parsed correctly.");
|
assertEquals(List.of(0, 2, 6, 7), result, "Range should be parsed correctly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveExtension() {
|
||||||
|
// Test common cases (should use fast string operations)
|
||||||
|
assertEquals("document", GeneralUtils.removeExtension("document.pdf"));
|
||||||
|
assertEquals("image", GeneralUtils.removeExtension("image.jpg"));
|
||||||
|
assertEquals("file.backup", GeneralUtils.removeExtension("file.backup.zip"));
|
||||||
|
assertEquals("complex.file.name", GeneralUtils.removeExtension("complex.file.name.txt"));
|
||||||
|
|
||||||
|
// Test edge cases (should fall back to regex)
|
||||||
|
assertEquals("default", GeneralUtils.removeExtension(null));
|
||||||
|
assertEquals("noextension", GeneralUtils.removeExtension("noextension"));
|
||||||
|
assertEquals(
|
||||||
|
".hidden", GeneralUtils.removeExtension(".hidden")); // Hidden file, no extension
|
||||||
|
assertEquals("trailing.", GeneralUtils.removeExtension("trailing.")); // Trailing dot
|
||||||
|
assertEquals("", GeneralUtils.removeExtension(""));
|
||||||
|
assertEquals("a", GeneralUtils.removeExtension("a"));
|
||||||
|
|
||||||
|
// Test multiple dots
|
||||||
|
assertEquals("file.with.multiple", GeneralUtils.removeExtension("file.with.multiple.dots"));
|
||||||
|
assertEquals("path/to/file", GeneralUtils.removeExtension("path/to/file.ext"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAppendSuffix() {
|
||||||
|
// Normal cases
|
||||||
|
assertEquals("document_processed", GeneralUtils.appendSuffix("document", "_processed"));
|
||||||
|
assertEquals("file.txt", GeneralUtils.appendSuffix("file", ".txt"));
|
||||||
|
|
||||||
|
// Null handling
|
||||||
|
assertEquals("default_suffix", GeneralUtils.appendSuffix(null, "_suffix"));
|
||||||
|
assertEquals("basename", GeneralUtils.appendSuffix("basename", null));
|
||||||
|
assertEquals("default", GeneralUtils.appendSuffix(null, null));
|
||||||
|
|
||||||
|
// Empty strings
|
||||||
|
assertEquals("_suffix", GeneralUtils.appendSuffix("", "_suffix"));
|
||||||
|
assertEquals("basename", GeneralUtils.appendSuffix("basename", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testProcessFilenames() {
|
||||||
|
List<String> filenames = new ArrayList<>();
|
||||||
|
filenames.add("document.pdf");
|
||||||
|
filenames.add("image.jpg");
|
||||||
|
filenames.add("spreadsheet.xlsx");
|
||||||
|
filenames.add("presentation.pptx");
|
||||||
|
filenames.add(null); // Should handle null gracefully
|
||||||
|
filenames.add("noextension");
|
||||||
|
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
|
GeneralUtils.processFilenames(filenames, "_processed", results::add);
|
||||||
|
|
||||||
|
List<String> expected =
|
||||||
|
List.of(
|
||||||
|
"document_processed",
|
||||||
|
"image_processed",
|
||||||
|
"spreadsheet_processed",
|
||||||
|
"presentation_processed",
|
||||||
|
"default_processed",
|
||||||
|
"noextension_processed");
|
||||||
|
|
||||||
|
assertEquals(expected, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testProcessFilenamesNullHandling() {
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
|
|
||||||
|
// Null filenames list
|
||||||
|
GeneralUtils.processFilenames(null, "_suffix", results::add);
|
||||||
|
assertTrue(results.isEmpty(), "Should handle null filenames list");
|
||||||
|
|
||||||
|
// Null processor
|
||||||
|
List<String> filenames = List.of("test.txt");
|
||||||
|
GeneralUtils.processFilenames(filenames, "_suffix", null); // Should not throw
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveExtensionThreadSafety() throws InterruptedException {
|
||||||
|
final int threadCount = 50;
|
||||||
|
final int operationsPerThread = 100;
|
||||||
|
final String[] testFilenames = {
|
||||||
|
"document.pdf", "image.jpg", "data.csv", "presentation.pptx",
|
||||||
|
"archive.zip", "music.mp3", "video.mp4", "text.txt"
|
||||||
|
};
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
|
||||||
|
CountDownLatch latch = new CountDownLatch(threadCount);
|
||||||
|
AtomicInteger successCount = new AtomicInteger(0);
|
||||||
|
List<Exception> exceptions = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
executor.submit(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
for (int j = 0; j < operationsPerThread; j++) {
|
||||||
|
String filename = testFilenames[j % testFilenames.length];
|
||||||
|
String result = GeneralUtils.removeExtension(filename);
|
||||||
|
|
||||||
|
// Verify result is correct
|
||||||
|
assertFalse(
|
||||||
|
result.contains("."),
|
||||||
|
"Result should not contain extension: " + result);
|
||||||
|
assertTrue(
|
||||||
|
filename.startsWith(result),
|
||||||
|
"Original should start with result: "
|
||||||
|
+ filename
|
||||||
|
+ " -> "
|
||||||
|
+ result);
|
||||||
|
}
|
||||||
|
successCount.incrementAndGet();
|
||||||
|
} catch (Exception e) {
|
||||||
|
exceptions.add(e);
|
||||||
|
} finally {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(latch.await(10, TimeUnit.SECONDS), "All threads should complete");
|
||||||
|
|
||||||
|
if (!exceptions.isEmpty()) {
|
||||||
|
fail("Thread safety test failed with exceptions: " + exceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(threadCount, successCount.get(), "All threads should succeed");
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBatchProcessingPerformance() {
|
||||||
|
List<String> filenames = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
filenames.add("file" + i + ".pdf");
|
||||||
|
filenames.add("document" + i + ".docx");
|
||||||
|
filenames.add("image" + i + ".jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
|
|
||||||
|
GeneralUtils.processFilenames(filenames, "_processed", results::add);
|
||||||
|
|
||||||
|
assertEquals(filenames.size(), results.size(), "Should process all filenames");
|
||||||
|
|
||||||
|
assertTrue(results.contains("file0_processed"), "Should contain processed filename");
|
||||||
|
assertTrue(results.contains("document500_processed"), "Should contain processed filename");
|
||||||
|
assertTrue(results.contains("image999_processed"), "Should contain processed filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHybridStringRegexApproach() {
|
||||||
|
|
||||||
|
String[] edgeCases = {
|
||||||
|
"", // Empty string
|
||||||
|
".", // Just a dot
|
||||||
|
"..", // Two dots
|
||||||
|
"...", // Three dots
|
||||||
|
".hidden", // Hidden file
|
||||||
|
"file.", // Trailing dot
|
||||||
|
"a.b.c.d.e.f.g", // Many extensions
|
||||||
|
"no-extension-here", // No extension
|
||||||
|
"file..double.dot" // Double dots
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String edgeCase : edgeCases) {
|
||||||
|
String result = GeneralUtils.removeExtension(edgeCase);
|
||||||
|
assertNotNull(result, "Result should not be null for: " + edgeCase);
|
||||||
|
|
||||||
|
// For specific edge cases, verify expected behavior
|
||||||
|
switch (edgeCase) {
|
||||||
|
case "" -> assertEquals("", result, "Empty string should remain empty");
|
||||||
|
case "." -> assertEquals(".", result, "Single dot should remain unchanged");
|
||||||
|
case ".." -> assertEquals("..", result, "Double dots should remain unchanged");
|
||||||
|
case "..." -> assertEquals("...", result, "Triple dots should remain unchanged");
|
||||||
|
case ".hidden" ->
|
||||||
|
assertEquals(".hidden", result, "Hidden file should remain unchanged");
|
||||||
|
case "file." ->
|
||||||
|
assertEquals("file.", result, "Trailing dot should remain unchanged");
|
||||||
|
case "no-extension-here" ->
|
||||||
|
assertEquals(
|
||||||
|
"no-extension-here",
|
||||||
|
result,
|
||||||
|
"No extension should remain unchanged");
|
||||||
|
case "a.b.c.d.e.f.g" ->
|
||||||
|
assertEquals(
|
||||||
|
"a.b.c.d.e.f",
|
||||||
|
result,
|
||||||
|
"Multiple extensions should remove last one");
|
||||||
|
case "file..double.dot" ->
|
||||||
|
assertEquals(
|
||||||
|
"file..double",
|
||||||
|
result,
|
||||||
|
"Double dot case should remove last extension");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetTitleFromFilename() {
|
||||||
|
// Test normal cases
|
||||||
|
assertEquals("document", GeneralUtils.getTitleFromFilename("document.pdf"));
|
||||||
|
assertEquals("presentation", GeneralUtils.getTitleFromFilename("presentation.pptx"));
|
||||||
|
assertEquals("file.backup", GeneralUtils.getTitleFromFilename("file.backup.zip"));
|
||||||
|
|
||||||
|
// Test null and empty handling
|
||||||
|
assertEquals("Untitled", GeneralUtils.getTitleFromFilename(null));
|
||||||
|
assertEquals("Untitled", GeneralUtils.getTitleFromFilename(""));
|
||||||
|
|
||||||
|
// Test edge cases
|
||||||
|
assertEquals(".hidden", GeneralUtils.getTitleFromFilename(".hidden"));
|
||||||
|
assertEquals("file.", GeneralUtils.getTitleFromFilename("file."));
|
||||||
|
assertEquals("noextension", GeneralUtils.getTitleFromFilename("noextension"));
|
||||||
|
|
||||||
|
// Test complex cases
|
||||||
|
assertEquals(
|
||||||
|
"complex.file.name", GeneralUtils.getTitleFromFilename("complex.file.name.txt"));
|
||||||
|
assertEquals("path/to/file", GeneralUtils.getTitleFromFilename("path/to/file.ext"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,23 +65,22 @@ public class PdfUtilsTest {
|
|||||||
doc1.addPage(new PDPage());
|
doc1.addPage(new PDPage());
|
||||||
doc1.addPage(new PDPage());
|
doc1.addPage(new PDPage());
|
||||||
doc1.addPage(new PDPage());
|
doc1.addPage(new PDPage());
|
||||||
PdfUtils utils = new PdfUtils();
|
assertTrue(PdfUtils.pageCount(doc1, 2, "greater"));
|
||||||
assertTrue(utils.pageCount(doc1, 2, "greater"));
|
|
||||||
|
|
||||||
PDDocument doc2 = new PDDocument();
|
PDDocument doc2 = new PDDocument();
|
||||||
doc2.addPage(new PDPage());
|
doc2.addPage(new PDPage());
|
||||||
doc2.addPage(new PDPage());
|
doc2.addPage(new PDPage());
|
||||||
doc2.addPage(new PDPage());
|
doc2.addPage(new PDPage());
|
||||||
assertTrue(utils.pageCount(doc2, 3, "equal"));
|
assertTrue(PdfUtils.pageCount(doc2, 3, "equal"));
|
||||||
|
|
||||||
PDDocument doc3 = new PDDocument();
|
PDDocument doc3 = new PDDocument();
|
||||||
doc3.addPage(new PDPage());
|
doc3.addPage(new PDPage());
|
||||||
doc3.addPage(new PDPage());
|
doc3.addPage(new PDPage());
|
||||||
assertTrue(utils.pageCount(doc3, 5, "less"));
|
assertTrue(PdfUtils.pageCount(doc3, 5, "less"));
|
||||||
|
|
||||||
PDDocument doc4 = new PDDocument();
|
PDDocument doc4 = new PDDocument();
|
||||||
doc4.addPage(new PDPage());
|
doc4.addPage(new PDPage());
|
||||||
assertThrows(IllegalArgumentException.class, () -> utils.pageCount(doc4, 1, "bad"));
|
assertThrows(IllegalArgumentException.class, () -> PdfUtils.pageCount(doc4, 1, "bad"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -91,8 +90,7 @@ public class PdfUtilsTest {
|
|||||||
doc.addPage(page);
|
doc.addPage(page);
|
||||||
PDRectangle rect = page.getMediaBox();
|
PDRectangle rect = page.getMediaBox();
|
||||||
String expected = rect.getWidth() + "x" + rect.getHeight();
|
String expected = rect.getWidth() + "x" + rect.getHeight();
|
||||||
PdfUtils utils = new PdfUtils();
|
assertTrue(PdfUtils.pageSize(doc, expected));
|
||||||
assertTrue(utils.pageSize(doc, expected));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package stirling.software.common.util;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class RegexPatternUtilsTest {
|
||||||
|
|
||||||
|
private RegexPatternUtils utils;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
utils = RegexPatternUtils.getInstance();
|
||||||
|
utils.clearCache(); // Start with clean cache for each test
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPatternCaching() {
|
||||||
|
String regex = "test\\d+";
|
||||||
|
|
||||||
|
Pattern pattern1 = utils.getPattern(regex);
|
||||||
|
assertNotNull(pattern1);
|
||||||
|
assertTrue(utils.isCached(regex));
|
||||||
|
assertEquals(
|
||||||
|
1, utils.getCacheSize()); // Should have at least 1 pattern (plus precompiled ones
|
||||||
|
// are cleared)
|
||||||
|
|
||||||
|
Pattern pattern2 = utils.getPattern(regex);
|
||||||
|
assertSame(pattern1, pattern2); // Should be the exact same object
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPatternWithFlags() {
|
||||||
|
String regex = "test";
|
||||||
|
int flags = Pattern.CASE_INSENSITIVE;
|
||||||
|
|
||||||
|
Pattern pattern1 = utils.getPattern(regex, flags);
|
||||||
|
Pattern pattern2 = utils.getPattern(regex); // No flags
|
||||||
|
|
||||||
|
assertNotSame(pattern1, pattern2); // Different flags = different cached patterns
|
||||||
|
assertTrue(utils.isCached(regex, flags));
|
||||||
|
assertTrue(utils.isCached(regex, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCacheEviction() {
|
||||||
|
String regex = "evict\\d+";
|
||||||
|
|
||||||
|
utils.getPattern(regex);
|
||||||
|
assertTrue(utils.isCached(regex));
|
||||||
|
|
||||||
|
boolean removed = utils.removeFromCache(regex);
|
||||||
|
assertTrue(removed);
|
||||||
|
assertFalse(utils.isCached(regex));
|
||||||
|
|
||||||
|
boolean removedAgain = utils.removeFromCache(regex);
|
||||||
|
assertFalse(removedAgain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNullRegexHandling() {
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> {
|
||||||
|
utils.getPattern(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> {
|
||||||
|
utils.getPattern(null, Pattern.CASE_INSENSITIVE);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertFalse(utils.isCached(null));
|
||||||
|
assertFalse(utils.removeFromCache(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCommonPatterns() {
|
||||||
|
Pattern whitespace = utils.getWhitespacePattern();
|
||||||
|
assertTrue(whitespace.matcher(" \t ").matches());
|
||||||
|
|
||||||
|
Pattern trailing = utils.getTrailingSlashesPattern();
|
||||||
|
assertTrue(trailing.matcher("/path/to/dir///").find());
|
||||||
|
|
||||||
|
Pattern filename = utils.getSafeFilenamePattern();
|
||||||
|
assertTrue(filename.matcher("bad<file>name").find());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateSearchPattern() {
|
||||||
|
String regex = "Hello";
|
||||||
|
|
||||||
|
Pattern caseSensitive = utils.createSearchPattern(regex, false);
|
||||||
|
Pattern caseInsensitive = utils.createSearchPattern(regex, true);
|
||||||
|
|
||||||
|
assertTrue(caseSensitive.matcher("Hello").matches());
|
||||||
|
assertFalse(caseSensitive.matcher("hello").matches());
|
||||||
|
|
||||||
|
assertTrue(caseInsensitive.matcher("Hello").matches());
|
||||||
|
assertTrue(caseInsensitive.matcher("hello").matches());
|
||||||
|
assertTrue(caseInsensitive.matcher("HELLO").matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSingletonBehavior() {
|
||||||
|
RegexPatternUtils instance1 = RegexPatternUtils.getInstance();
|
||||||
|
RegexPatternUtils instance2 = RegexPatternUtils.getInstance();
|
||||||
|
|
||||||
|
assertSame(instance1, instance2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import java.io.InputStreamReader;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -15,10 +16,13 @@ import io.github.pixee.security.BoundedLineReader;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.UIScaling;
|
import stirling.software.common.util.UIScaling;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LoadingWindow extends JDialog {
|
public class LoadingWindow extends JDialog {
|
||||||
|
private static final Pattern PATTERN =
|
||||||
|
RegexPatternUtils.getInstance().getContainsDigitsPattern();
|
||||||
private final JProgressBar progressBar;
|
private final JProgressBar progressBar;
|
||||||
private final JLabel statusLabel;
|
private final JLabel statusLabel;
|
||||||
private final JPanel mainPanel;
|
private final JPanel mainPanel;
|
||||||
@@ -151,7 +155,10 @@ public class LoadingWindow extends JDialog {
|
|||||||
BoundedLineReader.readLine(
|
BoundedLineReader.readLine(
|
||||||
reader, 5_000_000))
|
reader, 5_000_000))
|
||||||
!= null) {
|
!= null) {
|
||||||
if (line.matches(".*\\d+.*")) { // Contains numbers
|
if (RegexPatternUtils.getInstance()
|
||||||
|
.getContainsDigitsPattern()
|
||||||
|
.matcher(line)
|
||||||
|
.matches()) { // Contains numbers
|
||||||
String[] parts = line.trim().split(",");
|
String[] parts = line.trim().split(",");
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
existingPids.add(
|
existingPids.add(
|
||||||
@@ -206,8 +213,8 @@ public class LoadingWindow extends JDialog {
|
|||||||
newReader,
|
newReader,
|
||||||
5_000_000))
|
5_000_000))
|
||||||
!= null) {
|
!= null) {
|
||||||
if (newLine.matches(
|
if (PATTERN.matcher(newLine)
|
||||||
".*\\d+.*")) {
|
.matches()) {
|
||||||
String[] parts =
|
String[] parts =
|
||||||
newLine.trim()
|
newLine.trim()
|
||||||
.split(",");
|
.split(",");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import jakarta.annotation.PostConstruct;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.configuration.RuntimePathConfig;
|
import stirling.software.common.configuration.RuntimePathConfig;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -73,7 +74,7 @@ public class ExternalAppDepConfig {
|
|||||||
// First replace common terms
|
// First replace common terms
|
||||||
String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
|
String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
|
||||||
// Split into words and capitalize each word
|
// 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))
|
.map(word -> capitalizeWord(word))
|
||||||
.collect(Collectors.joining(" "));
|
.collect(Collectors.joining(" "));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -93,7 +94,7 @@ public class CropController {
|
|||||||
byte[] pdfContent = baos.toByteArray();
|
byte[] pdfContent = baos.toByteArray();
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
pdfContent,
|
pdfContent,
|
||||||
request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_cropped.pdf");
|
request.getFileInput().getOriginalFilename(), "_cropped.pdf"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-8
@@ -13,12 +13,7 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
|
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.*;
|
||||||
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.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
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.SPDF.model.api.EditTableOfContentsRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -182,9 +178,10 @@ public class EditTableOfContentsController {
|
|||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
document.save(baos);
|
document.save(baos);
|
||||||
|
|
||||||
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
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 {
|
} finally {
|
||||||
if (document != null) {
|
if (document != null) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.PdfErrorUtils;
|
import stirling.software.common.util.PdfErrorUtils;
|
||||||
import stirling.software.common.util.TempFile;
|
import stirling.software.common.util.TempFile;
|
||||||
import stirling.software.common.util.TempFileManager;
|
import stirling.software.common.util.TempFileManager;
|
||||||
@@ -127,10 +128,7 @@ public class MergeController {
|
|||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
// Get the filename without extension to use as bookmark title
|
// Get the filename without extension to use as bookmark title
|
||||||
String filename = file.getOriginalFilename();
|
String filename = file.getOriginalFilename();
|
||||||
String title = filename;
|
String title = GeneralUtils.removeExtension(filename);
|
||||||
if (title != null && title.contains(".")) {
|
|
||||||
title = title.substring(0, title.lastIndexOf('.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an outline item for this file
|
// Create an outline item for this file
|
||||||
PDOutlineItem item = new PDOutlineItem();
|
PDOutlineItem item = new PDOutlineItem();
|
||||||
@@ -236,10 +234,11 @@ public class MergeController {
|
|||||||
mergedDocument.save(outputTempFile.getFile());
|
mergedDocument.save(outputTempFile.getFile());
|
||||||
|
|
||||||
String mergedFileName =
|
String mergedFileName =
|
||||||
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_merged_unsigned.pdf";
|
files[0].getOriginalFilename(), "_merged_unsigned.pdf");
|
||||||
return WebResponseUtils.pdfFileToWebResponse(
|
return WebResponseUtils.pdfFileToWebResponse(
|
||||||
outputTempFile, mergedFileName); // Return the modified PDF as stream
|
outputTempFile, mergedFileName); // Return the modified PDF
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
if (ex instanceof IOException && PdfErrorUtils.isCorruptedPdfError((IOException) ex)) {
|
if (ex instanceof IOException && PdfErrorUtils.isCorruptedPdfError((IOException) ex)) {
|
||||||
log.warn("Corrupted PDF detected in merge pdf process: {}", ex.getMessage());
|
log.warn("Corrupted PDF detected in merge pdf process: {}", ex.getMessage());
|
||||||
|
|||||||
+3
-3
@@ -19,7 +19,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.general.MergeMultiplePagesRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.FormUtils;
|
import stirling.software.common.util.FormUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -166,7 +166,7 @@ public class MultiPageLayoutController {
|
|||||||
byte[] result = baos.toByteArray();
|
byte[] result = baos.toByteArray();
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
result,
|
result,
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_layoutChanged.pdf");
|
file.getOriginalFilename(), "_multi_page_layout.pdf"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import stirling.software.SPDF.service.PdfImageRemovalService;
|
import stirling.software.SPDF.service.PdfImageRemovalService;
|
||||||
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;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,8 +70,8 @@ public class PdfImageRemovalController {
|
|||||||
|
|
||||||
// Generate a new filename for the modified PDF
|
// Generate a new filename for the modified PDF
|
||||||
String mergedFileName =
|
String mergedFileName =
|
||||||
file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_removed_images.pdf";
|
file.getFileInput().getOriginalFilename(), "_images_removed.pdf");
|
||||||
|
|
||||||
// Convert the byte array to a web response and return it
|
// Convert the byte array to a web response and return it
|
||||||
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);
|
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);
|
||||||
|
|||||||
+2
-4
@@ -20,7 +20,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@@ -84,9 +83,8 @@ public class PdfOverlayController {
|
|||||||
overlay.overlay(overlayGuide).save(outputStream);
|
overlay.overlay(overlayGuide).save(outputStream);
|
||||||
byte[] data = outputStream.toByteArray();
|
byte[] data = outputStream.toByteArray();
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(baseFile.getOriginalFilename())
|
GeneralUtils.generateFilename(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
baseFile.getOriginalFilename(), "_overlayed.pdf");
|
||||||
+ "_overlayed.pdf"; // Remove file extension and append .pdf
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
data, outputFilename, MediaType.APPLICATION_PDF);
|
data, outputFilename, MediaType.APPLICATION_PDF);
|
||||||
|
|||||||
+3
-7
@@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@@ -68,9 +67,7 @@ public class RearrangePagesPDFController {
|
|||||||
}
|
}
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_removed_pages.pdf"));
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_removed_pages.pdf");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Integer> removeFirst(int totalPages) {
|
private List<Integer> removeFirst(int totalPages) {
|
||||||
@@ -286,9 +283,8 @@ public class RearrangePagesPDFController {
|
|||||||
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
GeneralUtils.generateFilename(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
pdfFile.getOriginalFilename(), "_rearranged.pdf"));
|
||||||
+ "_rearranged.pdf");
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ExceptionUtils.logException("document rearrangement", e);
|
ExceptionUtils.logException("document rearrangement", e);
|
||||||
throw e;
|
throw e;
|
||||||
|
|||||||
+3
-4
@@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.general.RotatePDFRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -59,10 +59,9 @@ public class RotationController {
|
|||||||
page.setRotation(page.getRotation() + angle);
|
page.setRotation(page.getRotation() + angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the rotated PDF as a response
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_rotated.pdf"));
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_rotated.pdf");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -20,7 +20,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.general.ScalePagesRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -99,8 +99,7 @@ public class ScalePagesController {
|
|||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
baos.toByteArray(),
|
baos.toByteArray(),
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(file.getOriginalFilename(), "_scaled.pdf"));
|
||||||
+ "_scaled.pdf");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
||||||
|
|||||||
+18
-21
@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
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.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.TempFile;
|
import stirling.software.common.util.TempFile;
|
||||||
import stirling.software.common.util.TempFileManager;
|
import stirling.software.common.util.TempFileManager;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
@@ -56,20 +55,15 @@ public class SplitPDFController {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
PDDocument document = null;
|
PDDocument document = null;
|
||||||
Path zipFile = null;
|
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
String filename;
|
|
||||||
TempFile outputTempFile = null;
|
TempFile outputTempFile = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
outputTempFile = new TempFile(tempFileManager, ".zip");
|
outputTempFile = new TempFile(tempFileManager, ".zip");
|
||||||
|
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
String pages = request.getPageNumbers();
|
|
||||||
// open the pdf document
|
|
||||||
|
|
||||||
document = pdfDocumentFactory.load(file);
|
document = pdfDocumentFactory.load(file);
|
||||||
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
|
||||||
int totalPages = document.getNumberOfPages();
|
int totalPages = document.getNumberOfPages();
|
||||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||||
if (!pageNumbers.contains(totalPages - 1)) {
|
if (!pageNumbers.contains(totalPages - 1)) {
|
||||||
@@ -82,8 +76,7 @@ public class SplitPDFController {
|
|||||||
"Splitting PDF into pages: {}",
|
"Splitting PDF into pages: {}",
|
||||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
|
|
||||||
// split the document
|
splitDocumentsBoas = new ArrayList<>(pageNumbers.size());
|
||||||
splitDocumentsBoas = new ArrayList<>();
|
|
||||||
int previousPageNumber = 0;
|
int previousPageNumber = 0;
|
||||||
for (int splitPoint : pageNumbers) {
|
for (int splitPoint : pageNumbers) {
|
||||||
try (PDDocument splitDocument =
|
try (PDDocument splitDocument =
|
||||||
@@ -100,7 +93,6 @@ public class SplitPDFController {
|
|||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
splitDocument.save(baos);
|
splitDocument.save(baos);
|
||||||
|
|
||||||
splitDocumentsBoas.add(baos);
|
splitDocumentsBoas.add(baos);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ExceptionUtils.logException("document splitting and saving", e);
|
ExceptionUtils.logException("document splitting and saving", e);
|
||||||
@@ -108,21 +100,21 @@ public class SplitPDFController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// closing the original document
|
|
||||||
document.close();
|
document.close();
|
||||||
|
|
||||||
filename =
|
String baseFilename = GeneralUtils.removeExtension(file.getOriginalFilename());
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
try (ZipOutputStream zipOut =
|
try (ZipOutputStream zipOut =
|
||||||
new ZipOutputStream(Files.newOutputStream(outputTempFile.getPath()))) {
|
new ZipOutputStream(Files.newOutputStream(outputTempFile.getPath()))) {
|
||||||
// loop through the split documents and write them to the zip file
|
int splitDocumentsSize = splitDocumentsBoas.size();
|
||||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
for (int i = 0; i < splitDocumentsSize; i++) {
|
||||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
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);
|
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||||
byte[] pdf = baos.toByteArray();
|
byte[] pdf = baos.toByteArray();
|
||||||
|
|
||||||
// Add PDF file to the zip
|
|
||||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||||
zipOut.putNextEntry(pdfEntry);
|
zipOut.putNextEntry(pdfEntry);
|
||||||
zipOut.write(pdf);
|
zipOut.write(pdf);
|
||||||
@@ -131,12 +123,17 @@ public class SplitPDFController {
|
|||||||
log.debug("Wrote split document {} to zip file", fileName);
|
log.debug("Wrote split document {} to zip file", fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"Successfully created zip file with split documents: {}",
|
"Successfully created zip file with split documents: {}",
|
||||||
outputTempFile.getPath());
|
outputTempFile.getPath().toString());
|
||||||
byte[] data = Files.readAllBytes(outputTempFile.getPath());
|
byte[] data = Files.readAllBytes(outputTempFile.getPath());
|
||||||
|
|
||||||
|
String zipFilename =
|
||||||
|
GeneralUtils.generateFilename(file.getOriginalFilename(), "_split.zip");
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
data, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
// Close the main document
|
// Close the main document
|
||||||
|
|||||||
+2
-4
@@ -20,7 +20,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.service.PdfMetadataService;
|
import stirling.software.common.service.PdfMetadataService;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -188,9 +188,7 @@ public class SplitPdfByChaptersController {
|
|||||||
byte[] data = Files.readAllBytes(zipFile);
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
Files.deleteIfExists(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
|
|
||||||
String filename =
|
String filename = GeneralUtils.generateFilename(file.getOriginalFilename(), "");
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
sourceDocument.close();
|
sourceDocument.close();
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|||||||
+2
-4
@@ -26,7 +26,6 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.SplitPdfBySectionsRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.PDFService;
|
import stirling.software.common.util.PDFService;
|
||||||
import stirling.software.common.util.TempFile;
|
import stirling.software.common.util.TempFile;
|
||||||
import stirling.software.common.util.TempFileManager;
|
import stirling.software.common.util.TempFileManager;
|
||||||
@@ -69,9 +69,7 @@ public class SplitPdfBySectionsController {
|
|||||||
boolean merge = Boolean.TRUE.equals(request.getMerge());
|
boolean merge = Boolean.TRUE.equals(request.getMerge());
|
||||||
List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz);
|
List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz);
|
||||||
|
|
||||||
String filename =
|
String filename = GeneralUtils.generateFilename(file.getOriginalFilename(), "_split.pdf");
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
if (merge) {
|
if (merge) {
|
||||||
TempFile tempFile = new TempFile(tempFileManager, ".pdf");
|
TempFile tempFile = new TempFile(tempFileManager, ".pdf");
|
||||||
try (PDDocument merged = pdfService.mergeDocuments(splitDocuments);
|
try (PDDocument merged = pdfService.mergeDocuments(splitDocuments);
|
||||||
|
|||||||
+6
-8
@@ -17,7 +17,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@@ -57,21 +56,20 @@ public class SplitPdfBySizeController {
|
|||||||
log.debug("Starting PDF split process with request: {}", request);
|
log.debug("Starting PDF split process with request: {}", request);
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
|
|
||||||
String filename =
|
String filename = GeneralUtils.generateFilename(file.getOriginalFilename(), "");
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
log.debug("Base filename for output: {}", filename);
|
log.debug("Base filename for output: {}", filename);
|
||||||
|
|
||||||
try (TempFile zipTempFile = new TempFile(tempFileManager, ".zip")) {
|
try (TempFile zipTempFile = new TempFile(tempFileManager, ".zip")) {
|
||||||
Path zipFile = zipTempFile.getPath();
|
Path managedZipPath = zipTempFile.getPath();
|
||||||
log.debug("Created temporary zip file: {}", zipFile);
|
log.debug("Created temporary managed zip file: {}", managedZipPath);
|
||||||
try {
|
try {
|
||||||
log.debug("Reading input file bytes");
|
log.debug("Reading input file bytes");
|
||||||
byte[] pdfBytes = file.getBytes();
|
byte[] pdfBytes = file.getBytes();
|
||||||
log.debug("Successfully read {} bytes from input file", pdfBytes.length);
|
log.debug("Successfully read {} bytes from input file", pdfBytes.length);
|
||||||
|
|
||||||
log.debug("Creating ZIP output stream");
|
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");
|
log.debug("Loading PDF document");
|
||||||
try (PDDocument sourceDocument = pdfDocumentFactory.load(pdfBytes)) {
|
try (PDDocument sourceDocument = pdfDocumentFactory.load(pdfBytes)) {
|
||||||
log.debug(
|
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("Successfully read {} bytes from ZIP file", data.length);
|
||||||
|
|
||||||
log.debug("Returning response with {} bytes of data", data.length);
|
log.debug("Returning response with {} bytes of data", data.length);
|
||||||
|
|||||||
+3
-2
@@ -24,6 +24,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
|
|
||||||
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;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -92,7 +93,7 @@ public class ToSinglePageController {
|
|||||||
byte[] result = baos.toByteArray();
|
byte[] result = baos.toByteArray();
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
result,
|
result,
|
||||||
request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_singlePage.pdf");
|
request.getFileInput().getOriginalFilename(), "_singlePage.pdf"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-8
@@ -17,11 +17,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import stirling.software.common.configuration.RuntimePathConfig;
|
import stirling.software.common.configuration.RuntimePathConfig;
|
||||||
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
|
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.CustomHtmlSanitizer;
|
import stirling.software.common.util.*;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
|
||||||
import stirling.software.common.util.FileToPdf;
|
|
||||||
import stirling.software.common.util.TempFileManager;
|
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
@@ -69,9 +65,7 @@ public class ConvertHtmlToPDF {
|
|||||||
|
|
||||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename = GeneralUtils.generateFilename(originalFilename, ".pdf");
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ ".pdf"; // Remove file extension and append .pdf
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-13
@@ -1,7 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLConnection;
|
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.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.ConvertToImageRequest;
|
||||||
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.CheckProgramInstall;
|
import stirling.software.common.util.*;
|
||||||
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.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
@@ -89,9 +82,7 @@ public class ConvertImgPDFController {
|
|||||||
}
|
}
|
||||||
// returns bytes for image
|
// returns bytes for image
|
||||||
boolean singleImage = "single".equals(singleOrMultiple);
|
boolean singleImage = "single".equals(singleOrMultiple);
|
||||||
String filename =
|
String filename = GeneralUtils.generateFilename(file.getOriginalFilename(), "");
|
||||||
Filenames.toSimpleFileName(new File(file.getOriginalFilename()).getName())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
|
|
||||||
result =
|
result =
|
||||||
PdfUtils.convertFromPdf(
|
PdfUtils.convertFromPdf(
|
||||||
@@ -240,8 +231,7 @@ public class ConvertImgPDFController {
|
|||||||
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
|
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
bytes,
|
bytes,
|
||||||
new File(file[0].getOriginalFilename()).getName().replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(file[0].getOriginalFilename(), "_converted.pdf"));
|
||||||
+ "_converted.pdf");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMediaType(String imageFormat) {
|
private String getMediaType(String imageFormat) {
|
||||||
|
|||||||
+2
-8
@@ -27,11 +27,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import stirling.software.common.configuration.RuntimePathConfig;
|
import stirling.software.common.configuration.RuntimePathConfig;
|
||||||
import stirling.software.common.model.api.GeneralFile;
|
import stirling.software.common.model.api.GeneralFile;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.CustomHtmlSanitizer;
|
import stirling.software.common.util.*;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
|
||||||
import stirling.software.common.util.FileToPdf;
|
|
||||||
import stirling.software.common.util.TempFileManager;
|
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
@@ -89,9 +85,7 @@ public class ConvertMarkdownToPdf {
|
|||||||
tempFileManager,
|
tempFileManager,
|
||||||
customHtmlSanitizer);
|
customHtmlSanitizer);
|
||||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||||
String outputFilename =
|
String outputFilename = GeneralUtils.generateFilename(originalFilename, ".pdf");
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ ".pdf"; // Remove file extension and append .pdf
|
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-5
@@ -32,8 +32,10 @@ import stirling.software.common.configuration.RuntimePathConfig;
|
|||||||
import stirling.software.common.model.api.GeneralFile;
|
import stirling.software.common.model.api.GeneralFile;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.CustomHtmlSanitizer;
|
import stirling.software.common.util.CustomHtmlSanitizer;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.ProcessExecutor;
|
import stirling.software.common.util.ProcessExecutor;
|
||||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -168,8 +170,10 @@ public class ConvertOfficeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidFileExtension(String fileExtension) {
|
private boolean isValidFileExtension(String fileExtension) {
|
||||||
String extensionPattern = "^(?i)[a-z0-9]{2,4}$";
|
return RegexPatternUtils.getInstance()
|
||||||
return fileExtension.matches(extensionPattern);
|
.getFileExtensionValidationPattern()
|
||||||
|
.matcher(fileExtension)
|
||||||
|
.matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/file/pdf")
|
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/file/pdf")
|
||||||
@@ -190,9 +194,8 @@ public class ConvertOfficeController {
|
|||||||
PDDocument doc = pdfDocumentFactory.load(file);
|
PDDocument doc = pdfDocumentFactory.load(file);
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
doc,
|
doc,
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
GeneralUtils.generateFilename(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
inputFile.getOriginalFilename(), "_convertedToPDF.pdf"));
|
||||||
+ "_convertedToPDF.pdf");
|
|
||||||
} finally {
|
} finally {
|
||||||
if (file != null && file.getParent() != null) {
|
if (file != null && file.getParent() != null) {
|
||||||
FileUtils.deleteDirectory(file.getParentFile());
|
FileUtils.deleteDirectory(file.getParentFile());
|
||||||
|
|||||||
+2
-4
@@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.converters.PdfToWordRequest;
|
||||||
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;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.PDFToFile;
|
import stirling.software.common.util.PDFToFile;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@@ -66,9 +66,7 @@ public class ConvertPDFToOffice {
|
|||||||
String text = stripper.getText(document);
|
String text = stripper.getText(document);
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
text.getBytes(),
|
text.getBytes(),
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
GeneralUtils.generateFilename(inputFile.getOriginalFilename(), ".txt"),
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ ".txt",
|
|
||||||
MediaType.TEXT_PLAIN);
|
MediaType.TEXT_PLAIN);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+47
-12
@@ -30,7 +30,7 @@ import stirling.software.common.model.ApplicationProperties;
|
|||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.GeneralUtils;
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.ProcessExecutor;
|
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;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -64,25 +64,26 @@ public class ConvertWebsiteToPDF {
|
|||||||
.queryParam("error", "error.endpointDisabled")
|
.queryParam("error", "error.endpointDisabled")
|
||||||
.build()
|
.build()
|
||||||
.toUri();
|
.toUri();
|
||||||
} else
|
} else {
|
||||||
|
// Validate the URL format (relaxed: only invalid if BOTH checks fail)
|
||||||
// Validate the URL format
|
boolean patternValid =
|
||||||
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
RegexPatternUtils.getInstance().getHttpUrlPattern().matcher(URL).matches();
|
||||||
|
boolean generalValid = GeneralUtils.isValidURL(URL);
|
||||||
|
if (!patternValid && !generalValid) {
|
||||||
location =
|
location =
|
||||||
uriComponentsBuilder
|
uriComponentsBuilder
|
||||||
.queryParam("error", "error.invalidUrlFormat")
|
.queryParam("error", "error.invalidUrlFormat")
|
||||||
.build()
|
.build()
|
||||||
.toUri();
|
.toUri();
|
||||||
} else
|
} else if (!GeneralUtils.isURLReachable(URL)) {
|
||||||
|
|
||||||
// validate the URL is reachable
|
// validate the URL is reachable
|
||||||
if (!GeneralUtils.isURLReachable(URL)) {
|
|
||||||
location =
|
location =
|
||||||
uriComponentsBuilder
|
uriComponentsBuilder
|
||||||
.queryParam("error", "error.urlNotReachable")
|
.queryParam("error", "error.urlNotReachable")
|
||||||
.build()
|
.build()
|
||||||
.toUri();
|
.toUri();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
log.info("Redirecting to: {}", location.toString());
|
log.info("Redirecting to: {}", location.toString());
|
||||||
@@ -102,7 +103,6 @@ public class ConvertWebsiteToPDF {
|
|||||||
command.add("--pdf-forms");
|
command.add("--pdf-forms");
|
||||||
command.add(tempOutputFile.toString());
|
command.add(tempOutputFile.toString());
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
@@ -112,7 +112,13 @@ public class ConvertWebsiteToPDF {
|
|||||||
// Convert URL to a safe filename
|
// Convert URL to a safe filename
|
||||||
String outputFilename = convertURLToFileName(URL);
|
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 {
|
} finally {
|
||||||
|
|
||||||
if (tempOutputFile != null) {
|
if (tempOutputFile != null) {
|
||||||
@@ -126,10 +132,39 @@ public class ConvertWebsiteToPDF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String convertURLToFileName(String url) {
|
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) {
|
if (safeName.length() > 50) {
|
||||||
safeName = safeName.substring(0, 50); // restrict to 50 characters
|
safeName = safeName.substring(0, 50); // restrict to 50 characters
|
||||||
}
|
}
|
||||||
return safeName + ".pdf";
|
return GeneralUtils.generateFilename(safeName, ".pdf");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -31,6 +31,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
|
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
|
|
||||||
import technology.tabula.ObjectExtractor;
|
import technology.tabula.ObjectExtractor;
|
||||||
import technology.tabula.Page;
|
import technology.tabula.Page;
|
||||||
@@ -127,7 +128,7 @@ public class ExtractCSVController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getBaseName(String filename) {
|
private String getBaseName(String filename) {
|
||||||
return filename.replaceFirst("[.][^.]+$", "");
|
return GeneralUtils.removeExtension(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CsvEntry(String filename, String content) {}
|
private record CsvEntry(String filename, String content) {}
|
||||||
|
|||||||
+4
-3
@@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import stirling.software.SPDF.model.api.misc.AddAttachmentRequest;
|
import stirling.software.SPDF.model.api.misc.AddAttachmentRequest;
|
||||||
import stirling.software.SPDF.service.AttachmentServiceInterface;
|
import stirling.software.SPDF.service.AttachmentServiceInterface;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -51,8 +52,8 @@ public class AttachmentController {
|
|||||||
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
GeneralUtils.generateFilename(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
Filenames.toSimpleFileName(fileInput.getOriginalFilename()),
|
||||||
+ "_with_attachments.pdf");
|
"_with_attachments.pdf"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
|
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -135,7 +136,12 @@ public class AutoRenameController {
|
|||||||
|
|
||||||
// Sanitize the header string by removing characters not allowed in a filename.
|
// Sanitize the header string by removing characters not allowed in a filename.
|
||||||
if (header != null && header.length() < 255) {
|
if (header != null && header.length() < 255) {
|
||||||
header = header.replaceAll("[/\\\\?%*:|\"<>]", "").trim();
|
header =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getSafeFilenamePattern()
|
||||||
|
.matcher(header)
|
||||||
|
.replaceAll("")
|
||||||
|
.trim();
|
||||||
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
|
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
|
||||||
} else {
|
} else {
|
||||||
log.info("File has no good title to be found");
|
log.info("File has no good title to be found");
|
||||||
|
|||||||
+3
-2
@@ -38,6 +38,7 @@ import stirling.software.common.model.ApplicationProperties;
|
|||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ApplicationContextProvider;
|
import stirling.software.common.util.ApplicationContextProvider;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.TempFile;
|
import stirling.software.common.util.TempFile;
|
||||||
import stirling.software.common.util.TempFileManager;
|
import stirling.software.common.util.TempFileManager;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
@@ -174,8 +175,8 @@ public class AutoSplitPdfController {
|
|||||||
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
|
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
|
||||||
|
|
||||||
String filename =
|
String filename =
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
GeneralUtils.removeExtension(
|
||||||
.replaceFirst("[.][^.]+$", "");
|
Filenames.toSimpleFileName(file.getOriginalFilename()));
|
||||||
|
|
||||||
try (ZipOutputStream zipOut =
|
try (ZipOutputStream zipOut =
|
||||||
new ZipOutputStream(Files.newOutputStream(outputTempFile.getPath()))) {
|
new ZipOutputStream(Files.newOutputStream(outputTempFile.getPath()))) {
|
||||||
|
|||||||
+3
-2
@@ -34,6 +34,7 @@ import stirling.software.common.model.ApplicationProperties;
|
|||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ApplicationContextProvider;
|
import stirling.software.common.util.ApplicationContextProvider;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.PdfUtils;
|
import stirling.software.common.util.PdfUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@@ -149,8 +150,8 @@ public class BlankPageController {
|
|||||||
ZipOutputStream zos = new ZipOutputStream(baos);
|
ZipOutputStream zos = new ZipOutputStream(baos);
|
||||||
|
|
||||||
String filename =
|
String filename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
GeneralUtils.removeExtension(
|
||||||
.replaceFirst("[.][^.]+$", "");
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
|
||||||
|
|
||||||
if (!nonBlankPages.isEmpty()) {
|
if (!nonBlankPages.isEmpty()) {
|
||||||
createZipEntry(zos, nonBlankPages, filename + "_nonBlankPages.pdf");
|
createZipEntry(zos, nonBlankPages, filename + "_nonBlankPages.pdf");
|
||||||
|
|||||||
+4
-14
@@ -10,12 +10,8 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import javax.imageio.IIOImage;
|
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.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.*;
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
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;
|
||||||
@@ -806,9 +797,8 @@ public class CompressController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
GeneralUtils.generateFilename(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
inputFile.getOriginalFilename(), "_Optimized.pdf");
|
||||||
+ "_Optimized.pdf";
|
|
||||||
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
pdfDocumentFactory.load(currentFile.toFile()), outputFilename);
|
pdfDocumentFactory.load(currentFile.toFile()), outputFilename);
|
||||||
|
|||||||
+4
-3
@@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
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;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -55,10 +56,10 @@ public class DecompressPdfController {
|
|||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
document.save(baos, CompressParameters.NO_COMPRESSION);
|
document.save(baos, CompressParameters.NO_COMPRESSION);
|
||||||
|
|
||||||
String outputFilename =
|
// Return the PDF as a response
|
||||||
file.getOriginalFilename().replaceFirst("\\.(?=[^.]+$)", "_decompressed.");
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
baos.toByteArray(), outputFilename, MediaType.APPLICATION_PDF);
|
baos.toByteArray(),
|
||||||
|
GeneralUtils.generateFilename(file.getOriginalFilename(), "_decompressed.pdf"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -176,7 +176,7 @@ public class ExtractImageScansController {
|
|||||||
// Create zip file if multiple images
|
// Create zip file if multiple images
|
||||||
if (processedImageBytes.size() > 1) {
|
if (processedImageBytes.size() > 1) {
|
||||||
String outputZipFilename =
|
String outputZipFilename =
|
||||||
fileName.replaceFirst(REPLACEFIRST, "") + "_processed.zip";
|
GeneralUtils.generateFilename(fileName, "_processed.zip");
|
||||||
tempZipFile = Files.createTempFile("output_", ".zip");
|
tempZipFile = Files.createTempFile("output_", ".zip");
|
||||||
|
|
||||||
try (ZipOutputStream zipOut =
|
try (ZipOutputStream zipOut =
|
||||||
@@ -185,10 +185,8 @@ public class ExtractImageScansController {
|
|||||||
for (int i = 0; i < processedImageBytes.size(); i++) {
|
for (int i = 0; i < processedImageBytes.size(); i++) {
|
||||||
ZipEntry entry =
|
ZipEntry entry =
|
||||||
new ZipEntry(
|
new ZipEntry(
|
||||||
fileName.replaceFirst(REPLACEFIRST, "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_"
|
fileName, "_processed_" + (i + 1) + ".png"));
|
||||||
+ (i + 1)
|
|
||||||
+ ".png");
|
|
||||||
zipOut.putNextEntry(entry);
|
zipOut.putNextEntry(entry);
|
||||||
zipOut.write(processedImageBytes.get(i));
|
zipOut.write(processedImageBytes.get(i));
|
||||||
zipOut.closeEntry();
|
zipOut.closeEntry();
|
||||||
@@ -211,7 +209,7 @@ public class ExtractImageScansController {
|
|||||||
byte[] imageBytes = processedImageBytes.get(0);
|
byte[] imageBytes = processedImageBytes.get(0);
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
imageBytes,
|
imageBytes,
|
||||||
fileName.replaceFirst(REPLACEFIRST, "") + ".png",
|
GeneralUtils.generateFilename(fileName, ".png"),
|
||||||
MediaType.IMAGE_PNG);
|
MediaType.IMAGE_PNG);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
+2
-4
@@ -32,7 +32,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.PDFExtractImagesRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.ImageProcessingUtils;
|
import stirling.software.common.util.ImageProcessingUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@@ -80,9 +80,7 @@ public class ExtractImagesController {
|
|||||||
// Set compression level
|
// Set compression level
|
||||||
zos.setLevel(Deflater.BEST_COMPRESSION);
|
zos.setLevel(Deflater.BEST_COMPRESSION);
|
||||||
|
|
||||||
String filename =
|
String filename = GeneralUtils.removeExtension(file.getOriginalFilename());
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
Set<byte[]> processedImages = new HashSet<>();
|
Set<byte[]> processedImages = new HashSet<>();
|
||||||
|
|
||||||
if (useMultithreading) {
|
if (useMultithreading) {
|
||||||
|
|||||||
+10
-3
@@ -23,6 +23,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.MetadataRequest;
|
import stirling.software.SPDF.model.api.misc.MetadataRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
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.service.PdfMetadataService;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor;
|
import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor;
|
||||||
@@ -136,7 +138,12 @@ public class MetadataController {
|
|||||||
&& !key.contains("customValue")) {
|
&& !key.contains("customValue")) {
|
||||||
info.setCustomMetadataValue(key, entry.getValue());
|
info.setCustomMetadataValue(key, entry.getValue());
|
||||||
} else if (key.contains("customKey")) {
|
} 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 customKey = entry.getValue();
|
||||||
String customValue = allRequestParams.get("customValue" + number);
|
String customValue = allRequestParams.get("customValue" + number);
|
||||||
info.setCustomMetadataValue(customKey, customValue);
|
info.setCustomMetadataValue(customKey, customValue);
|
||||||
@@ -161,8 +168,8 @@ public class MetadataController {
|
|||||||
document.setDocumentInformation(info);
|
document.setDocumentInformation(info);
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
GeneralUtils.removeExtension(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()))
|
||||||
+ "_metadata.pdf");
|
+ "_metadata.pdf");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -1,10 +1,14 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
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.Files;
|
||||||
import java.nio.file.Path;
|
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.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
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.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.*;
|
||||||
import stirling.software.common.util.ProcessExecutor;
|
|
||||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
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
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
@@ -161,15 +160,16 @@ public class OCRController {
|
|||||||
|
|
||||||
// Return the OCR processed PDF as a response
|
// Return the OCR processed PDF as a response
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
GeneralUtils.removeExtension(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
|
||||||
+ "_OCR.pdf";
|
+ "_OCR.pdf";
|
||||||
|
|
||||||
if (sidecar != null && sidecar && sidecarTextFile != null) {
|
if (sidecar != null && sidecar && sidecarTextFile != null) {
|
||||||
// Create a zip file containing both the PDF and the text file
|
// Create a zip file containing both the PDF and the text file
|
||||||
String outputZipFilename =
|
String outputZipFilename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
GeneralUtils.removeExtension(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
Filenames.toSimpleFileName(
|
||||||
|
inputFile.getOriginalFilename()))
|
||||||
+ "_OCR.zip";
|
+ "_OCR.zip";
|
||||||
|
|
||||||
try (TempFile tempZipFile = new TempFile(tempFileManager, ".zip");
|
try (TempFile tempZipFile = new TempFile(tempFileManager, ".zip");
|
||||||
|
|||||||
+2
-4
@@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.misc.OverlayImageRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.PdfUtils;
|
import stirling.software.common.util.PdfUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@@ -54,9 +54,7 @@ public class OverlayImageController {
|
|||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
result,
|
result,
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_overlayed.pdf"));
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_overlayed.pdf");
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Failed to add image to PDF", e);
|
log.error("Failed to add image to PDF", e);
|
||||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
|||||||
+7
-4
@@ -103,7 +103,11 @@ public class PageNumbersController {
|
|||||||
customText
|
customText
|
||||||
.replace("{n}", String.valueOf(pageNumber))
|
.replace("{n}", String.valueOf(pageNumber))
|
||||||
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
||||||
.replace("{filename}", baseFilename);
|
.replace(
|
||||||
|
"{filename}",
|
||||||
|
GeneralUtils.removeExtension(
|
||||||
|
Filenames.toSimpleFileName(
|
||||||
|
file.getOriginalFilename())));
|
||||||
|
|
||||||
PDType1Font currentFont =
|
PDType1Font currentFont =
|
||||||
switch (fontType == null ? "" : fontType.toLowerCase(Locale.ROOT)) {
|
switch (fontType == null ? "" : fontType.toLowerCase(Locale.ROOT)) {
|
||||||
@@ -169,8 +173,7 @@ public class PageNumbersController {
|
|||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
baos.toByteArray(),
|
baos.toByteArray(),
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_numbersAdded.pdf",
|
file.getOriginalFilename(), "_page_numbers_added.pdf"));
|
||||||
MediaType.APPLICATION_PDF);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-6
@@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.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;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.ProcessExecutor;
|
import stirling.software.common.util.ProcessExecutor;
|
||||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.common.util.TempFile;
|
import stirling.software.common.util.TempFile;
|
||||||
@@ -124,11 +124,10 @@ public class RepairController {
|
|||||||
byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.getFile());
|
byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.getFile());
|
||||||
|
|
||||||
// Return the repaired PDF as a response
|
// Return the repaired PDF as a response
|
||||||
String outputFilename =
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
pdfBytes,
|
||||||
.replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_repaired.pdf";
|
inputFile.getOriginalFilename(), "_repaired.pdf"));
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-7
@@ -24,7 +24,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ApplicationContextProvider;
|
import stirling.software.common.util.ApplicationContextProvider;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -339,13 +339,10 @@ public class ScannerEffectController {
|
|||||||
outputDocument.save(outputStream);
|
outputDocument.save(outputStream);
|
||||||
outputDocument.close();
|
outputDocument.close();
|
||||||
|
|
||||||
String outputFilename =
|
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_scanner_effect.pdf";
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
outputStream.toByteArray(), outputFilename, MediaType.APPLICATION_PDF);
|
outputStream.toByteArray(),
|
||||||
|
GeneralUtils.generateFilename(
|
||||||
|
file.getOriginalFilename(), "_scanner_effect.pdf"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-9
@@ -1,9 +1,8 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
import java.nio.charset.StandardCharsets;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import java.util.Map;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
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.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.model.api.PDFFile;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
@@ -55,7 +58,9 @@ public class ShowJavascript {
|
|||||||
|
|
||||||
if (jsCodeStr != null && !jsCodeStr.trim().isEmpty()) {
|
if (jsCodeStr != null && !jsCodeStr.trim().isEmpty()) {
|
||||||
script.append("// File: ")
|
script.append("// File: ")
|
||||||
.append(Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
|
.append(
|
||||||
|
Filenames.toSimpleFileName(
|
||||||
|
inputFile.getOriginalFilename()))
|
||||||
.append(", Script: ")
|
.append(", Script: ")
|
||||||
.append(name)
|
.append(name)
|
||||||
.append("\n")
|
.append("\n")
|
||||||
@@ -68,7 +73,8 @@ public class ShowJavascript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!foundScript) {
|
if (!foundScript) {
|
||||||
script = new StringBuilder("PDF '")
|
script =
|
||||||
|
new StringBuilder("PDF '")
|
||||||
.append(Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
|
.append(Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
|
||||||
.append("' does not contain Javascript");
|
.append("' does not contain Javascript");
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -35,7 +35,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.misc.AddStampRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
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.TempFile;
|
||||||
import stirling.software.common.util.TempFileManager;
|
import stirling.software.common.util.TempFileManager;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
@@ -172,11 +173,10 @@ public class StampController {
|
|||||||
contentStream.close();
|
contentStream.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Return the stamped PDF as a response
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_stamped.pdf"));
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_stamped.pdf");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTextStamp(
|
private void addTextStamp(
|
||||||
@@ -250,7 +250,8 @@ public class StampController {
|
|||||||
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
|
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
|
||||||
}
|
}
|
||||||
// Split the stampText into multiple lines
|
// 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
|
// Calculate dynamic line height based on font ascent and descent
|
||||||
float ascent = font.getFontDescriptor().getAscent();
|
float ascent = font.getFontDescriptor().getAscent();
|
||||||
|
|||||||
+10
-6
@@ -25,6 +25,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
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;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -66,13 +68,15 @@ public class UnlockPDFFormsController {
|
|||||||
COSBase xfaBase = acroForm.getCOSObject().getDictionaryObject(COSName.XFA);
|
COSBase xfaBase = acroForm.getCOSObject().getDictionaryObject(COSName.XFA);
|
||||||
if (xfaBase != null) {
|
if (xfaBase != null) {
|
||||||
try {
|
try {
|
||||||
|
var accessReadOnlyPattern =
|
||||||
|
RegexPatternUtils.getInstance().getAccessReadOnlyPattern();
|
||||||
if (xfaBase instanceof COSStream xfaStream) {
|
if (xfaBase instanceof COSStream xfaStream) {
|
||||||
InputStream is = xfaStream.createInputStream();
|
InputStream is = xfaStream.createInputStream();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
is.transferTo(baos);
|
is.transferTo(baos);
|
||||||
String xml = baos.toString(StandardCharsets.UTF_8);
|
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 =
|
PDStream newStream =
|
||||||
new PDStream(
|
new PDStream(
|
||||||
@@ -92,9 +96,9 @@ public class UnlockPDFFormsController {
|
|||||||
String xml = baos.toString(StandardCharsets.UTF_8);
|
String xml = baos.toString(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
xml =
|
xml =
|
||||||
xml.replaceAll(
|
accessReadOnlyPattern
|
||||||
"access\\s*=\\s*\"readOnly\"",
|
.matcher(xml)
|
||||||
"access=\"open\"");
|
.replaceAll("access=\"open\"");
|
||||||
|
|
||||||
PDStream newStream =
|
PDStream newStream =
|
||||||
new PDStream(
|
new PDStream(
|
||||||
@@ -111,8 +115,8 @@ public class UnlockPDFFormsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String mergedFileName =
|
String mergedFileName =
|
||||||
file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(
|
||||||
+ "_unlocked_forms.pdf";
|
file.getFileInput().getOriginalFilename(), "_unlocked_forms.pdf");
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document, Filenames.toSimpleFileName(mergedFileName));
|
document, Filenames.toSimpleFileName(mergedFileName));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
+3
-3
@@ -31,6 +31,7 @@ import stirling.software.SPDF.model.PipelineOperation;
|
|||||||
import stirling.software.SPDF.model.PipelineResult;
|
import stirling.software.SPDF.model.PipelineResult;
|
||||||
import stirling.software.SPDF.model.api.HandleDataRequest;
|
import stirling.software.SPDF.model.api.HandleDataRequest;
|
||||||
import stirling.software.common.service.PostHogService;
|
import stirling.software.common.service.PostHogService;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -98,9 +99,8 @@ public class PipelineController {
|
|||||||
// Check if the filename already exists, and modify it if necessary
|
// Check if the filename already exists, and modify it if necessary
|
||||||
if (filenameCount.containsKey(originalFilename)) {
|
if (filenameCount.containsKey(originalFilename)) {
|
||||||
int count = filenameCount.get(originalFilename);
|
int count = filenameCount.get(originalFilename);
|
||||||
String baseName = originalFilename.replaceAll("\\.[^.]*$", "");
|
assert originalFilename != null;
|
||||||
String extension = originalFilename.replaceAll("^.*\\.", "");
|
filename = GeneralUtils.generateFilename(originalFilename, "(" + count + ")");
|
||||||
filename = baseName + "(" + count + ")." + extension;
|
|
||||||
filenameCount.put(originalFilename, count + 1);
|
filenameCount.put(originalFilename, count + 1);
|
||||||
} else {
|
} else {
|
||||||
filenameCount.put(originalFilename, 1);
|
filenameCount.put(originalFilename, 1);
|
||||||
|
|||||||
+6
-10
@@ -57,14 +57,9 @@ import org.springframework.core.io.ClassPathResource;
|
|||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.WebDataBinder;
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.bind.annotation.InitBinder;
|
import org.springframework.web.bind.annotation.*;
|
||||||
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.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
|
||||||
import io.micrometer.common.util.StringUtils;
|
import io.micrometer.common.util.StringUtils;
|
||||||
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;
|
||||||
@@ -75,6 +70,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -214,10 +210,10 @@ public class CertSignController {
|
|||||||
location,
|
location,
|
||||||
reason,
|
reason,
|
||||||
showLogo);
|
showLogo);
|
||||||
return WebResponseUtils.baosToWebResponse(
|
// Return the signed PDF
|
||||||
baos,
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
baos.toByteArray(),
|
||||||
+ "_signed.pdf");
|
GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_signed.pdf"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
|
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
|
||||||
|
|||||||
+7
-2
@@ -66,6 +66,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
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;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -224,9 +225,13 @@ public class GetInfoOnPDF {
|
|||||||
|
|
||||||
// Number of words, paragraphs, and images in the entire document
|
// Number of words, paragraphs, and images in the entire document
|
||||||
String fullText = new PDFTextStripper().getText(pdfBoxDoc);
|
String fullText = new PDFTextStripper().getText(pdfBoxDoc);
|
||||||
String[] words = fullText.split("\\s+");
|
String[] words = RegexPatternUtils.getInstance().getWhitespacePattern().split(fullText);
|
||||||
int wordCount = words.length;
|
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("WordCount", wordCount);
|
||||||
basicInfo.put("ParagraphCount", paragraphCount);
|
basicInfo.put("ParagraphCount", paragraphCount);
|
||||||
// Number of characters in the entire document (including spaces and special characters)
|
// Number of characters in the entire document (including spaces and special characters)
|
||||||
|
|||||||
+6
-10
@@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.security.PDFPasswordRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -49,9 +49,8 @@ public class PasswordController {
|
|||||||
document.setAllSecurityToBeRemoved(true);
|
document.setAllSecurityToBeRemoved(true);
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
GeneralUtils.generateFilename(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
fileInput.getOriginalFilename(), "_password_removed.pdf"));
|
||||||
+ "_password_removed.pdf");
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
document.close();
|
document.close();
|
||||||
ExceptionUtils.logException("password removal", e);
|
ExceptionUtils.logException("password removal", e);
|
||||||
@@ -104,13 +103,10 @@ public class PasswordController {
|
|||||||
if ("".equals(ownerPassword) && "".equals(password))
|
if ("".equals(ownerPassword) && "".equals(password))
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
GeneralUtils.generateFilename(
|
||||||
.replaceFirst("[.][^.]+$", "")
|
fileInput.getOriginalFilename(), "_permissions.pdf"));
|
||||||
+ "_permissions.pdf");
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
GeneralUtils.generateFilename(fileInput.getOriginalFilename(), "_passworded.pdf"));
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_passworded.pdf");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -89,7 +89,7 @@ public class RedactController {
|
|||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
private String removeFileExtension(String filename) {
|
private String removeFileExtension(String filename) {
|
||||||
return filename.replaceFirst("[.][^.]+$", "");
|
return GeneralUtils.removeExtension(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitBinder
|
@InitBinder
|
||||||
|
|||||||
+2
-3
@@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.model.api.PDFFile;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -65,7 +65,6 @@ public class RemoveCertSignController {
|
|||||||
// Return the modified PDF as a response
|
// Return the modified PDF as a response
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_unsigned.pdf"));
|
||||||
+ "_unsigned.pdf");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
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.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.security.SanitizePdfRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -88,11 +89,14 @@ public class SanitizeController {
|
|||||||
sanitizeFonts(document);
|
sanitizeFonts(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
// Save the sanitized document to output stream
|
||||||
document,
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
document.save(outputStream);
|
||||||
.replaceFirst("[.][^.]+$", "")
|
document.close();
|
||||||
+ "_sanitized.pdf");
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
outputStream.toByteArray(),
|
||||||
|
GeneralUtils.generateFilename(inputFile.getOriginalFilename(), "_sanitized.pdf"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
||||||
|
|||||||
+6
-5
@@ -34,7 +34,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
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.SPDF.model.api.security.AddWatermarkRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.PdfUtils;
|
import stirling.software.common.util.PdfUtils;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -149,11 +150,10 @@ public class WatermarkController {
|
|||||||
document = convertedPdf;
|
document = convertedPdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the watermarked PDF as a response
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_watermarked.pdf"));
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_watermarked.pdf");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTextWatermark(
|
private void addTextWatermark(
|
||||||
@@ -219,7 +219,8 @@ public class WatermarkController {
|
|||||||
}
|
}
|
||||||
contentStream.setNonStrokingColor(redactColor);
|
contentStream.setNonStrokingColor(redactColor);
|
||||||
|
|
||||||
String[] textLines = watermarkText.split("\\\\n");
|
String[] textLines =
|
||||||
|
RegexPatternUtils.getInstance().getEscapedNewlinePattern().split(watermarkText);
|
||||||
float maxLineWidth = 0;
|
float maxLineWidth = 0;
|
||||||
|
|
||||||
for (int i = 0; i < textLines.length; ++i) {
|
for (int i = 0; i < textLines.length; ++i) {
|
||||||
|
|||||||
+34
-22
@@ -1,7 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -17,31 +16,44 @@ public class UploadLimitService {
|
|||||||
@Autowired private ApplicationProperties applicationProperties;
|
@Autowired private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
public long getUploadLimit() {
|
public long getUploadLimit() {
|
||||||
String maxUploadSize =
|
String raw =
|
||||||
applicationProperties.getSystem().getFileUploadLimit() != null
|
applicationProperties.getSystem().getFileUploadLimit() != null
|
||||||
? applicationProperties.getSystem().getFileUploadLimit()
|
? applicationProperties.getSystem().getFileUploadLimit()
|
||||||
: "";
|
: "";
|
||||||
|
if (raw == null || raw.isEmpty()) {
|
||||||
if (maxUploadSize.isEmpty()) {
|
return 0L;
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
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?
|
// TODO: why do this server side not client?
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import lombok.Getter;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.PDFText;
|
import stirling.software.SPDF.model.PDFText;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class TextFinder extends PDFTextStripper {
|
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);
|
Matcher matcher = pattern.matcher(text);
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
@@ -26,6 +25,7 @@ import stirling.software.SPDF.SPDFApplication;
|
|||||||
import stirling.software.SPDF.model.ApiEndpoint;
|
import stirling.software.SPDF.model.ApiEndpoint;
|
||||||
import stirling.software.common.model.enumeration.Role;
|
import stirling.software.common.model.enumeration.Role;
|
||||||
import stirling.software.common.service.UserServiceInterface;
|
import stirling.software.common.service.UserServiceInterface;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -82,13 +82,11 @@ public class ApiDocService {
|
|||||||
}
|
}
|
||||||
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
||||||
String description = endpoint.getDescription();
|
String description = endpoint.getDescription();
|
||||||
Pattern pattern = null;
|
Matcher matcher =
|
||||||
if (output) {
|
(output
|
||||||
pattern = Pattern.compile("Output:(\\w+)");
|
? RegexPatternUtils.getInstance().getApiDocOutputTypePattern()
|
||||||
} else {
|
: RegexPatternUtils.getInstance().getApiDocInputTypePattern())
|
||||||
pattern = Pattern.compile("Input:(\\w+)");
|
.matcher(description);
|
||||||
}
|
|
||||||
Matcher matcher = pattern.matcher(description);
|
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String type = matcher.group(1).toUpperCase();
|
String type = matcher.group(1).toUpperCase();
|
||||||
if (outputToFileTypes.containsKey(type)) {
|
if (outputToFileTypes.containsKey(type)) {
|
||||||
@@ -157,8 +155,8 @@ public class ApiDocService {
|
|||||||
}
|
}
|
||||||
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
||||||
String description = endpoint.getDescription();
|
String description = endpoint.getDescription();
|
||||||
Pattern pattern = Pattern.compile("Type:(\\w+)");
|
Matcher matcher =
|
||||||
Matcher matcher = pattern.matcher(description);
|
RegexPatternUtils.getInstance().getApiDocTypePattern().matcher(description);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
String type = matcher.group(1);
|
String type = matcher.group(1);
|
||||||
return type.startsWith("MI");
|
return type.startsWith("MI");
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import org.apache.pdfbox.pdmodel.font.encoding.Encoding;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class TextEncodingHelper {
|
public class TextEncodingHelper {
|
||||||
|
|
||||||
@@ -322,7 +324,7 @@ public class TextEncodingHelper {
|
|||||||
if (fontName == null) {
|
if (fontName == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return fontName.matches("^[A-Z]{6}\\+.*");
|
return RegexPatternUtils.getInstance().getFontNamePattern().matcher(fontName).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean canCalculateBasicWidths(PDFont font) {
|
public static boolean canCalculateBasicWidths(PDFont font) {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import org.apache.pdfbox.pdmodel.PDResources;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class TextFinderUtils {
|
public class TextFinderUtils {
|
||||||
|
|
||||||
@@ -69,9 +71,9 @@ public class TextFinderUtils {
|
|||||||
patternString = applyWordBoundaries(term.trim(), patternString);
|
patternString = applyWordBoundaries(term.trim(), patternString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use PatternFactory for better performance with cached compilation
|
||||||
Pattern pattern =
|
Pattern pattern =
|
||||||
Pattern.compile(
|
RegexPatternUtils.getInstance().createSearchPattern(patternString, true);
|
||||||
patternString, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
|
||||||
patterns.add(pattern);
|
patterns.add(pattern);
|
||||||
|
|
||||||
log.debug("Created search pattern: '{}' -> '{}'", term.trim(), patternString);
|
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.FileStorage;
|
||||||
import stirling.software.common.service.JobQueue;
|
import stirling.software.common.service.JobQueue;
|
||||||
import stirling.software.common.service.TaskManager;
|
import stirling.software.common.service.TaskManager;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
/** REST controller for job-related endpoints */
|
/** REST controller for job-related endpoints */
|
||||||
@RestController
|
@RestController
|
||||||
@@ -319,8 +320,10 @@ public class JobController {
|
|||||||
private String createContentDispositionHeader(String fileName) {
|
private String createContentDispositionHeader(String fileName) {
|
||||||
try {
|
try {
|
||||||
String encodedFileName =
|
String encodedFileName =
|
||||||
URLEncoder.encode(fileName, StandardCharsets.UTF_8)
|
RegexPatternUtils.getInstance()
|
||||||
.replace("+", "%20"); // URLEncoder uses + for spaces, but we want %20
|
.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;
|
return "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + encodedFileName;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Fallback to basic filename if encoding fails
|
// Fallback to basic filename if encoding fails
|
||||||
|
|||||||
+4
-2
@@ -70,7 +70,8 @@ class MultiPageLayoutControllerTest {
|
|||||||
Assertions.assertNotNull(resp.getBody());
|
Assertions.assertNotNull(resp.getBody());
|
||||||
Assertions.assertTrue(resp.getBody().length > 0);
|
Assertions.assertTrue(resp.getBody().length > 0);
|
||||||
Assertions.assertEquals(
|
Assertions.assertEquals(
|
||||||
"test_layoutChanged.pdf", resp.getHeaders().getContentDisposition().getFilename());
|
"test_multi_page_layout.pdf",
|
||||||
|
resp.getHeaders().getContentDisposition().getFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -111,6 +112,7 @@ class MultiPageLayoutControllerTest {
|
|||||||
|
|
||||||
ResponseEntity<byte[]> resp = controller.mergeMultiplePagesIntoOne(req);
|
ResponseEntity<byte[]> resp = controller.mergeMultiplePagesIntoOne(req);
|
||||||
Assertions.assertEquals(
|
Assertions.assertEquals(
|
||||||
"name_layoutChanged.pdf", resp.getHeaders().getContentDisposition().getFilename());
|
"name_multi_page_layout.pdf",
|
||||||
|
resp.getHeaders().getContentDisposition().getFilename());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.RequestUriUtils;
|
import stirling.software.common.util.RequestUriUtils;
|
||||||
import stirling.software.proprietary.config.AuditConfigurationProperties;
|
import stirling.software.proprietary.config.AuditConfigurationProperties;
|
||||||
|
|
||||||
@@ -323,7 +324,10 @@ public class AuditUtils {
|
|||||||
return AuditEventType.SETTINGS_CHANGED;
|
return AuditEventType.SETTINGS_CHANGED;
|
||||||
} else if (cls.contains("file")
|
} else if (cls.contains("file")
|
||||||
|| path.startsWith("/file")
|
|| path.startsWith("/file")
|
||||||
|| path.matches("(?i).*/(upload|download)/.*")) {
|
|| RegexPatternUtils.getInstance()
|
||||||
|
.getUploadDownloadPathPattern()
|
||||||
|
.matcher(path)
|
||||||
|
.matches()) {
|
||||||
return AuditEventType.FILE_OPERATION;
|
return AuditEventType.FILE_OPERATION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,7 +391,10 @@ public class AuditUtils {
|
|||||||
return AuditEventType.SETTINGS_CHANGED;
|
return AuditEventType.SETTINGS_CHANGED;
|
||||||
} else if (cls.contains("file")
|
} else if (cls.contains("file")
|
||||||
|| path.startsWith("/file")
|
|| path.startsWith("/file")
|
||||||
|| path.matches("(?i).*/(upload|download)/.*")) {
|
|| RegexPatternUtils.getInstance()
|
||||||
|
.getUploadDownloadPathPattern()
|
||||||
|
.matcher(path)
|
||||||
|
.matches()) {
|
||||||
return AuditEventType.FILE_OPERATION;
|
return AuditEventType.FILE_OPERATION;
|
||||||
} else {
|
} else {
|
||||||
return AuditEventType.PDF_PROCESS;
|
return AuditEventType.PDF_PROCESS;
|
||||||
|
|||||||
+5
-1
@@ -27,6 +27,7 @@ import stirling.software.common.model.ApplicationProperties;
|
|||||||
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.common.model.ApplicationProperties.Security.SAML2;
|
import stirling.software.common.model.ApplicationProperties.Security.SAML2;
|
||||||
import stirling.software.common.model.oauth2.KeycloakProvider;
|
import stirling.software.common.model.oauth2.KeycloakProvider;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.common.util.UrlUtils;
|
import stirling.software.common.util.UrlUtils;
|
||||||
import stirling.software.proprietary.audit.AuditEventType;
|
import stirling.software.proprietary.audit.AuditEventType;
|
||||||
import stirling.software.proprietary.audit.AuditLevel;
|
import stirling.software.proprietary.audit.AuditLevel;
|
||||||
@@ -250,6 +251,9 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
* @return a sanitised <code>String</code>
|
* @return a sanitised <code>String</code>
|
||||||
*/
|
*/
|
||||||
private String sanitizeInput(String input) {
|
private String sanitizeInput(String input) {
|
||||||
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
return RegexPatternUtils.getInstance()
|
||||||
|
.getInputSanitizePattern()
|
||||||
|
.matcher(input)
|
||||||
|
.replaceAll("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.common.util.GeneralUtils;
|
import stirling.software.common.util.GeneralUtils;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -117,7 +118,11 @@ public class KeygenLicenseVerifier {
|
|||||||
// Remove the footer
|
// Remove the footer
|
||||||
encodedPayload = encodedPayload.replace(CERT_SUFFIX, "");
|
encodedPayload = encodedPayload.replace(CERT_SUFFIX, "");
|
||||||
// Remove all newlines
|
// Remove all newlines
|
||||||
encodedPayload = encodedPayload.replaceAll("\\r?\\n", "");
|
encodedPayload =
|
||||||
|
RegexPatternUtils.getInstance()
|
||||||
|
.getEncodedPayloadNewlinePattern()
|
||||||
|
.matcher(encodedPayload)
|
||||||
|
.replaceAll("");
|
||||||
|
|
||||||
byte[] payloadBytes = Base64.getDecoder().decode(encodedPayload);
|
byte[] payloadBytes = Base64.getDecoder().decode(encodedPayload);
|
||||||
String payload = new String(payloadBytes);
|
String payload = new String(payloadBytes);
|
||||||
|
|||||||
+3
-1
@@ -36,6 +36,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.common.util.GeneralUtils;
|
import stirling.software.common.util.GeneralUtils;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.proprietary.security.model.api.admin.SettingValueResponse;
|
import stirling.software.proprietary.security.model.api.admin.SettingValueResponse;
|
||||||
import stirling.software.proprietary.security.model.api.admin.UpdateSettingValueRequest;
|
import stirling.software.proprietary.security.model.api.admin.UpdateSettingValueRequest;
|
||||||
import stirling.software.proprietary.security.model.api.admin.UpdateSettingsRequest;
|
import stirling.software.proprietary.security.model.api.admin.UpdateSettingsRequest;
|
||||||
@@ -444,7 +445,8 @@ public class AdminSettingsController {
|
|||||||
"legal");
|
"legal");
|
||||||
|
|
||||||
// Pattern to validate safe property paths - only alphanumeric, dots, and underscores
|
// Pattern to validate safe property paths - only alphanumeric, dots, and underscores
|
||||||
private static final Pattern SAFE_KEY_PATTERN = Pattern.compile("^[a-zA-Z0-9._]+$");
|
private static final Pattern SAFE_KEY_PATTERN =
|
||||||
|
RegexPatternUtils.getInstance().getPattern("^[a-zA-Z0-9._]+$");
|
||||||
private static final int MAX_NESTING_DEPTH = 10;
|
private static final int MAX_NESTING_DEPTH = 10;
|
||||||
|
|
||||||
// Security: Generic error messages to prevent information disclosure
|
// Security: Generic error messages to prevent information disclosure
|
||||||
|
|||||||
+2
-1
@@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import stirling.software.common.model.enumeration.Role;
|
import stirling.software.common.model.enumeration.Role;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
||||||
@@ -143,6 +144,6 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String stripNewlines(final String s) {
|
private static String stripNewlines(final String s) {
|
||||||
return s.replaceAll("[\n\r]", "");
|
return RegexPatternUtils.getInstance().getNewlineCharsPattern().matcher(s).replaceAll("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -75,8 +75,11 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
*/
|
*/
|
||||||
private AuthenticationType determinePreferredSSOType() {
|
private AuthenticationType determinePreferredSSOType() {
|
||||||
// Check what SSO types are enabled and prefer in order: OAUTH2 > SAML2 > fallback to OAUTH2
|
// Check what SSO types are enabled and prefer in order: OAUTH2 > SAML2 > fallback to OAUTH2
|
||||||
boolean oauth2Enabled = securityProperties.getOauth2() != null && securityProperties.getOauth2().getEnabled();
|
boolean oauth2Enabled =
|
||||||
boolean saml2Enabled = securityProperties.getSaml2() != null && securityProperties.getSaml2().getEnabled();
|
securityProperties.getOauth2() != null
|
||||||
|
&& securityProperties.getOauth2().getEnabled();
|
||||||
|
boolean saml2Enabled =
|
||||||
|
securityProperties.getSaml2() != null && securityProperties.getSaml2().getEnabled();
|
||||||
|
|
||||||
if (oauth2Enabled) {
|
if (oauth2Enabled) {
|
||||||
return AuthenticationType.OAUTH2;
|
return AuthenticationType.OAUTH2;
|
||||||
|
|||||||
+9
-3
@@ -31,6 +31,7 @@ import stirling.software.common.model.ApplicationProperties;
|
|||||||
import stirling.software.common.model.enumeration.Role;
|
import stirling.software.common.model.enumeration.Role;
|
||||||
import stirling.software.common.model.exception.UnsupportedProviderException;
|
import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||||
import stirling.software.common.service.UserServiceInterface;
|
import stirling.software.common.service.UserServiceInterface;
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
import stirling.software.proprietary.model.Team;
|
import stirling.software.proprietary.model.Team;
|
||||||
import stirling.software.proprietary.security.database.repository.AuthorityRepository;
|
import stirling.software.proprietary.security.database.repository.AuthorityRepository;
|
||||||
import stirling.software.proprietary.security.database.repository.UserRepository;
|
import stirling.software.proprietary.security.database.repository.UserRepository;
|
||||||
@@ -480,13 +481,18 @@ public class UserService implements UserServiceInterface {
|
|||||||
// Checks whether the simple username is formatted correctly
|
// Checks whether the simple username is formatted correctly
|
||||||
// Regular expression for user name: Min. 3 characters, max. 50 characters
|
// Regular expression for user name: Min. 3 characters, max. 50 characters
|
||||||
boolean isValidSimpleUsername =
|
boolean isValidSimpleUsername =
|
||||||
username.matches("^[a-zA-Z0-9](?!.*[-@._+]{2,})[a-zA-Z0-9@._+-]{1,48}[a-zA-Z0-9]$");
|
RegexPatternUtils.getInstance()
|
||||||
|
.getUsernameValidationPattern()
|
||||||
|
.matcher(username)
|
||||||
|
.matches();
|
||||||
|
|
||||||
// Checks whether the email address is formatted correctly
|
// Checks whether the email address is formatted correctly
|
||||||
// Regular expression for email addresses: Max. 320 characters, with RFC-like validation
|
// Regular expression for email addresses: Max. 320 characters, with RFC-like validation
|
||||||
boolean isValidEmail =
|
boolean isValidEmail =
|
||||||
username.matches(
|
RegexPatternUtils.getInstance()
|
||||||
"^(?=.{1,320}$)(?=.{1,64}@)[A-Za-z0-9](?:[A-Za-z0-9_.+-]*[A-Za-z0-9])?@[^-][A-Za-z0-9-]+(?:\\\\.[A-Za-z0-9-]+)*(?:\\\\.[A-Za-z]{2,})$");
|
.getEmailValidationPattern()
|
||||||
|
.matcher(username)
|
||||||
|
.matches();
|
||||||
|
|
||||||
List<String> notAllowedUserList = new ArrayList<>();
|
List<String> notAllowedUserList = new ArrayList<>();
|
||||||
notAllowedUserList.add("ALL_USERS".toLowerCase());
|
notAllowedUserList.add("ALL_USERS".toLowerCase());
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.util.RegexPatternUtils;
|
||||||
|
|
||||||
/** Redacts any map values whose keys match common secret/token patterns. */
|
/** Redacts any map values whose keys match common secret/token patterns. */
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class SecretMasker {
|
public final class SecretMasker {
|
||||||
|
|
||||||
private static final Pattern SENSITIVE =
|
private static final Pattern SENSITIVE =
|
||||||
Pattern.compile(
|
RegexPatternUtils.getInstance()
|
||||||
|
.getPattern(
|
||||||
"(?i)(password|token|secret|api[_-]?key|authorization|auth|jwt|cred|cert)");
|
"(?i)(password|token|secret|api[_-]?key|authorization|auth|jwt|cred|cert)");
|
||||||
|
|
||||||
private SecretMasker() {}
|
private SecretMasker() {}
|
||||||
|
|||||||
Reference in New Issue
Block a user