mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
refactor(api): replace regex string literals with Pattern instances for improved performance and readability (#5680)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Signed-off-by: Balázs Szücs <[email protected]>
This commit is contained in:
@@ -9,6 +9,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@@ -38,6 +39,10 @@ import stirling.software.common.model.ApplicationProperties;
|
||||
})
|
||||
public class SPDFApplication {
|
||||
|
||||
private static final Pattern PORT_SUFFIX_PATTERN = Pattern.compile(".+:\\d+$");
|
||||
private static final Pattern URL_SCHEME_PATTERN =
|
||||
Pattern.compile("^[a-zA-Z][a-zA-Z0-9+.-]*://.*");
|
||||
private static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile("/+$");
|
||||
private static String serverPortStatic;
|
||||
private static String baseUrlStatic;
|
||||
private static String contextPathStatic;
|
||||
@@ -244,8 +249,8 @@ public class SPDFApplication {
|
||||
String trimmedBase =
|
||||
(backendUrl == null || backendUrl.isBlank())
|
||||
? "http://localhost"
|
||||
: backendUrl.trim().replaceAll("/+$", "");
|
||||
boolean hasScheme = trimmedBase.matches("^[a-zA-Z][a-zA-Z0-9+.-]*://.*");
|
||||
: TRAILING_SLASH_PATTERN.matcher(backendUrl.trim()).replaceAll("");
|
||||
boolean hasScheme = URL_SCHEME_PATTERN.matcher(trimmedBase).matches();
|
||||
String baseForParsing = hasScheme ? trimmedBase : "http://" + trimmedBase;
|
||||
Integer parsedPort = parsePort(port);
|
||||
|
||||
@@ -298,7 +303,7 @@ public class SPDFApplication {
|
||||
if (port == null) {
|
||||
return trimmedBase;
|
||||
}
|
||||
if (trimmedBase.matches(".+:\\d+$")) {
|
||||
if (PORT_SUFFIX_PATTERN.matcher(trimmedBase).matches()) {
|
||||
return trimmedBase;
|
||||
}
|
||||
return trimmedBase + ":" + port;
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -51,6 +52,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
@RequiredArgsConstructor
|
||||
public class MergeController {
|
||||
|
||||
private static final Pattern QUOTE_WRAP_PATTERN = Pattern.compile("^\"|\"$");
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@@ -173,7 +175,7 @@ public class MergeController {
|
||||
String[] parts = inside.split(",");
|
||||
String[] result = new String[parts.length];
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
result[i] = parts[i].trim().replaceAll("^\"|\"$", "");
|
||||
result[i] = QUOTE_WRAP_PATTERN.matcher(parts[i].trim()).replaceAll("");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+14
-4
@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -36,6 +37,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
@RequiredArgsConstructor
|
||||
public class ConvertPdfJsonController {
|
||||
|
||||
private static final Pattern FILE_EXTENSION_PATTERN = Pattern.compile("[.][^.]+$");
|
||||
private final PdfJsonConversionService pdfJsonConversionService;
|
||||
|
||||
@Autowired(required = false)
|
||||
@@ -59,7 +61,9 @@ public class ConvertPdfJsonController {
|
||||
String originalName = inputFile.getOriginalFilename();
|
||||
String baseName =
|
||||
(originalName != null && !originalName.isBlank())
|
||||
? Filenames.toSimpleFileName(originalName).replaceFirst("[.][^.]+$", "")
|
||||
? FILE_EXTENSION_PATTERN
|
||||
.matcher(Filenames.toSimpleFileName(originalName))
|
||||
.replaceFirst("")
|
||||
: "document";
|
||||
String docName = baseName + ".json";
|
||||
return WebResponseUtils.bytesToWebResponse(jsonBytes, docName, MediaType.APPLICATION_JSON);
|
||||
@@ -82,7 +86,9 @@ public class ConvertPdfJsonController {
|
||||
String originalName = jsonFile.getOriginalFilename();
|
||||
String baseName =
|
||||
(originalName != null && !originalName.isBlank())
|
||||
? Filenames.toSimpleFileName(originalName).replaceFirst("[.][^.]+$", "")
|
||||
? FILE_EXTENSION_PATTERN
|
||||
.matcher(Filenames.toSimpleFileName(originalName))
|
||||
.replaceFirst("")
|
||||
: "document";
|
||||
String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, docName);
|
||||
@@ -115,7 +121,9 @@ public class ConvertPdfJsonController {
|
||||
String originalName = inputFile.getOriginalFilename();
|
||||
String baseName =
|
||||
(originalName != null && !originalName.isBlank())
|
||||
? Filenames.toSimpleFileName(originalName).replaceFirst("[.][^.]+$", "")
|
||||
? FILE_EXTENSION_PATTERN
|
||||
.matcher(Filenames.toSimpleFileName(originalName))
|
||||
.replaceFirst("")
|
||||
: "document";
|
||||
String docName = baseName + "_metadata.json";
|
||||
|
||||
@@ -152,7 +160,9 @@ public class ConvertPdfJsonController {
|
||||
|
||||
String baseName =
|
||||
(filename != null && !filename.isBlank())
|
||||
? Filenames.toSimpleFileName(filename).replaceFirst("[.][^.]+$", "")
|
||||
? FILE_EXTENSION_PATTERN
|
||||
.matcher(Filenames.toSimpleFileName(filename))
|
||||
.replaceFirst("")
|
||||
: Optional.ofNullable(document.getMetadata())
|
||||
.map(PdfJsonMetadata::getTitle)
|
||||
.filter(title -> title != null && !title.isBlank())
|
||||
|
||||
+2
-1
@@ -57,6 +57,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
@RequiredArgsConstructor
|
||||
public class StampController {
|
||||
|
||||
private static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@@ -266,7 +267,7 @@ public class StampController {
|
||||
.getEscapedNewlinePattern()
|
||||
.matcher(processedStampText)
|
||||
.replaceAll("\n");
|
||||
String[] lines = normalizedText.split("\\r?\\n");
|
||||
String[] lines = NEWLINE_PATTERN.split(normalizedText);
|
||||
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
|
||||
|
||||
+8
-4
@@ -21,6 +21,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -44,6 +45,7 @@ import stirling.software.common.util.FileMonitor;
|
||||
public class PipelineDirectoryProcessor {
|
||||
|
||||
private static final int MAX_DIRECTORY_DEPTH = 50; // Prevent excessive recursion
|
||||
private static final Pattern WATCHED_FOLDERS_PATTERN = Pattern.compile("\\\\?watchedFolders");
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ApiDocService apiDocService;
|
||||
@@ -433,10 +435,12 @@ public class PipelineDirectoryProcessor {
|
||||
|
||||
private Path determineOutputPath(PipelineConfig config, Path dir) {
|
||||
String outputDir =
|
||||
config.getOutputDir()
|
||||
.replace("{outputFolder}", finishedFoldersDir)
|
||||
.replace("{folderName}", dir.toString())
|
||||
.replaceAll("\\\\?watchedFolders", "");
|
||||
WATCHED_FOLDERS_PATTERN
|
||||
.matcher(
|
||||
config.getOutputDir()
|
||||
.replace("{outputFolder}", finishedFoldersDir)
|
||||
.replace("{folderName}", dir.toString()))
|
||||
.replaceAll("");
|
||||
return Paths.get(outputDir).isAbsolute() ? Paths.get(outputDir) : Paths.get(".", outputDir);
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -6,6 +6,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
@@ -28,6 +29,8 @@ public class ReactRoutingController {
|
||||
|
||||
private static final org.slf4j.Logger log =
|
||||
org.slf4j.LoggerFactory.getLogger(ReactRoutingController.class);
|
||||
private static final Pattern BASE_HREF_PATTERN =
|
||||
Pattern.compile("<base href=\\\"[^\\\"]*\\\"\\s*/?>");
|
||||
|
||||
@Value("${server.servlet.context-path:/}")
|
||||
private String contextPath;
|
||||
@@ -94,9 +97,9 @@ public class ReactRoutingController {
|
||||
html = html.replace("%BASE_URL%", baseUrl);
|
||||
// Also rewrite any existing <base> tag (Vite may have baked one in)
|
||||
html =
|
||||
html.replaceFirst(
|
||||
"<base href=\\\"[^\\\"]*\\\"\\s*/?>",
|
||||
"<base href=\\\"" + baseUrl + "\\\" />");
|
||||
BASE_HREF_PATTERN
|
||||
.matcher(html)
|
||||
.replaceFirst("<base href=\\\"" + baseUrl + "\\\" />");
|
||||
|
||||
// Inject context path as a global variable for API calls
|
||||
String contextPathScript =
|
||||
|
||||
+16
-6
@@ -38,6 +38,7 @@ import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -133,6 +134,9 @@ import stirling.software.common.util.TempFileManager;
|
||||
@RequiredArgsConstructor
|
||||
public class PdfJsonConversionService {
|
||||
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
|
||||
private static final Pattern WHITESPACE_DASH_UNDERSCORE_PATTERN = Pattern.compile("[\\s\\-_]");
|
||||
private static final Pattern FONT_SUBSET_PREFIX_PATTERN = Pattern.compile("^[A-Z]{6}\\+");
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final EndpointConfiguration endpointConfiguration;
|
||||
@@ -531,7 +535,10 @@ public class PdfJsonConversionService {
|
||||
: "Unknown";
|
||||
// Clean up subset prefix (e.g., "ABCDEF+TimesNewRoman"
|
||||
// -> "TimesNewRoman")
|
||||
String cleanName = name.replaceAll("^[A-Z]{6}\\+", "");
|
||||
String cleanName =
|
||||
FONT_SUBSET_PREFIX_PATTERN
|
||||
.matcher(name)
|
||||
.replaceAll("");
|
||||
return String.format("%s (%s)", cleanName, subtype);
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
@@ -1442,9 +1449,9 @@ public class PdfJsonConversionService {
|
||||
if (!fallbackFontService.canEncodeFully(font, text)) {
|
||||
String fontName =
|
||||
fontModel != null && fontModel.getBaseName() != null
|
||||
? fontModel
|
||||
.getBaseName()
|
||||
.replaceAll("^[A-Z]{6}\\+", "") // Remove subset prefix
|
||||
? FONT_SUBSET_PREFIX_PATTERN
|
||||
.matcher(fontModel.getBaseName())
|
||||
.replaceAll("") // Remove subset prefix
|
||||
: (font != null ? font.getName() : "unknown");
|
||||
String fontKey = fontName + ":" + element.getFontId() + ":" + pageNumber;
|
||||
if (!warnedFonts.contains(fontKey)) {
|
||||
@@ -1902,7 +1909,10 @@ public class PdfJsonConversionService {
|
||||
if (plusIndex >= 0 && plusIndex < normalized.length() - 1) {
|
||||
normalized = normalized.substring(plusIndex + 1);
|
||||
}
|
||||
normalized = normalized.toLowerCase(Locale.ROOT).replaceAll("[\\s\\-_]", "");
|
||||
normalized =
|
||||
WHITESPACE_DASH_UNDERSCORE_PATTERN
|
||||
.matcher(normalized.toLowerCase(Locale.ROOT))
|
||||
.replaceAll("");
|
||||
|
||||
// Exact match after normalization
|
||||
try {
|
||||
@@ -3256,7 +3266,7 @@ public class PdfJsonConversionService {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
String trimmed = value.replaceAll("\s+", " ").trim();
|
||||
String trimmed = WHITESPACE_PATTERN.matcher(value).replaceAll(" ").trim();
|
||||
if (trimmed.length() <= 32) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
+16
-9
@@ -7,6 +7,7 @@ import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
@@ -310,6 +311,11 @@ public class PdfJsonFallbackFontService {
|
||||
"classpath:/static/fonts/DejaVuSansMono-BoldOblique.ttf",
|
||||
"DejaVuSansMono-BoldOblique",
|
||||
"ttf")));
|
||||
private static final Pattern BOLD_FONT_WEIGHT_PATTERN =
|
||||
Pattern.compile(".*[_-]?[6-9]00(wght)?.*");
|
||||
private static final Pattern FONT_NAME_DELIMITER_PATTERN = Pattern.compile("[-_,+]");
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
|
||||
private static final Pattern PATTERN = Pattern.compile("^[A-Z]{6}\\+");
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
private final stirling.software.common.model.ApplicationProperties applicationProperties;
|
||||
@@ -418,16 +424,17 @@ public class PdfJsonFallbackFontService {
|
||||
// Normalize font name: remove subset prefix (e.g. "PXAAAC+"), convert to lowercase,
|
||||
// remove spaces
|
||||
String normalized =
|
||||
originalFontName
|
||||
.replaceAll("^[A-Z]{6}\\+", "") // Remove subset prefix
|
||||
.toLowerCase()
|
||||
.replaceAll("\\s+", ""); // Remove spaces (e.g. "Times New Roman" ->
|
||||
WHITESPACE_PATTERN
|
||||
.matcher(
|
||||
PATTERN.matcher(originalFontName).replaceAll("") // Remove subset prefix
|
||||
.toLowerCase())
|
||||
.replaceAll(""); // Remove spaces (e.g. "Times New Roman" ->
|
||||
// "timesnewroman")
|
||||
|
||||
// Extract base name without weight/style suffixes
|
||||
// Split on common delimiters: hyphen, underscore, comma, plus
|
||||
// Handles: "Arimo_700wght" -> "arimo", "Arial-Bold" -> "arial", "Arial,Bold" -> "arial"
|
||||
String baseName = normalized.split("[-_,+]")[0];
|
||||
String baseName = FONT_NAME_DELIMITER_PATTERN.split(normalized)[0];
|
||||
|
||||
String aliasedFontId = FONT_NAME_ALIASES.get(baseName);
|
||||
if (aliasedFontId != null) {
|
||||
@@ -470,7 +477,7 @@ public class PdfJsonFallbackFontService {
|
||||
|
||||
// Check for numeric weight indicators (600-900 = bold)
|
||||
// Handles: "Arimo_700wght", "Arial-700", "Font-w700"
|
||||
if (normalizedFontName.matches(".*[_-]?[6-9]00(wght)?.*")) {
|
||||
if (BOLD_FONT_WEIGHT_PATTERN.matcher(normalizedFontName).matches()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -514,7 +521,7 @@ public class PdfJsonFallbackFontService {
|
||||
// Supported: Liberation (Sans/Serif/Mono), Noto Sans, DejaVu (Sans/Serif/Mono)
|
||||
boolean isSupported =
|
||||
baseFontId.startsWith("fallback-liberation-")
|
||||
|| baseFontId.equals("fallback-noto-sans")
|
||||
|| "fallback-noto-sans".equals(baseFontId)
|
||||
|| baseFontId.startsWith("fallback-dejavu-");
|
||||
|
||||
if (!isSupported) {
|
||||
@@ -523,8 +530,8 @@ public class PdfJsonFallbackFontService {
|
||||
|
||||
// DejaVu Sans and Mono use "oblique" instead of "italic"
|
||||
boolean useOblique =
|
||||
baseFontId.equals("fallback-dejavu-sans")
|
||||
|| baseFontId.equals("fallback-dejavu-mono");
|
||||
"fallback-dejavu-sans".equals(baseFontId)
|
||||
|| "fallback-dejavu-mono".equals(baseFontId);
|
||||
|
||||
if (isBold && isItalic) {
|
||||
return baseFontId + (useOblique ? "-boldoblique" : "-bolditalic");
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -27,6 +28,7 @@ import stirling.software.common.configuration.InstallationPathConfig;
|
||||
@Slf4j
|
||||
public class SharedSignatureService {
|
||||
|
||||
private static final Pattern FILENAME_VALIDATION_PATTERN = Pattern.compile("^[a-zA-Z0-9_.-]+$");
|
||||
private final String SIGNATURE_BASE_PATH;
|
||||
private final String ALL_USERS_FOLDER = "ALL_USERS";
|
||||
private final ObjectMapper objectMapper;
|
||||
@@ -105,7 +107,7 @@ public class SharedSignatureService {
|
||||
throw new IllegalArgumentException("Invalid filename");
|
||||
}
|
||||
// Only allow alphanumeric, hyphen, underscore, and dot (for extensions)
|
||||
if (!fileName.matches("^[a-zA-Z0-9_.-]+$")) {
|
||||
if (!FILENAME_VALIDATION_PATTERN.matcher(fileName).matches()) {
|
||||
throw new IllegalArgumentException("Filename contains invalid characters");
|
||||
}
|
||||
}
|
||||
@@ -113,7 +115,7 @@ public class SharedSignatureService {
|
||||
private String validateAndNormalizeExtension(String extension) {
|
||||
String normalized = extension.toLowerCase().trim();
|
||||
// Whitelist only safe image extensions
|
||||
if (normalized.equals("png") || normalized.equals("jpg") || normalized.equals("jpeg")) {
|
||||
if ("png".equals(normalized) || "jpg".equals(normalized) || "jpeg".equals(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported image extension: " + extension);
|
||||
|
||||
Reference in New Issue
Block a user