mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
refactor(common,core,proprietary): standardize Locale.ROOT usage for case/format & safer string handling (#4628)
# Description of Changes - Standardized all locale-sensitive operations to use `Locale.ROOT`: - Replaced `toLowerCase()/toUpperCase()` and `String.format(...)` with `Locale.ROOT` variants across services, controllers, utils, and tests (e.g., `InstallationPathConfig`, `ApplicationProperties`, `ResourceMonitor`, `ChecksumUtils`, `PdfUtils`, `UploadLimitService`). - Hardened comparisons and parsing: - Normalized host/domain and file-extension checks with `toLowerCase(Locale.ROOT)`; switched several `equals` calls to constant-first style (e.g., content types, security domain checks). - Logging & formatting improvements: - Ensured percent/size values and hex formatting use root-locale formatting to avoid locale-dependent output. - Code quality & readability: - Converted multiple if/else ladders to modern `switch` expressions. - Minor refactors (method references, early returns), removed redundant returns, and clarified log messages. - Minor fixes/behavioral nits: - Normalized printer selection by lowercasing the searched name once; made some equality checks null-safe/constant-first; added missing `@Override` annotations where appropriate. ## Why - Consistent use of `Locale.ROOT` avoids surprises in different user/system locales (e.g., Turkish-I issues), makes string comparisons deterministic, and keeps numeric/hex formatting stable across environments. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] 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) - [x] I have performed a self-review of my own code - [x] 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) ### 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. --------- Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Co-authored-by: James Brunton <jbrunton96@gmail.com>
This commit is contained in:
parent
7f801157c8
commit
6e82f124a4
@ -2,6 +2,7 @@ package stirling.software.common.configuration;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ public class InstallationPathConfig {
|
|||||||
|
|
||||||
private static String initializeBasePath() {
|
private static String initializeBasePath() {
|
||||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
|
||||||
if (os.contains("win")) {
|
if (os.contains("win")) {
|
||||||
return Paths.get(
|
return Paths.get(
|
||||||
System.getenv("APPDATA"), // parent path
|
System.getenv("APPDATA"), // parent path
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -286,7 +287,7 @@ public class ApplicationProperties {
|
|||||||
private KeycloakProvider keycloak = new KeycloakProvider();
|
private KeycloakProvider keycloak = new KeycloakProvider();
|
||||||
|
|
||||||
public Provider get(String registrationId) throws UnsupportedProviderException {
|
public Provider get(String registrationId) throws UnsupportedProviderException {
|
||||||
return switch (registrationId.toLowerCase()) {
|
return switch (registrationId.toLowerCase(Locale.ROOT)) {
|
||||||
case "google" -> getGoogle();
|
case "google" -> getGoogle();
|
||||||
case "github" -> getGithub();
|
case "github" -> getGithub();
|
||||||
case "keycloak" -> getKeycloak();
|
case "keycloak" -> getKeycloak();
|
||||||
|
|||||||
@ -30,11 +30,11 @@ public class FileInfo {
|
|||||||
// Formats the file size into a human-readable string.
|
// Formats the file size into a human-readable string.
|
||||||
public String getFormattedFileSize() {
|
public String getFormattedFileSize() {
|
||||||
if (fileSize >= 1024 * 1024 * 1024) {
|
if (fileSize >= 1024 * 1024 * 1024) {
|
||||||
return String.format(Locale.US, "%.2f GB", fileSize / (1024.0 * 1024 * 1024));
|
return String.format(Locale.ROOT, "%.2f GB", fileSize / (1024.0 * 1024 * 1024));
|
||||||
} else if (fileSize >= 1024 * 1024) {
|
} else if (fileSize >= 1024 * 1024) {
|
||||||
return String.format(Locale.US, "%.2f MB", fileSize / (1024.0 * 1024));
|
return String.format(Locale.ROOT, "%.2f MB", fileSize / (1024.0 * 1024));
|
||||||
} else if (fileSize >= 1024) {
|
} else if (fileSize >= 1024) {
|
||||||
return String.format(Locale.US, "%.2f KB", fileSize / 1024.0);
|
return String.format(Locale.ROOT, "%.2f KB", fileSize / 1024.0);
|
||||||
} else {
|
} else {
|
||||||
return String.format("%d Bytes", fileSize);
|
return String.format("%d Bytes", fileSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.io.InputStream;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
@ -249,7 +250,7 @@ public class CustomPDFDocumentFactory {
|
|||||||
log.debug(
|
log.debug(
|
||||||
"Memory status - Free: {}MB ({}%), Used: {}MB, Max: {}MB",
|
"Memory status - Free: {}MB ({}%), Used: {}MB, Max: {}MB",
|
||||||
actualFreeMemory / (1024 * 1024),
|
actualFreeMemory / (1024 * 1024),
|
||||||
String.format("%.2f", freeMemoryPercent),
|
String.format(Locale.ROOT, "%.2f", freeMemoryPercent),
|
||||||
usedMemory / (1024 * 1024),
|
usedMemory / (1024 * 1024),
|
||||||
maxMemory / (1024 * 1024));
|
maxMemory / (1024 * 1024));
|
||||||
|
|
||||||
@ -258,7 +259,7 @@ public class CustomPDFDocumentFactory {
|
|||||||
|| actualFreeMemory < MIN_FREE_MEMORY_BYTES) {
|
|| actualFreeMemory < MIN_FREE_MEMORY_BYTES) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"Low memory detected ({}%), forcing file-based cache",
|
"Low memory detected ({}%), forcing file-based cache",
|
||||||
String.format("%.2f", freeMemoryPercent));
|
String.format(Locale.ROOT, "%.2f", freeMemoryPercent));
|
||||||
return createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly());
|
return createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly());
|
||||||
} else if (contentSize < SMALL_FILE_THRESHOLD) {
|
} else if (contentSize < SMALL_FILE_THRESHOLD) {
|
||||||
log.debug("Using memory-only cache for small document ({}KB)", contentSize / 1024);
|
log.debug("Using memory-only cache for small document ({}KB)", contentSize / 1024);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package stirling.software.common.service;
|
package stirling.software.common.service;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@ -440,7 +441,7 @@ public class JobExecutorService {
|
|||||||
|
|
||||||
double numericValue = Double.parseDouble(value);
|
double numericValue = Double.parseDouble(value);
|
||||||
|
|
||||||
return switch (unit.toLowerCase()) {
|
return switch (unit.toLowerCase(Locale.ROOT)) {
|
||||||
case "s" -> (long) (numericValue * 1000);
|
case "s" -> (long) (numericValue * 1000);
|
||||||
case "m" -> (long) (numericValue * 60 * 1000);
|
case "m" -> (long) (numericValue * 60 * 1000);
|
||||||
case "h" -> (long) (numericValue * 60 * 60 * 1000);
|
case "h" -> (long) (numericValue * 60 * 60 * 1000);
|
||||||
|
|||||||
@ -397,7 +397,7 @@ public class PostHogService {
|
|||||||
if (hardwareAddress != null) {
|
if (hardwareAddress != null) {
|
||||||
String[] hexadecimal = new String[hardwareAddress.length];
|
String[] hexadecimal = new String[hardwareAddress.length];
|
||||||
for (int i = 0; i < hardwareAddress.length; i++) {
|
for (int i = 0; i < hardwareAddress.length; i++) {
|
||||||
hexadecimal[i] = String.format("%02X", hardwareAddress[i]);
|
hexadecimal[i] = String.format(Locale.ROOT, "%02X", hardwareAddress[i]);
|
||||||
}
|
}
|
||||||
return String.join("-", hexadecimal);
|
return String.join("-", hexadecimal);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.lang.management.MemoryMXBean;
|
|||||||
import java.lang.management.OperatingSystemMXBean;
|
import java.lang.management.OperatingSystemMXBean;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -173,8 +174,8 @@ public class ResourceMonitor {
|
|||||||
log.info("System resource status changed from {} to {}", oldStatus, newStatus);
|
log.info("System resource status changed from {} to {}", oldStatus, newStatus);
|
||||||
log.info(
|
log.info(
|
||||||
"Current metrics - CPU: {}%, Memory: {}%, Free Memory: {} MB",
|
"Current metrics - CPU: {}%, Memory: {}%, Free Memory: {} MB",
|
||||||
String.format("%.1f", cpuUsage * 100),
|
String.format(Locale.ROOT, "%.1f", cpuUsage * 100),
|
||||||
String.format("%.1f", memoryUsage * 100),
|
String.format(Locale.ROOT, "%.1f", memoryUsage * 100),
|
||||||
freeMemory / (1024 * 1024));
|
freeMemory / (1024 * 1024));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.net.Inet6Address;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -83,7 +84,7 @@ public class SsrfProtectionService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.getAllowedDomains().contains(host.toLowerCase());
|
return config.getAllowedDomains().contains(host.toLowerCase(Locale.ROOT));
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("Failed to parse URL for MAX security check: {}", url, e);
|
log.debug("Failed to parse URL for MAX security check: {}", url, e);
|
||||||
@ -101,7 +102,7 @@ public class SsrfProtectionService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String hostLower = host.toLowerCase();
|
String hostLower = host.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
// Check explicit blocked domains
|
// Check explicit blocked domains
|
||||||
if (config.getBlockedDomains().contains(hostLower)) {
|
if (config.getBlockedDomains().contains(hostLower)) {
|
||||||
@ -111,7 +112,7 @@ public class SsrfProtectionService {
|
|||||||
|
|
||||||
// Check internal TLD patterns
|
// Check internal TLD patterns
|
||||||
for (String tld : config.getInternalTlds()) {
|
for (String tld : config.getInternalTlds()) {
|
||||||
if (hostLower.endsWith(tld.toLowerCase())) {
|
if (hostLower.endsWith(tld.toLowerCase(Locale.ROOT))) {
|
||||||
log.debug("URL blocked by internal TLD pattern '{}': {}", tld, url);
|
log.debug("URL blocked by internal TLD pattern '{}': {}", tld, url);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -123,9 +124,11 @@ public class SsrfProtectionService {
|
|||||||
config.getAllowedDomains().stream()
|
config.getAllowedDomains().stream()
|
||||||
.anyMatch(
|
.anyMatch(
|
||||||
domain ->
|
domain ->
|
||||||
hostLower.equals(domain.toLowerCase())
|
hostLower.equals(domain.toLowerCase(Locale.ROOT))
|
||||||
|| hostLower.endsWith(
|
|| hostLower.endsWith(
|
||||||
"." + domain.toLowerCase()));
|
"."
|
||||||
|
+ domain.toLowerCase(
|
||||||
|
Locale.ROOT)));
|
||||||
|
|
||||||
if (!isAllowed) {
|
if (!isAllowed) {
|
||||||
log.debug("URL not in allowed domains list: {}", url);
|
log.debug("URL not in allowed domains list: {}", url);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@ -101,14 +102,16 @@ public class TaskManager {
|
|||||||
if (!extractedFiles.isEmpty()) {
|
if (!extractedFiles.isEmpty()) {
|
||||||
jobResult.completeWithFiles(extractedFiles);
|
jobResult.completeWithFiles(extractedFiles);
|
||||||
log.debug(
|
log.debug(
|
||||||
"Set multiple file results for job ID: {} with {} files extracted from ZIP",
|
"Set multiple file results for job ID: {} with {} files extracted from"
|
||||||
|
+ " ZIP",
|
||||||
jobId,
|
jobId,
|
||||||
extractedFiles.size());
|
extractedFiles.size());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn(
|
log.warn(
|
||||||
"Failed to extract ZIP file for job {}: {}. Falling back to single file result.",
|
"Failed to extract ZIP file for job {}: {}. Falling back to single file"
|
||||||
|
+ " result.",
|
||||||
jobId,
|
jobId,
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
}
|
}
|
||||||
@ -342,12 +345,12 @@ public class TaskManager {
|
|||||||
/** Check if a file is a ZIP file based on content type and filename */
|
/** Check if a file is a ZIP file based on content type and filename */
|
||||||
private boolean isZipFile(String contentType, String fileName) {
|
private boolean isZipFile(String contentType, String fileName) {
|
||||||
if (contentType != null
|
if (contentType != null
|
||||||
&& (contentType.equals("application/zip")
|
&& ("application/zip".equals(contentType)
|
||||||
|| contentType.equals("application/x-zip-compressed"))) {
|
|| "application/x-zip-compressed".equals(contentType))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileName != null && fileName.toLowerCase().endsWith(".zip")) {
|
if (fileName != null && fileName.toLowerCase(Locale.ROOT).endsWith(".zip")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +417,7 @@ public class TaskManager {
|
|||||||
return MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
return MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
String lowerName = fileName.toLowerCase();
|
String lowerName = fileName.toLowerCase(Locale.ROOT);
|
||||||
if (lowerName.endsWith(".pdf")) {
|
if (lowerName.endsWith(".pdf")) {
|
||||||
return MediaType.APPLICATION_PDF_VALUE;
|
return MediaType.APPLICATION_PDF_VALUE;
|
||||||
} else if (lowerName.endsWith(".txt")) {
|
} else if (lowerName.endsWith(".txt")) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import java.io.InputStream;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@ -60,10 +61,9 @@ public class CbrUtils {
|
|||||||
e.getMessage());
|
e.getMessage());
|
||||||
throw ExceptionUtils.createIllegalArgumentException(
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
"error.invalidFormat",
|
"error.invalidFormat",
|
||||||
"Invalid or corrupted CBR/RAR archive. "
|
"Invalid or corrupted CBR/RAR archive. The file may be corrupted, use"
|
||||||
+ "The file may be corrupted, use an unsupported RAR format (RAR5+), "
|
+ " an unsupported RAR format (RAR5+), or may not be a valid RAR"
|
||||||
+ "or may not be a valid RAR archive. "
|
+ " archive. Please ensure the file is a valid RAR archive.");
|
||||||
+ "Please ensure the file is a valid RAR archive.");
|
|
||||||
} catch (RarException e) {
|
} catch (RarException e) {
|
||||||
log.warn("Failed to open CBR/RAR archive: {}", e.getMessage());
|
log.warn("Failed to open CBR/RAR archive: {}", e.getMessage());
|
||||||
String errorMessage;
|
String errorMessage;
|
||||||
@ -73,13 +73,14 @@ public class CbrUtils {
|
|||||||
errorMessage = "Encrypted CBR/RAR archives are not supported.";
|
errorMessage = "Encrypted CBR/RAR archives are not supported.";
|
||||||
} else if (exMessage.isEmpty()) {
|
} else if (exMessage.isEmpty()) {
|
||||||
errorMessage =
|
errorMessage =
|
||||||
"Invalid CBR/RAR archive. "
|
"Invalid CBR/RAR archive. The file may be encrypted, corrupted, or"
|
||||||
+ "The file may be encrypted, corrupted, or use an unsupported format.";
|
+ " use an unsupported format.";
|
||||||
} else {
|
} else {
|
||||||
errorMessage =
|
errorMessage =
|
||||||
"Invalid CBR/RAR archive: "
|
"Invalid CBR/RAR archive: "
|
||||||
+ exMessage
|
+ exMessage
|
||||||
+ ". The file may be encrypted, corrupted, or use an unsupported format.";
|
+ ". The file may be encrypted, corrupted, or use an"
|
||||||
|
+ " unsupported format.";
|
||||||
}
|
}
|
||||||
throw ExceptionUtils.createIllegalArgumentException(
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
"error.invalidFormat", errorMessage);
|
"error.invalidFormat", errorMessage);
|
||||||
@ -121,7 +122,8 @@ public class CbrUtils {
|
|||||||
if (imageEntries.isEmpty()) {
|
if (imageEntries.isEmpty()) {
|
||||||
throw ExceptionUtils.createIllegalArgumentException(
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
"error.fileProcessing",
|
"error.fileProcessing",
|
||||||
"No valid images found in the CBR file. The archive may be empty or contain no supported image formats.");
|
"No valid images found in the CBR file. The archive may be empty or"
|
||||||
|
+ " contain no supported image formats.");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ImageEntryData imageEntry : imageEntries) {
|
for (ImageEntryData imageEntry : imageEntries) {
|
||||||
@ -146,7 +148,8 @@ public class CbrUtils {
|
|||||||
if (document.getNumberOfPages() == 0) {
|
if (document.getNumberOfPages() == 0) {
|
||||||
throw ExceptionUtils.createIllegalArgumentException(
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
"error.fileProcessing",
|
"error.fileProcessing",
|
||||||
"No images could be processed from the CBR file. All images may be corrupted or in unsupported formats.");
|
"No images could be processed from the CBR file. All images may be"
|
||||||
|
+ " corrupted or in unsupported formats.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
@ -159,7 +162,6 @@ public class CbrUtils {
|
|||||||
return GeneralUtils.optimizePdfWithGhostscript(pdfBytes);
|
return GeneralUtils.optimizePdfWithGhostscript(pdfBytes);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.warn("Ghostscript optimization failed, returning unoptimized PDF", e);
|
log.warn("Ghostscript optimization failed, returning unoptimized PDF", e);
|
||||||
return pdfBytes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +180,7 @@ public class CbrUtils {
|
|||||||
throw new IllegalArgumentException("File must have a name");
|
throw new IllegalArgumentException("File must have a name");
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
if (!"cbr".equals(extension) && !"rar".equals(extension)) {
|
if (!"cbr".equals(extension) && !"rar".equals(extension)) {
|
||||||
throw new IllegalArgumentException("File must be a CBR or RAR archive");
|
throw new IllegalArgumentException("File must be a CBR or RAR archive");
|
||||||
}
|
}
|
||||||
@ -190,7 +192,7 @@ public class CbrUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
return "cbr".equals(extension) || "rar".equals(extension);
|
return "cbr".equals(extension) || "rar".equals(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
@ -119,7 +120,6 @@ public class CbzUtils {
|
|||||||
return GeneralUtils.optimizePdfWithGhostscript(pdfBytes);
|
return GeneralUtils.optimizePdfWithGhostscript(pdfBytes);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.warn("Ghostscript optimization failed, returning unoptimized PDF", e);
|
log.warn("Ghostscript optimization failed, returning unoptimized PDF", e);
|
||||||
return pdfBytes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ public class CbzUtils {
|
|||||||
throw new IllegalArgumentException("File must have a name");
|
throw new IllegalArgumentException("File must have a name");
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
if (!"cbz".equals(extension) && !"zip".equals(extension)) {
|
if (!"cbz".equals(extension) && !"zip".equals(extension)) {
|
||||||
throw new IllegalArgumentException("File must be a CBZ or ZIP archive");
|
throw new IllegalArgumentException("File must be a CBZ or ZIP archive");
|
||||||
}
|
}
|
||||||
@ -150,7 +150,7 @@ public class CbzUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
return "cbz".equals(extension) || "zip".equals(extension);
|
return "cbz".equals(extension) || "zip".equals(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ public class CbzUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
return "cbz".equals(extension)
|
return "cbz".equals(extension)
|
||||||
|| "zip".equals(extension)
|
|| "zip".equals(extension)
|
||||||
|| "cbr".equals(extension)
|
|| "cbr".equals(extension)
|
||||||
|
|||||||
@ -58,14 +58,11 @@ public class ChecksumUtils {
|
|||||||
* @throws IOException if reading from the stream fails
|
* @throws IOException if reading from the stream fails
|
||||||
*/
|
*/
|
||||||
public static String checksum(InputStream is, String algorithm) throws IOException {
|
public static String checksum(InputStream is, String algorithm) throws IOException {
|
||||||
switch (algorithm.toUpperCase(Locale.ROOT)) {
|
return switch (algorithm.toUpperCase(Locale.ROOT)) {
|
||||||
case "CRC32":
|
case "CRC32" -> checksumChecksum(is, new CRC32());
|
||||||
return checksumChecksum(is, new CRC32());
|
case "ADLER32" -> checksumChecksum(is, new Adler32());
|
||||||
case "ADLER32":
|
default -> toHex(checksumBytes(is, algorithm));
|
||||||
return checksumChecksum(is, new Adler32());
|
};
|
||||||
default:
|
|
||||||
return toHex(checksumBytes(is, algorithm));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,14 +95,13 @@ public class ChecksumUtils {
|
|||||||
* @throws IOException if reading from the stream fails
|
* @throws IOException if reading from the stream fails
|
||||||
*/
|
*/
|
||||||
public static String checksumBase64(InputStream is, String algorithm) throws IOException {
|
public static String checksumBase64(InputStream is, String algorithm) throws IOException {
|
||||||
switch (algorithm.toUpperCase(Locale.ROOT)) {
|
return switch (algorithm.toUpperCase(Locale.ROOT)) {
|
||||||
case "CRC32":
|
case "CRC32" ->
|
||||||
return Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new CRC32()));
|
Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new CRC32()));
|
||||||
case "ADLER32":
|
case "ADLER32" ->
|
||||||
return Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new Adler32()));
|
Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new Adler32()));
|
||||||
default:
|
default -> Base64.getEncoder().encodeToString(checksumBytes(is, algorithm));
|
||||||
return Base64.getEncoder().encodeToString(checksumBytes(is, algorithm));
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -179,7 +175,7 @@ public class ChecksumUtils {
|
|||||||
for (Map.Entry<String, Checksum> entry : checksums.entrySet()) {
|
for (Map.Entry<String, Checksum> entry : checksums.entrySet()) {
|
||||||
// Keep value as long and mask to ensure unsigned hex formatting.
|
// Keep value as long and mask to ensure unsigned hex formatting.
|
||||||
long unsigned32 = entry.getValue().getValue() & UNSIGNED_32_BIT_MASK;
|
long unsigned32 = entry.getValue().getValue() & UNSIGNED_32_BIT_MASK;
|
||||||
results.put(entry.getKey(), String.format("%08x", unsigned32));
|
results.put(entry.getKey(), String.format(Locale.ROOT, "%08x", unsigned32));
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
@ -258,7 +254,7 @@ public class ChecksumUtils {
|
|||||||
}
|
}
|
||||||
// Keep as long and mask to ensure correct unsigned representation.
|
// Keep as long and mask to ensure correct unsigned representation.
|
||||||
long unsigned32 = checksum.getValue() & UNSIGNED_32_BIT_MASK;
|
long unsigned32 = checksum.getValue() & UNSIGNED_32_BIT_MASK;
|
||||||
return String.format("%08x", unsigned32);
|
return String.format(Locale.ROOT, "%08x", unsigned32);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -294,7 +290,7 @@ public class ChecksumUtils {
|
|||||||
private static String toHex(byte[] hash) {
|
private static String toHex(byte[] hash) {
|
||||||
StringBuilder sb = new StringBuilder(hash.length * 2);
|
StringBuilder sb = new StringBuilder(hash.length * 2);
|
||||||
for (byte b : hash) {
|
for (byte b : hash) {
|
||||||
sb.append(String.format("%02x", b));
|
sb.append(String.format(Locale.ROOT, "%02x", b));
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import java.time.ZonedDateTime;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -229,7 +230,8 @@ public class EmlParser {
|
|||||||
Method getContentType = message.getClass().getMethod("getContentType");
|
Method getContentType = message.getClass().getMethod("getContentType");
|
||||||
String contentType = (String) getContentType.invoke(message);
|
String contentType = (String) getContentType.invoke(message);
|
||||||
|
|
||||||
if (contentType != null && contentType.toLowerCase().contains(TEXT_HTML)) {
|
if (contentType != null
|
||||||
|
&& contentType.toLowerCase(Locale.ROOT).contains(TEXT_HTML)) {
|
||||||
content.setHtmlBody(stringContent);
|
content.setHtmlBody(stringContent);
|
||||||
} else {
|
} else {
|
||||||
content.setTextBody(stringContent);
|
content.setTextBody(stringContent);
|
||||||
@ -296,7 +298,7 @@ public class EmlParser {
|
|||||||
String contentType = (String) getContentType.invoke(part);
|
String contentType = (String) getContentType.invoke(part);
|
||||||
|
|
||||||
String normalizedDisposition =
|
String normalizedDisposition =
|
||||||
disposition != null ? ((String) disposition).toLowerCase() : null;
|
disposition != null ? ((String) disposition).toLowerCase(Locale.ROOT) : null;
|
||||||
|
|
||||||
if ((Boolean) isMimeType.invoke(part, TEXT_PLAIN) && normalizedDisposition == null) {
|
if ((Boolean) isMimeType.invoke(part, TEXT_PLAIN) && normalizedDisposition == null) {
|
||||||
Object partContent = getContent.invoke(part);
|
Object partContent = getContent.invoke(part);
|
||||||
@ -422,7 +424,7 @@ public class EmlParser {
|
|||||||
RegexPatternUtils.getInstance().getNewlineSplitPattern().split(emlContent);
|
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(Locale.ROOT).startsWith(headerName.toLowerCase(Locale.ROOT))) {
|
||||||
StringBuilder value =
|
StringBuilder value =
|
||||||
new StringBuilder(line.substring(headerName.length()).trim());
|
new StringBuilder(line.substring(headerName.length()).trim());
|
||||||
for (int j = i + 1; j < lines.length; j++) {
|
for (int j = i + 1; j < lines.length; j++) {
|
||||||
@ -444,7 +446,7 @@ public class EmlParser {
|
|||||||
|
|
||||||
private static String extractHtmlBody(String emlContent) {
|
private static String extractHtmlBody(String emlContent) {
|
||||||
try {
|
try {
|
||||||
String lowerContent = emlContent.toLowerCase();
|
String lowerContent = emlContent.toLowerCase(Locale.ROOT);
|
||||||
int htmlStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_HTML);
|
int htmlStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_HTML);
|
||||||
if (htmlStart == -1) return null;
|
if (htmlStart == -1) return null;
|
||||||
|
|
||||||
@ -463,7 +465,7 @@ public class EmlParser {
|
|||||||
|
|
||||||
private static String extractTextBody(String emlContent) {
|
private static String extractTextBody(String emlContent) {
|
||||||
try {
|
try {
|
||||||
String lowerContent = emlContent.toLowerCase();
|
String lowerContent = emlContent.toLowerCase(Locale.ROOT);
|
||||||
int textStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_PLAIN);
|
int textStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_PLAIN);
|
||||||
if (textStart == -1) {
|
if (textStart == -1) {
|
||||||
int bodyStart = emlContent.indexOf("\r\n\r\n");
|
int bodyStart = emlContent.indexOf("\r\n\r\n");
|
||||||
@ -516,7 +518,7 @@ public class EmlParser {
|
|||||||
String currentEncoding = "";
|
String currentEncoding = "";
|
||||||
|
|
||||||
for (String line : lines) {
|
for (String line : lines) {
|
||||||
String lowerLine = line.toLowerCase().trim();
|
String lowerLine = line.toLowerCase(Locale.ROOT).trim();
|
||||||
|
|
||||||
if (line.trim().isEmpty()) {
|
if (line.trim().isEmpty()) {
|
||||||
inHeaders = false;
|
inHeaders = false;
|
||||||
@ -554,9 +556,12 @@ public class EmlParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAttachment(String disposition, String filename, String contentType) {
|
private static boolean isAttachment(String disposition, String filename, String contentType) {
|
||||||
return (disposition.toLowerCase().contains(DISPOSITION_ATTACHMENT) && !filename.isEmpty())
|
return (disposition.toLowerCase(Locale.ROOT).contains(DISPOSITION_ATTACHMENT)
|
||||||
|| (!filename.isEmpty() && !contentType.toLowerCase().startsWith("text/"))
|
&& !filename.isEmpty())
|
||||||
|| (contentType.toLowerCase().contains("application/") && !filename.isEmpty());
|
|| (!filename.isEmpty()
|
||||||
|
&& !contentType.toLowerCase(Locale.ROOT).startsWith("text/"))
|
||||||
|
|| (contentType.toLowerCase(Locale.ROOT).contains("application/")
|
||||||
|
&& !filename.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractFilenameFromDisposition(String disposition) {
|
private static String extractFilenameFromDisposition(String disposition) {
|
||||||
@ -565,8 +570,8 @@ public class EmlParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle filename*= (RFC 2231 encoded filename)
|
// Handle filename*= (RFC 2231 encoded filename)
|
||||||
if (disposition.toLowerCase().contains("filename*=")) {
|
if (disposition.toLowerCase(Locale.ROOT).contains("filename*=")) {
|
||||||
int filenameStarStart = disposition.toLowerCase().indexOf("filename*=") + 10;
|
int filenameStarStart = disposition.toLowerCase(Locale.ROOT).indexOf("filename*=") + 10;
|
||||||
int filenameStarEnd = disposition.indexOf(";", filenameStarStart);
|
int filenameStarEnd = disposition.indexOf(";", filenameStarStart);
|
||||||
if (filenameStarEnd == -1) filenameStarEnd = disposition.length();
|
if (filenameStarEnd == -1) filenameStarEnd = disposition.length();
|
||||||
String extendedFilename =
|
String extendedFilename =
|
||||||
@ -586,7 +591,7 @@ public class EmlParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle regular filename=
|
// Handle regular filename=
|
||||||
int filenameStart = disposition.toLowerCase().indexOf("filename=") + 9;
|
int filenameStart = disposition.toLowerCase(Locale.ROOT).indexOf("filename=") + 9;
|
||||||
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();
|
||||||
|
|||||||
@ -109,6 +109,7 @@ public class EmlProcessingUtils {
|
|||||||
|
|
||||||
html.append(
|
html.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en"><head><meta charset="UTF-8">
|
<html lang="en"><head><meta charset="UTF-8">
|
||||||
@ -127,6 +128,7 @@ public class EmlProcessingUtils {
|
|||||||
|
|
||||||
html.append(
|
html.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
<div class="email-container">
|
<div class="email-container">
|
||||||
<div class="email-header">
|
<div class="email-header">
|
||||||
@ -142,6 +144,7 @@ public class EmlProcessingUtils {
|
|||||||
if (content.getCc() != null && !content.getCc().trim().isEmpty()) {
|
if (content.getCc() != null && !content.getCc().trim().isEmpty()) {
|
||||||
html.append(
|
html.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"<div><strong>CC:</strong> %s</div>\n",
|
"<div><strong>CC:</strong> %s</div>\n",
|
||||||
sanitizeText(content.getCc(), customHtmlSanitizer)));
|
sanitizeText(content.getCc(), customHtmlSanitizer)));
|
||||||
}
|
}
|
||||||
@ -149,6 +152,7 @@ public class EmlProcessingUtils {
|
|||||||
if (content.getBcc() != null && !content.getBcc().trim().isEmpty()) {
|
if (content.getBcc() != null && !content.getBcc().trim().isEmpty()) {
|
||||||
html.append(
|
html.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"<div><strong>BCC:</strong> %s</div>\n",
|
"<div><strong>BCC:</strong> %s</div>\n",
|
||||||
sanitizeText(content.getBcc(), customHtmlSanitizer)));
|
sanitizeText(content.getBcc(), customHtmlSanitizer)));
|
||||||
}
|
}
|
||||||
@ -156,11 +160,13 @@ public class EmlProcessingUtils {
|
|||||||
if (content.getDate() != null) {
|
if (content.getDate() != null) {
|
||||||
html.append(
|
html.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"<div><strong>Date:</strong> %s</div>\n",
|
"<div><strong>Date:</strong> %s</div>\n",
|
||||||
PdfAttachmentHandler.formatEmailDate(content.getDate())));
|
PdfAttachmentHandler.formatEmailDate(content.getDate())));
|
||||||
} else if (content.getDateString() != null && !content.getDateString().trim().isEmpty()) {
|
} else if (content.getDateString() != null && !content.getDateString().trim().isEmpty()) {
|
||||||
html.append(
|
html.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"<div><strong>Date:</strong> %s</div>\n",
|
"<div><strong>Date:</strong> %s</div>\n",
|
||||||
sanitizeText(content.getDateString(), customHtmlSanitizer)));
|
sanitizeText(content.getDateString(), customHtmlSanitizer)));
|
||||||
}
|
}
|
||||||
@ -175,6 +181,7 @@ public class EmlProcessingUtils {
|
|||||||
} else if (content.getTextBody() != null && !content.getTextBody().trim().isEmpty()) {
|
} else if (content.getTextBody() != null && !content.getTextBody().trim().isEmpty()) {
|
||||||
html.append(
|
html.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"<div class=\"text-body\">%s</div>",
|
"<div class=\"text-body\">%s</div>",
|
||||||
convertTextToHtml(content.getTextBody(), customHtmlSanitizer)));
|
convertTextToHtml(content.getTextBody(), customHtmlSanitizer)));
|
||||||
} else {
|
} else {
|
||||||
@ -234,14 +241,16 @@ public class EmlProcessingUtils {
|
|||||||
.getUrlLinkPattern()
|
.getUrlLinkPattern()
|
||||||
.matcher(html)
|
.matcher(html)
|
||||||
.replaceAll(
|
.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 =
|
||||||
RegexPatternUtils.getInstance()
|
RegexPatternUtils.getInstance()
|
||||||
.getEmailLinkPattern()
|
.getEmailLinkPattern()
|
||||||
.matcher(html)
|
.matcher(html)
|
||||||
.replaceAll(
|
.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;
|
||||||
}
|
}
|
||||||
@ -249,6 +258,7 @@ public class EmlProcessingUtils {
|
|||||||
private static void appendEnhancedStyles(StringBuilder html) {
|
private static void appendEnhancedStyles(StringBuilder html) {
|
||||||
String css =
|
String css =
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
body {
|
body {
|
||||||
font-family: %s;
|
font-family: %s;
|
||||||
@ -420,6 +430,7 @@ public class EmlProcessingUtils {
|
|||||||
String attachmentId = "attachment_" + i;
|
String attachmentId = "attachment_" + i;
|
||||||
html.append(
|
html.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
<div class="attachment-item" id="%s">
|
<div class="attachment-item" id="%s">
|
||||||
<span class="attachment-icon" data-filename="%s">@</span>
|
<span class="attachment-icon" data-filename="%s">@</span>
|
||||||
@ -470,7 +481,7 @@ public class EmlProcessingUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (filename != null) {
|
if (filename != null) {
|
||||||
String lowerFilename = filename.toLowerCase();
|
String lowerFilename = filename.toLowerCase(Locale.ROOT);
|
||||||
for (Map.Entry<String, String> entry : EXTENSION_TO_MIME_TYPE.entrySet()) {
|
for (Map.Entry<String, String> entry : EXTENSION_TO_MIME_TYPE.entrySet()) {
|
||||||
if (lowerFilename.endsWith(entry.getKey())) {
|
if (lowerFilename.endsWith(entry.getKey())) {
|
||||||
return entry.getValue();
|
return entry.getValue();
|
||||||
@ -516,7 +527,7 @@ public class EmlProcessingUtils {
|
|||||||
result.append(processedText, lastEnd, matcher.start());
|
result.append(processedText, lastEnd, matcher.start());
|
||||||
|
|
||||||
String charset = matcher.group(1);
|
String charset = matcher.group(1);
|
||||||
String encoding = matcher.group(2).toUpperCase();
|
String encoding = matcher.group(2).toUpperCase(Locale.ROOT);
|
||||||
String encodedValue = matcher.group(3);
|
String encodedValue = matcher.group(3);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package stirling.software.common.util;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -34,9 +35,8 @@ public class ExceptionUtils {
|
|||||||
if (context != null && !context.isEmpty()) {
|
if (context != null && !context.isEmpty()) {
|
||||||
message =
|
message =
|
||||||
String.format(
|
String.format(
|
||||||
"Error %s: PDF file appears to be corrupted or damaged. Please try"
|
Locale.ROOT,
|
||||||
+ " using the 'Repair PDF' feature first to fix the file before"
|
"Error %s: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.",
|
||||||
+ " proceeding with this operation.",
|
|
||||||
context);
|
context);
|
||||||
} else {
|
} else {
|
||||||
message =
|
message =
|
||||||
@ -97,8 +97,10 @@ public class ExceptionUtils {
|
|||||||
public static IOException createFileProcessingException(String operation, Exception cause) {
|
public static IOException createFileProcessingException(String operation, Exception cause) {
|
||||||
String message =
|
String message =
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"An error occurred while processing the file during %s operation: %s",
|
"An error occurred while processing the file during %s operation: %s",
|
||||||
operation, cause.getMessage());
|
operation,
|
||||||
|
cause.getMessage());
|
||||||
return new IOException(message, cause);
|
return new IOException(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import java.nio.file.SimpleFileVisitor;
|
|||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
@ -37,9 +38,11 @@ public class FileToPdf {
|
|||||||
try (TempFile tempInputFile =
|
try (TempFile tempInputFile =
|
||||||
new TempFile(
|
new TempFile(
|
||||||
tempFileManager,
|
tempFileManager,
|
||||||
fileName.toLowerCase().endsWith(".html") ? ".html" : ".zip")) {
|
fileName.toLowerCase(Locale.ROOT).endsWith(".html")
|
||||||
|
? ".html"
|
||||||
|
: ".zip")) {
|
||||||
|
|
||||||
if (fileName.toLowerCase().endsWith(".html")) {
|
if (fileName.toLowerCase(Locale.ROOT).endsWith(".html")) {
|
||||||
String sanitizedHtml =
|
String sanitizedHtml =
|
||||||
sanitizeHtmlContent(
|
sanitizeHtmlContent(
|
||||||
new String(fileBytes, StandardCharsets.UTF_8),
|
new String(fileBytes, StandardCharsets.UTF_8),
|
||||||
@ -47,7 +50,7 @@ public class FileToPdf {
|
|||||||
Files.write(
|
Files.write(
|
||||||
tempInputFile.getPath(),
|
tempInputFile.getPath(),
|
||||||
sanitizedHtml.getBytes(StandardCharsets.UTF_8));
|
sanitizedHtml.getBytes(StandardCharsets.UTF_8));
|
||||||
} else if (fileName.toLowerCase().endsWith(".zip")) {
|
} else if (fileName.toLowerCase(Locale.ROOT).endsWith(".zip")) {
|
||||||
Files.write(tempInputFile.getPath(), fileBytes);
|
Files.write(tempInputFile.getPath(), fileBytes);
|
||||||
sanitizeHtmlFilesInZip(
|
sanitizeHtmlFilesInZip(
|
||||||
tempInputFile.getPath(), tempFileManager, customHtmlSanitizer);
|
tempInputFile.getPath(), tempFileManager, customHtmlSanitizer);
|
||||||
@ -102,8 +105,8 @@ public class FileToPdf {
|
|||||||
tempUnzippedDir.getPath().resolve(sanitizeZipFilename(entry.getName()));
|
tempUnzippedDir.getPath().resolve(sanitizeZipFilename(entry.getName()));
|
||||||
if (!entry.isDirectory()) {
|
if (!entry.isDirectory()) {
|
||||||
Files.createDirectories(filePath.getParent());
|
Files.createDirectories(filePath.getParent());
|
||||||
if (entry.getName().toLowerCase().endsWith(".html")
|
if (entry.getName().toLowerCase(Locale.ROOT).endsWith(".html")
|
||||||
|| entry.getName().toLowerCase().endsWith(".htm")) {
|
|| entry.getName().toLowerCase(Locale.ROOT).endsWith(".htm")) {
|
||||||
String content =
|
String content =
|
||||||
new String(zipIn.readAllBytes(), StandardCharsets.UTF_8);
|
new String(zipIn.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
String sanitizedContent =
|
String sanitizedContent =
|
||||||
|
|||||||
@ -545,7 +545,7 @@ public class GeneralUtils {
|
|||||||
throw new IllegalArgumentException("Invalid default unit: " + defaultUnit);
|
throw new IllegalArgumentException("Invalid default unit: " + defaultUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeStr = sizeStr.trim().toUpperCase();
|
sizeStr = sizeStr.trim().toUpperCase(Locale.ROOT);
|
||||||
sizeStr = sizeStr.replace(",", ".").replace(" ", "");
|
sizeStr = sizeStr.replace(",", ".").replace(" ", "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -574,7 +574,7 @@ public class GeneralUtils {
|
|||||||
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
||||||
} else {
|
} else {
|
||||||
// Use provided default unit or fall back to MB
|
// Use provided default unit or fall back to MB
|
||||||
String unit = defaultUnit != null ? defaultUnit.toUpperCase() : "MB";
|
String unit = defaultUnit != null ? defaultUnit.toUpperCase(Locale.ROOT) : "MB";
|
||||||
double value = Double.parseDouble(sizeStr);
|
double value = Double.parseDouble(sizeStr);
|
||||||
return switch (unit) {
|
return switch (unit) {
|
||||||
case "TB" -> (long) (value * 1024L * 1024L * 1024L * 1024L);
|
case "TB" -> (long) (value * 1024L * 1024L * 1024L * 1024L);
|
||||||
@ -616,13 +616,14 @@ public class GeneralUtils {
|
|||||||
if (bytes < 1024) {
|
if (bytes < 1024) {
|
||||||
return bytes + " B";
|
return bytes + " B";
|
||||||
} else if (bytes < 1024L * 1024L) {
|
} else if (bytes < 1024L * 1024L) {
|
||||||
return String.format(Locale.US, "%.2f KB", bytes / 1024.0);
|
return String.format(Locale.ROOT, "%.2f KB", bytes / 1024.0);
|
||||||
} else if (bytes < 1024L * 1024L * 1024L) {
|
} else if (bytes < 1024L * 1024L * 1024L) {
|
||||||
return String.format(Locale.US, "%.2f MB", bytes / (1024.0 * 1024.0));
|
return String.format(Locale.ROOT, "%.2f MB", bytes / (1024.0 * 1024.0));
|
||||||
} else if (bytes < 1024L * 1024L * 1024L * 1024L) {
|
} 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.ROOT, "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
||||||
} else {
|
} else {
|
||||||
return String.format(Locale.US, "%.2f TB", bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0));
|
return String.format(
|
||||||
|
Locale.ROOT, "%.2f TB", bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -882,7 +883,7 @@ public class GeneralUtils {
|
|||||||
byte[] mac = net.getHardwareAddress();
|
byte[] mac = net.getHardwareAddress();
|
||||||
if (mac != null && mac.length > 0) {
|
if (mac != null && mac.length > 0) {
|
||||||
for (byte b : mac) {
|
for (byte b : mac) {
|
||||||
sb.append(String.format("%02X", b));
|
sb.append(String.format(Locale.ROOT, "%02X", b));
|
||||||
}
|
}
|
||||||
break; // Use the first valid network interface
|
break; // Use the first valid network interface
|
||||||
}
|
}
|
||||||
@ -892,7 +893,7 @@ public class GeneralUtils {
|
|||||||
byte[] mac = network.getHardwareAddress();
|
byte[] mac = network.getHardwareAddress();
|
||||||
if (mac != null) {
|
if (mac != null) {
|
||||||
for (byte b : mac) {
|
for (byte b : mac) {
|
||||||
sb.append(String.format("%02X", b));
|
sb.append(String.format(Locale.ROOT, "%02X", b));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageReader;
|
import javax.imageio.ImageReader;
|
||||||
@ -118,7 +119,7 @@ public class ImageProcessingUtils {
|
|||||||
BufferedImage image = null;
|
BufferedImage image = null;
|
||||||
String filename = file.getOriginalFilename();
|
String filename = file.getOriginalFilename();
|
||||||
|
|
||||||
if (filename != null && filename.toLowerCase().endsWith(".psd")) {
|
if (filename != null && filename.toLowerCase(Locale.ROOT).endsWith(".psd")) {
|
||||||
// For PSD files, try explicit ImageReader
|
// For PSD files, try explicit ImageReader
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("PSD");
|
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("PSD");
|
||||||
if (readers.hasNext()) {
|
if (readers.hasNext()) {
|
||||||
@ -134,7 +135,8 @@ public class ImageProcessingUtils {
|
|||||||
throw new IOException(
|
throw new IOException(
|
||||||
"Unable to read image from file: "
|
"Unable to read image from file: "
|
||||||
+ filename
|
+ filename
|
||||||
+ ". Supported PSD formats: RGB/CMYK/Gray 8-32 bit, RLE/ZIP compression");
|
+ ". Supported PSD formats: RGB/CMYK/Gray 8-32 bit, RLE/ZIP"
|
||||||
|
+ " compression");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For non-PSD files, use standard ImageIO
|
// For non-PSD files, use standard ImageIO
|
||||||
|
|||||||
@ -318,7 +318,7 @@ public class PdfAttachmentHandler {
|
|||||||
|
|
||||||
private static String normalizeFilename(String filename) {
|
private static String normalizeFilename(String filename) {
|
||||||
if (filename == null) return "";
|
if (filename == null) return "";
|
||||||
String normalized = filename.toLowerCase().trim();
|
String normalized = filename.toLowerCase(Locale.ROOT).trim();
|
||||||
normalized =
|
normalized =
|
||||||
RegexPatternUtils.getInstance()
|
RegexPatternUtils.getInstance()
|
||||||
.getWhitespacePattern()
|
.getWhitespacePattern()
|
||||||
@ -560,7 +560,7 @@ public class PdfAttachmentHandler {
|
|||||||
@Override
|
@Override
|
||||||
protected void writeString(String string, List<TextPosition> textPositions)
|
protected void writeString(String string, List<TextPosition> textPositions)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String lowerString = string.toLowerCase();
|
String lowerString = string.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
if (ATTACHMENT_SECTION_PATTERN.matcher(lowerString).find()) {
|
if (ATTACHMENT_SECTION_PATTERN.matcher(lowerString).find()) {
|
||||||
isInAttachmentSection = true;
|
isInAttachmentSection = true;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import java.nio.file.Path;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ public class PdfToCbrUtils {
|
|||||||
throw new IllegalArgumentException("File must have a name");
|
throw new IllegalArgumentException("File must have a name");
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
if (!"pdf".equals(extension)) {
|
if (!"pdf".equals(extension)) {
|
||||||
throw new IllegalArgumentException("File must be a PDF");
|
throw new IllegalArgumentException("File must be a PDF");
|
||||||
}
|
}
|
||||||
@ -70,7 +71,8 @@ public class PdfToCbrUtils {
|
|||||||
BufferedImage image =
|
BufferedImage image =
|
||||||
pdfRenderer.renderImageWithDPI(pageIndex, dpi, ImageType.RGB);
|
pdfRenderer.renderImageWithDPI(pageIndex, dpi, ImageType.RGB);
|
||||||
|
|
||||||
String imageFilename = String.format("page_%03d.png", pageIndex + 1);
|
String imageFilename =
|
||||||
|
String.format(Locale.ROOT, "page_%03d.png", pageIndex + 1);
|
||||||
Path imagePath = tempDir.resolve(imageFilename);
|
Path imagePath = tempDir.resolve(imageFilename);
|
||||||
|
|
||||||
ImageIO.write(image, "PNG", imagePath.toFile());
|
ImageIO.write(image, "PNG", imagePath.toFile());
|
||||||
@ -167,7 +169,7 @@ public class PdfToCbrUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
return "pdf".equals(extension);
|
return "pdf".equals(extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package stirling.software.common.util;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ public class PdfToCbzUtils {
|
|||||||
throw new IllegalArgumentException("File must have a name");
|
throw new IllegalArgumentException("File must have a name");
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
if (!"pdf".equals(extension)) {
|
if (!"pdf".equals(extension)) {
|
||||||
throw new IllegalArgumentException("File must be a PDF");
|
throw new IllegalArgumentException("File must be a PDF");
|
||||||
}
|
}
|
||||||
@ -65,7 +66,8 @@ public class PdfToCbzUtils {
|
|||||||
BufferedImage image =
|
BufferedImage image =
|
||||||
pdfRenderer.renderImageWithDPI(pageIndex, dpi, ImageType.RGB);
|
pdfRenderer.renderImageWithDPI(pageIndex, dpi, ImageType.RGB);
|
||||||
|
|
||||||
String imageFilename = String.format("page_%03d.png", pageIndex + 1);
|
String imageFilename =
|
||||||
|
String.format(Locale.ROOT, "page_%03d.png", pageIndex + 1);
|
||||||
|
|
||||||
ZipEntry zipEntry = new ZipEntry(imageFilename);
|
ZipEntry zipEntry = new ZipEntry(imageFilename);
|
||||||
zipOut.putNextEntry(zipEntry);
|
zipOut.putNextEntry(zipEntry);
|
||||||
@ -93,7 +95,7 @@ public class PdfToCbzUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(filename).toLowerCase();
|
String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT);
|
||||||
return "pdf".equals(extension);
|
return "pdf".equals(extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ 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.Locale;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
@ -50,36 +51,18 @@ public class PdfUtils {
|
|||||||
|
|
||||||
public PDRectangle textToPageSize(String size) {
|
public PDRectangle textToPageSize(String size) {
|
||||||
|
|
||||||
switch (size.toUpperCase()) {
|
return switch (size.toUpperCase(Locale.ROOT)) {
|
||||||
case "A0" -> {
|
case "A0" -> PDRectangle.A0;
|
||||||
return PDRectangle.A0;
|
case "A1" -> PDRectangle.A1;
|
||||||
}
|
case "A2" -> PDRectangle.A2;
|
||||||
case "A1" -> {
|
case "A3" -> PDRectangle.A3;
|
||||||
return PDRectangle.A1;
|
case "A4" -> PDRectangle.A4;
|
||||||
}
|
case "A5" -> PDRectangle.A5;
|
||||||
case "A2" -> {
|
case "A6" -> PDRectangle.A6;
|
||||||
return PDRectangle.A2;
|
case "LETTER" -> PDRectangle.LETTER;
|
||||||
}
|
case "LEGAL" -> PDRectangle.LEGAL;
|
||||||
case "A3" -> {
|
|
||||||
return PDRectangle.A3;
|
|
||||||
}
|
|
||||||
case "A4" -> {
|
|
||||||
return PDRectangle.A4;
|
|
||||||
}
|
|
||||||
case "A5" -> {
|
|
||||||
return PDRectangle.A5;
|
|
||||||
}
|
|
||||||
case "A6" -> {
|
|
||||||
return PDRectangle.A6;
|
|
||||||
}
|
|
||||||
case "LETTER" -> {
|
|
||||||
return PDRectangle.LETTER;
|
|
||||||
}
|
|
||||||
case "LEGAL" -> {
|
|
||||||
return PDRectangle.LEGAL;
|
|
||||||
}
|
|
||||||
default -> throw ExceptionUtils.createInvalidPageSizeException(size);
|
default -> throw ExceptionUtils.createInvalidPageSizeException(size);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RenderedImage> getAllImages(PDResources resources) throws IOException {
|
public List<RenderedImage> getAllImages(PDResources resources) throws IOException {
|
||||||
@ -182,8 +165,8 @@ public class PdfUtils {
|
|||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
if (singleImage) {
|
if (singleImage) {
|
||||||
if ("tiff".equals(imageType.toLowerCase())
|
if ("tiff".equals(imageType.toLowerCase(Locale.ROOT))
|
||||||
|| "tif".equals(imageType.toLowerCase())) {
|
|| "tif".equals(imageType.toLowerCase(Locale.ROOT))) {
|
||||||
// Write the images to the output stream as a TIFF with multiple frames
|
// Write the images to the output stream as a TIFF with multiple frames
|
||||||
ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next();
|
ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next();
|
||||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
@ -361,9 +344,10 @@ public class PdfUtils {
|
|||||||
zos.putNextEntry(
|
zos.putNextEntry(
|
||||||
new ZipEntry(
|
new ZipEntry(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
filename + "_%d.%s",
|
filename + "_%d.%s",
|
||||||
i + 1,
|
i + 1,
|
||||||
imageType.toLowerCase())));
|
imageType.toLowerCase(Locale.ROOT))));
|
||||||
zos.write(baosImage.toByteArray());
|
zos.write(baosImage.toByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -463,8 +447,8 @@ public class PdfUtils {
|
|||||||
String contentType = file.getContentType();
|
String contentType = file.getContentType();
|
||||||
String originalFilename = Filenames.toSimpleFileName(file.getOriginalFilename());
|
String originalFilename = Filenames.toSimpleFileName(file.getOriginalFilename());
|
||||||
if (originalFilename != null
|
if (originalFilename != null
|
||||||
&& (originalFilename.toLowerCase().endsWith(".tiff")
|
&& (originalFilename.toLowerCase(Locale.ROOT).endsWith(".tiff")
|
||||||
|| originalFilename.toLowerCase().endsWith(".tif"))) {
|
|| originalFilename.toLowerCase(Locale.ROOT).endsWith(".tif"))) {
|
||||||
ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next();
|
ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next();
|
||||||
reader.setInput(ImageIO.createImageInputStream(file.getInputStream()));
|
reader.setInput(ImageIO.createImageInputStream(file.getInputStream()));
|
||||||
int numPages = reader.getNumImages(true);
|
int numPages = reader.getNumImages(true);
|
||||||
@ -631,7 +615,7 @@ public class PdfUtils {
|
|||||||
int actualPageCount = pdfDocument.getNumberOfPages();
|
int actualPageCount = pdfDocument.getNumberOfPages();
|
||||||
pdfDocument.close();
|
pdfDocument.close();
|
||||||
|
|
||||||
return switch (comparator.toLowerCase()) {
|
return switch (comparator.toLowerCase(Locale.ROOT)) {
|
||||||
case "greater" -> actualPageCount > pageCount;
|
case "greater" -> actualPageCount > pageCount;
|
||||||
case "equal" -> actualPageCount == pageCount;
|
case "equal" -> actualPageCount == pageCount;
|
||||||
case "less" -> actualPageCount < pageCount;
|
case "less" -> actualPageCount < pageCount;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.nio.file.Paths;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -114,7 +115,7 @@ class ApplicationPropertiesLogicTest {
|
|||||||
|
|
||||||
UnsupportedProviderException ex =
|
UnsupportedProviderException ex =
|
||||||
assertThrows(UnsupportedProviderException.class, () -> client.get("unknown"));
|
assertThrows(UnsupportedProviderException.class, () -> client.get("unknown"));
|
||||||
assertTrue(ex.getMessage().toLowerCase().contains("not supported"));
|
assertTrue(ex.getMessage().toLowerCase(Locale.ROOT).contains("not supported"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
import java.lang.management.MemoryMXBean;
|
import java.lang.management.MemoryMXBean;
|
||||||
import java.lang.management.OperatingSystemMXBean;
|
import java.lang.management.OperatingSystemMXBean;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@ -116,8 +117,11 @@ class ResourceMonitorTest {
|
|||||||
shouldQueue,
|
shouldQueue,
|
||||||
result,
|
result,
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"For weight %d and status %s, shouldQueue should be %s",
|
"For weight %d and status %s, shouldQueue should be %s",
|
||||||
weight, status, shouldQueue));
|
weight,
|
||||||
|
status,
|
||||||
|
shouldQueue));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@ -773,20 +774,33 @@ class EmlToPdfTest {
|
|||||||
private String createSimpleTextEmailWithCharset(
|
private String createSimpleTextEmailWithCharset(
|
||||||
String from, String to, String subject, String body, String charset) {
|
String from, String to, String subject, String body, String charset) {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nContent-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n%s",
|
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nContent-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n%s",
|
||||||
from, to, subject, getTimestamp(), charset, body);
|
from,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
getTimestamp(),
|
||||||
|
charset,
|
||||||
|
body);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createEmailWithCustomHeaders() {
|
private String createEmailWithCustomHeaders() {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"From: sender@example.com\nDate: %s\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n%s",
|
"From: sender@example.com\nDate: %s\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n%s",
|
||||||
getTimestamp(), "This is an email body with some headers missing.");
|
getTimestamp(),
|
||||||
|
"This is an email body with some headers missing.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createHtmlEmail(String from, String to, String subject, String htmlBody) {
|
private String createHtmlEmail(String from, String to, String subject, String htmlBody) {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n%s",
|
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n%s",
|
||||||
from, to, subject, getTimestamp(), htmlBody);
|
from,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
getTimestamp(),
|
||||||
|
htmlBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createMultipartEmailWithAttachment(
|
private String createMultipartEmailWithAttachment(
|
||||||
@ -801,6 +815,7 @@ class EmlToPdfTest {
|
|||||||
Base64.getEncoder()
|
Base64.getEncoder()
|
||||||
.encodeToString(attachmentContent.getBytes(StandardCharsets.UTF_8));
|
.encodeToString(attachmentContent.getBytes(StandardCharsets.UTF_8));
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
From: %s
|
From: %s
|
||||||
To: %s
|
To: %s
|
||||||
@ -840,6 +855,7 @@ class EmlToPdfTest {
|
|||||||
Base64.getEncoder()
|
Base64.getEncoder()
|
||||||
.encodeToString(attachmentEmlContent.getBytes(StandardCharsets.UTF_8));
|
.encodeToString(attachmentEmlContent.getBytes(StandardCharsets.UTF_8));
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
From: %s
|
From: %s
|
||||||
To: %s
|
To: %s
|
||||||
@ -878,6 +894,7 @@ class EmlToPdfTest {
|
|||||||
private String createMultipartAlternativeEmail(
|
private String createMultipartAlternativeEmail(
|
||||||
String textBody, String htmlBody, String boundary) {
|
String textBody, String htmlBody, String boundary) {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
From: %s
|
From: %s
|
||||||
To: %s
|
To: %s
|
||||||
@ -913,6 +930,7 @@ class EmlToPdfTest {
|
|||||||
|
|
||||||
private String createQuotedPrintableEmail() {
|
private String createQuotedPrintableEmail() {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n%s",
|
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n%s",
|
||||||
"sender@example.com",
|
"sender@example.com",
|
||||||
"recipient@example.com",
|
"recipient@example.com",
|
||||||
@ -925,6 +943,7 @@ class EmlToPdfTest {
|
|||||||
String encodedBody =
|
String encodedBody =
|
||||||
Base64.getEncoder().encodeToString(body.getBytes(StandardCharsets.UTF_8));
|
Base64.getEncoder().encodeToString(body.getBytes(StandardCharsets.UTF_8));
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: base64\n\n%s",
|
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: base64\n\n%s",
|
||||||
"sender@example.com",
|
"sender@example.com",
|
||||||
"recipient@example.com",
|
"recipient@example.com",
|
||||||
@ -936,6 +955,7 @@ class EmlToPdfTest {
|
|||||||
private String createEmailWithInlineImage(
|
private String createEmailWithInlineImage(
|
||||||
String htmlBody, String boundary, String contentId, String base64Image) {
|
String htmlBody, String boundary, String contentId, String base64Image) {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
From: %s
|
From: %s
|
||||||
To: %s
|
To: %s
|
||||||
@ -980,6 +1000,7 @@ class EmlToPdfTest {
|
|||||||
String encodedAttachment =
|
String encodedAttachment =
|
||||||
Base64.getEncoder().encodeToString(attachmentBody.getBytes(StandardCharsets.UTF_8));
|
Base64.getEncoder().encodeToString(attachmentBody.getBytes(StandardCharsets.UTF_8));
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"""
|
"""
|
||||||
From: %s
|
From: %s
|
||||||
To: %s
|
To: %s
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
@ -155,7 +156,7 @@ public class SPDFApplication {
|
|||||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||||
if (browserOpen) {
|
if (browserOpen) {
|
||||||
try {
|
try {
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
|
||||||
if (os.contains("win")) {
|
if (os.contains("win")) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@ -204,7 +205,7 @@ public class RearrangePagesPDFController {
|
|||||||
|
|
||||||
private List<Integer> processSortTypes(String sortTypes, int totalPages, String pageOrder) {
|
private List<Integer> processSortTypes(String sortTypes, int totalPages, String pageOrder) {
|
||||||
try {
|
try {
|
||||||
SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());
|
SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase(Locale.ROOT));
|
||||||
return switch (mode) {
|
return switch (mode) {
|
||||||
case REVERSE_ORDER -> reverseOrder(totalPages);
|
case REVERSE_ORDER -> reverseOrder(totalPages);
|
||||||
case DUPLEX_SORT -> duplexSort(totalPages);
|
case DUPLEX_SORT -> duplexSort(totalPages);
|
||||||
@ -247,7 +248,7 @@ public class RearrangePagesPDFController {
|
|||||||
List<Integer> newPageOrder;
|
List<Integer> newPageOrder;
|
||||||
if (sortType != null
|
if (sortType != null
|
||||||
&& !sortType.isEmpty()
|
&& !sortType.isEmpty()
|
||||||
&& !"custom".equals(sortType.toLowerCase())) {
|
&& !"custom".equals(sortType.toLowerCase(Locale.ROOT))) {
|
||||||
newPageOrder = processSortTypes(sortType, totalPages, pageOrder);
|
newPageOrder = processSortTypes(sortType, totalPages, pageOrder);
|
||||||
} else {
|
} else {
|
||||||
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
@ -242,7 +243,7 @@ public class SplitPdfByChaptersController {
|
|||||||
// split files will be named as "[FILE_NUMBER] [BOOKMARK_TITLE].pdf"
|
// split files will be named as "[FILE_NUMBER] [BOOKMARK_TITLE].pdf"
|
||||||
|
|
||||||
String fileName =
|
String fileName =
|
||||||
String.format(fileNumberFormatter, i)
|
String.format(Locale.ROOT, fileNumberFormatter, i)
|
||||||
+ bookmarks.get(i).getTitle()
|
+ bookmarks.get(i).getTitle()
|
||||||
+ ".pdf";
|
+ ".pdf";
|
||||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api.converters;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@ -45,9 +46,9 @@ public class ConvertEmlToPDF {
|
|||||||
summary = "Convert EML to PDF",
|
summary = "Convert EML to PDF",
|
||||||
description =
|
description =
|
||||||
"This endpoint converts EML (email) files to PDF format with extensive"
|
"This endpoint converts EML (email) files to PDF format with extensive"
|
||||||
+ " customization options. Features include font settings, image constraints, display modes, attachment handling,"
|
+ " customization options. Features include font settings, image"
|
||||||
+ " and HTML debug output. Input: EML file, Output: PDF"
|
+ " constraints, display modes, attachment handling, and HTML debug output."
|
||||||
+ " or HTML file. Type: SISO")
|
+ " Input: EML file, Output: PDF or HTML file. Type: SISO")
|
||||||
public ResponseEntity<byte[]> convertEmlToPdf(@ModelAttribute EmlToPdfRequest request) {
|
public ResponseEntity<byte[]> convertEmlToPdf(@ModelAttribute EmlToPdfRequest request) {
|
||||||
|
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@ -67,7 +68,7 @@ public class ConvertEmlToPDF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate file type - support EML
|
// Validate file type - support EML
|
||||||
String lowerFilename = originalFilename.toLowerCase();
|
String lowerFilename = originalFilename.toLowerCase(Locale.ROOT);
|
||||||
if (!lowerFilename.endsWith(".eml")) {
|
if (!lowerFilename.endsWith(".eml")) {
|
||||||
log.error("Invalid file type for EML to PDF: {}", originalFilename);
|
log.error("Invalid file type for EML to PDF: {}", originalFilename);
|
||||||
return ResponseEntity.badRequest()
|
return ResponseEntity.badRequest()
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@ -118,7 +119,7 @@ public class ConvertImgPDFController {
|
|||||||
newPdfBytes,
|
newPdfBytes,
|
||||||
"webp".equalsIgnoreCase(imageFormat)
|
"webp".equalsIgnoreCase(imageFormat)
|
||||||
? "png"
|
? "png"
|
||||||
: imageFormat.toUpperCase(),
|
: imageFormat.toUpperCase(Locale.ROOT),
|
||||||
colorTypeResult,
|
colorTypeResult,
|
||||||
singleImage,
|
singleImage,
|
||||||
dpi,
|
dpi,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
@ -67,7 +68,7 @@ public class ConvertOfficeController {
|
|||||||
if (extension == null || !isValidFileExtension(extension)) {
|
if (extension == null || !isValidFileExtension(extension)) {
|
||||||
throw new IllegalArgumentException("Invalid file extension");
|
throw new IllegalArgumentException("Invalid file extension");
|
||||||
}
|
}
|
||||||
String extensionLower = extension.toLowerCase();
|
String extensionLower = extension.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
String baseName = FilenameUtils.getBaseName(originalFilename);
|
String baseName = FilenameUtils.getBaseName(originalFilename);
|
||||||
if (baseName == null || baseName.isBlank()) {
|
if (baseName == null || baseName.isBlank()) {
|
||||||
@ -141,7 +142,7 @@ public class ConvertOfficeController {
|
|||||||
p ->
|
p ->
|
||||||
p.getFileName()
|
p.getFileName()
|
||||||
.toString()
|
.toString()
|
||||||
.toLowerCase()
|
.toLowerCase(Locale.ROOT)
|
||||||
.endsWith(".pdf"))
|
.endsWith(".pdf"))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
@ -124,7 +125,7 @@ public class ExtractCSVController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String generateEntryName(String baseName, int pageNum, int tableIndex) {
|
private String generateEntryName(String baseName, int pageNum, int tableIndex) {
|
||||||
return String.format("%s_p%d_t%d.csv", baseName, pageNum, tableIndex);
|
return String.format(Locale.ROOT, "%s_p%d_t%d.csv", baseName, pageNum, tableIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getBaseName(String filename) {
|
private String getBaseName(String filename) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
@ -68,7 +69,11 @@ public class BlankPageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;
|
double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;
|
||||||
log.info(String.format("Page has white pixel percent of %.2f%%", whitePixelPercentage));
|
log.info(
|
||||||
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"Page has white pixel percent of %.2f%%",
|
||||||
|
whitePixelPercentage));
|
||||||
|
|
||||||
return whitePixelPercentage >= whitePercent;
|
return whitePixelPercentage >= whitePercent;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -293,7 +293,7 @@ public class CompressController {
|
|||||||
imageIdentity,
|
imageIdentity,
|
||||||
GeneralUtils.formatBytes(originalSize),
|
GeneralUtils.formatBytes(originalSize),
|
||||||
GeneralUtils.formatBytes(compressedSize),
|
GeneralUtils.formatBytes(compressedSize),
|
||||||
String.format("%.1f", reductionPercentage));
|
String.format(Locale.ROOT, "%.1f", reductionPercentage));
|
||||||
} else {
|
} else {
|
||||||
log.info(
|
log.info(
|
||||||
"Image identity {}: Not suitable for compression, skipping", imageIdentity);
|
"Image identity {}: Not suitable for compression, skipping", imageIdentity);
|
||||||
@ -386,7 +386,7 @@ public class CompressController {
|
|||||||
"Overall PDF compression: {} → {} (reduced by {}%)",
|
"Overall PDF compression: {} → {} (reduced by {}%)",
|
||||||
GeneralUtils.formatBytes(originalFileSize),
|
GeneralUtils.formatBytes(originalFileSize),
|
||||||
GeneralUtils.formatBytes(compressedFileSize),
|
GeneralUtils.formatBytes(compressedFileSize),
|
||||||
String.format("%.1f", overallReduction));
|
String.format(Locale.ROOT, "%.1f", overallReduction));
|
||||||
return newCompressedPDF;
|
return newCompressedPDF;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
newCompressedPDF.close();
|
newCompressedPDF.close();
|
||||||
@ -481,7 +481,7 @@ public class CompressController {
|
|||||||
private static String bytesToHexString(byte[] bytes) {
|
private static String bytesToHexString(byte[] bytes) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (byte b : bytes) {
|
for (byte b : bytes) {
|
||||||
sb.append(String.format("%02x", b));
|
sb.append(String.format(Locale.ROOT, "%02x", b));
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@ -538,7 +538,7 @@ public class CompressController {
|
|||||||
"Total original image size: {}, compressed: {} (reduced by {}%)",
|
"Total original image size: {}, compressed: {} (reduced by {}%)",
|
||||||
GeneralUtils.formatBytes(stats.totalOriginalBytes),
|
GeneralUtils.formatBytes(stats.totalOriginalBytes),
|
||||||
GeneralUtils.formatBytes(stats.totalCompressedBytes),
|
GeneralUtils.formatBytes(stats.totalCompressedBytes),
|
||||||
String.format("%.1f", overallImageReduction));
|
String.format(Locale.ROOT, "%.1f", overallImageReduction));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BufferedImage convertToGrayscale(BufferedImage image) {
|
private static BufferedImage convertToGrayscale(BufferedImage image) {
|
||||||
@ -848,6 +848,7 @@ public class CompressController {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"%s_%s_%d_%dx%d_%s_%s_%d_%s_%s_%s",
|
"%s_%s_%d_%dx%d_%s_%s_%d_%s_%s_%s",
|
||||||
pixelHash.substring(0, Math.min(8, pixelHash.length())),
|
pixelHash.substring(0, Math.min(8, pixelHash.length())),
|
||||||
colorSpace,
|
colorSpace,
|
||||||
@ -911,7 +912,7 @@ public class CompressController {
|
|||||||
// Increment optimization level if we need more compression
|
// Increment optimization level if we need more compression
|
||||||
private static int incrementOptimizeLevel(int currentLevel, long currentSize, long targetSize) {
|
private static int incrementOptimizeLevel(int currentLevel, long currentSize, long targetSize) {
|
||||||
double currentRatio = currentSize / (double) targetSize;
|
double currentRatio = currentSize / (double) targetSize;
|
||||||
log.info("Current compression ratio: {}", String.format("%.2f", currentRatio));
|
log.info("Current compression ratio: {}", String.format(Locale.ROOT, "%.2f", currentRatio));
|
||||||
|
|
||||||
if (currentRatio > 2.0) {
|
if (currentRatio > 2.0) {
|
||||||
return Math.min(9, currentLevel + 3);
|
return Math.min(9, currentLevel + 3);
|
||||||
@ -1184,7 +1185,7 @@ public class CompressController {
|
|||||||
log.info(
|
log.info(
|
||||||
"Post-Ghostscript file size: {} (reduced by {}%)",
|
"Post-Ghostscript file size: {} (reduced by {}%)",
|
||||||
GeneralUtils.formatBytes(postGsSize),
|
GeneralUtils.formatBytes(postGsSize),
|
||||||
String.format("%.1f", gsReduction));
|
String.format(Locale.ROOT, "%.1f", gsReduction));
|
||||||
} else {
|
} else {
|
||||||
log.warn(
|
log.warn(
|
||||||
"Ghostscript compression failed with return code: {}",
|
"Ghostscript compression failed with return code: {}",
|
||||||
@ -1291,7 +1292,7 @@ public class CompressController {
|
|||||||
log.info(
|
log.info(
|
||||||
"Post-QPDF file size: {} (reduced by {}%)",
|
"Post-QPDF file size: {} (reduced by {}%)",
|
||||||
GeneralUtils.formatBytes(postQpdfSize),
|
GeneralUtils.formatBytes(postQpdfSize),
|
||||||
String.format("%.1f", qpdfReduction));
|
String.format(Locale.ROOT, "%.1f", qpdfReduction));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (returnCode != null && returnCode.getRc() != 3) {
|
if (returnCode != null && returnCode.getRc() != 3) {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
@ -336,7 +337,9 @@ public class OCRController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
File pageOutputPath =
|
File pageOutputPath =
|
||||||
new File(tempOutputDir, String.format("page_%d.pdf", pageNum));
|
new File(
|
||||||
|
tempOutputDir,
|
||||||
|
String.format(Locale.ROOT, "page_%d.pdf", pageNum));
|
||||||
|
|
||||||
if (shouldOcr) {
|
if (shouldOcr) {
|
||||||
// Convert page to image
|
// Convert page to image
|
||||||
@ -359,7 +362,9 @@ public class OCRController {
|
|||||||
pageNum + 1, renderDpi, e);
|
pageNum + 1, renderDpi, e);
|
||||||
}
|
}
|
||||||
File imagePath =
|
File imagePath =
|
||||||
new File(tempImagesDir, String.format("page_%d.png", pageNum));
|
new File(
|
||||||
|
tempImagesDir,
|
||||||
|
String.format(Locale.ROOT, "page_%d.png", pageNum));
|
||||||
ImageIO.write(image, "png", imagePath);
|
ImageIO.write(image, "png", imagePath);
|
||||||
|
|
||||||
// Build OCR command
|
// Build OCR command
|
||||||
@ -367,7 +372,9 @@ public class OCRController {
|
|||||||
command.add("tesseract");
|
command.add("tesseract");
|
||||||
command.add(imagePath.toString());
|
command.add(imagePath.toString());
|
||||||
command.add(
|
command.add(
|
||||||
new File(tempOutputDir, String.format("page_%d", pageNum))
|
new File(
|
||||||
|
tempOutputDir,
|
||||||
|
String.format(Locale.ROOT, "page_%d", pageNum))
|
||||||
.toString());
|
.toString());
|
||||||
command.add("-l");
|
command.add("-l");
|
||||||
command.add(String.join("+", selectedLanguages));
|
command.add(String.join("+", selectedLanguages));
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import java.awt.print.PrinterJob;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.print.PrintService;
|
import javax.print.PrintService;
|
||||||
@ -57,11 +58,14 @@ public class PrintFileController {
|
|||||||
try {
|
try {
|
||||||
// Find matching printer
|
// Find matching printer
|
||||||
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
|
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
|
||||||
|
String normalizedPrinterName = printerName.toLowerCase(Locale.ROOT);
|
||||||
PrintService selectedService =
|
PrintService selectedService =
|
||||||
Arrays.stream(services)
|
Arrays.stream(services)
|
||||||
.filter(
|
.filter(
|
||||||
service ->
|
service ->
|
||||||
service.getName().toLowerCase().contains(printerName))
|
service.getName()
|
||||||
|
.toLowerCase(Locale.ROOT)
|
||||||
|
.contains(normalizedPrinterName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(
|
.orElseThrow(
|
||||||
() ->
|
() ->
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ public class StampController {
|
|||||||
|
|
||||||
String customColor = request.getCustomColor();
|
String customColor = request.getCustomColor();
|
||||||
float marginFactor =
|
float marginFactor =
|
||||||
switch (request.getCustomMargin().toLowerCase()) {
|
switch (request.getCustomMargin().toLowerCase(Locale.ROOT)) {
|
||||||
case "small" -> 0.02f;
|
case "small" -> 0.02f;
|
||||||
case "medium" -> 0.035f;
|
case "medium" -> 0.035f;
|
||||||
case "large" -> 0.05f;
|
case "large" -> 0.05f;
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import java.time.format.DateTimeFormatter;
|
|||||||
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.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@ -203,7 +204,7 @@ public class PipelineDirectoryProcessor {
|
|||||||
? filename.substring(
|
? filename.substring(
|
||||||
filename.lastIndexOf(".")
|
filename.lastIndexOf(".")
|
||||||
+ 1)
|
+ 1)
|
||||||
.toLowerCase()
|
.toLowerCase(Locale.ROOT)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
// Check against allowed extensions
|
// Check against allowed extensions
|
||||||
@ -213,7 +214,8 @@ public class PipelineDirectoryProcessor {
|
|||||||
extension.toLowerCase());
|
extension.toLowerCase());
|
||||||
if (!isAllowed) {
|
if (!isAllowed) {
|
||||||
log.info(
|
log.info(
|
||||||
"Skipping file with unsupported extension: {} ({})",
|
"Skipping file with unsupported extension: {}"
|
||||||
|
+ " ({})",
|
||||||
filename,
|
filename,
|
||||||
extension);
|
extension);
|
||||||
}
|
}
|
||||||
@ -226,7 +228,8 @@ public class PipelineDirectoryProcessor {
|
|||||||
fileMonitor.isFileReadyForProcessing(path);
|
fileMonitor.isFileReadyForProcessing(path);
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
log.info(
|
log.info(
|
||||||
"File not ready for processing (locked/created last 5s): {}",
|
"File not ready for processing (locked/created"
|
||||||
|
+ " last 5s): {}",
|
||||||
path);
|
path);
|
||||||
}
|
}
|
||||||
return isReady;
|
return isReady;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
@ -121,7 +122,9 @@ public class PipelineProcessor {
|
|||||||
boolean hasInputFileType = false;
|
boolean hasInputFileType = false;
|
||||||
for (String extension : inputFileTypes) {
|
for (String extension : inputFileTypes) {
|
||||||
if ("ALL".equals(extension)
|
if ("ALL".equals(extension)
|
||||||
|| file.getFilename().toLowerCase().endsWith(extension)) {
|
|| file.getFilename()
|
||||||
|
.toLowerCase(Locale.ROOT)
|
||||||
|
.endsWith(extension)) {
|
||||||
hasInputFileType = true;
|
hasInputFileType = true;
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
body.add("fileInput", file);
|
body.add("fileInput", file);
|
||||||
@ -159,7 +162,8 @@ public class PipelineProcessor {
|
|||||||
String providedExtension = "no extension";
|
String providedExtension = "no extension";
|
||||||
if (filename != null && filename.contains(".")) {
|
if (filename != null && filename.contains(".")) {
|
||||||
providedExtension =
|
providedExtension =
|
||||||
filename.substring(filename.lastIndexOf(".")).toLowerCase();
|
filename.substring(filename.lastIndexOf("."))
|
||||||
|
.toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
logPrintStream.println(
|
logPrintStream.println(
|
||||||
@ -187,7 +191,10 @@ public class PipelineProcessor {
|
|||||||
file ->
|
file ->
|
||||||
finalinputFileTypes.stream()
|
finalinputFileTypes.stream()
|
||||||
.anyMatch(
|
.anyMatch(
|
||||||
file.getFilename().toLowerCase()
|
file.getFilename()
|
||||||
|
.toLowerCase(
|
||||||
|
Locale
|
||||||
|
.ROOT)
|
||||||
::endsWith))
|
::endsWith))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -228,7 +235,7 @@ public class PipelineProcessor {
|
|||||||
if (filename != null && filename.contains(".")) {
|
if (filename != null && filename.contains(".")) {
|
||||||
return filename.substring(
|
return filename.substring(
|
||||||
filename.lastIndexOf("."))
|
filename.lastIndexOf("."))
|
||||||
.toLowerCase();
|
.toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
return "no extension";
|
return "no extension";
|
||||||
})
|
})
|
||||||
|
|||||||
@ -410,12 +410,12 @@ public class GetInfoOnPDF {
|
|||||||
float widthInCm = widthInInches * 2.54f;
|
float widthInCm = widthInInches * 2.54f;
|
||||||
float heightInCm = heightInInches * 2.54f;
|
float heightInCm = heightInInches * 2.54f;
|
||||||
|
|
||||||
dimensionInfo.put("Width (px)", String.format("%.2f", width));
|
dimensionInfo.put("Width (px)", String.format(Locale.ROOT, "%.2f", width));
|
||||||
dimensionInfo.put("Height (px)", String.format("%.2f", height));
|
dimensionInfo.put("Height (px)", String.format(Locale.ROOT, "%.2f", height));
|
||||||
dimensionInfo.put("Width (in)", String.format("%.2f", widthInInches));
|
dimensionInfo.put("Width (in)", String.format(Locale.ROOT, "%.2f", widthInInches));
|
||||||
dimensionInfo.put("Height (in)", String.format("%.2f", heightInInches));
|
dimensionInfo.put("Height (in)", String.format(Locale.ROOT, "%.2f", heightInInches));
|
||||||
dimensionInfo.put("Width (cm)", String.format("%.2f", widthInCm));
|
dimensionInfo.put("Width (cm)", String.format(Locale.ROOT, "%.2f", widthInCm));
|
||||||
dimensionInfo.put("Height (cm)", String.format("%.2f", heightInCm));
|
dimensionInfo.put("Height (cm)", String.format(Locale.ROOT, "%.2f", heightInCm));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ArrayNode exploreStructureTree(List<Object> nodes) {
|
private static ArrayNode exploreStructureTree(List<Object> nodes) {
|
||||||
|
|||||||
@ -361,7 +361,7 @@ public class MetricsController {
|
|||||||
long hours = duration.toHoursPart();
|
long hours = duration.toHoursPart();
|
||||||
long minutes = duration.toMinutesPart();
|
long minutes = duration.toMinutesPart();
|
||||||
long seconds = duration.toSecondsPart();
|
long seconds = duration.toSecondsPart();
|
||||||
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
return String.format(Locale.ROOT, "%dd %dh %dm %ds", days, hours, minutes, seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
|
|||||||
@ -65,6 +65,6 @@ public class UploadLimitService {
|
|||||||
if (bytes < 1024) return bytes + " B";
|
if (bytes < 1024) return bytes + " B";
|
||||||
int exp = (int) (Math.log(bytes) / Math.log(1024));
|
int exp = (int) (Math.log(bytes) / Math.log(1024));
|
||||||
String pre = "KMGTPE".charAt(exp - 1) + "B";
|
String pre = "KMGTPE".charAt(exp - 1) + "B";
|
||||||
return String.format(Locale.US, "%.1f %s", bytes / Math.pow(1024, exp), pre);
|
return String.format(Locale.ROOT, "%.1f %s", bytes / Math.pow(1024, exp), pre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package stirling.software.SPDF.pdf;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -204,12 +205,14 @@ public class TextFinder extends PDFTextStripper {
|
|||||||
TextPosition pos = i < pageTextPositions.size() ? pageTextPositions.get(i) : null;
|
TextPosition pos = i < pageTextPositions.size() ? pageTextPositions.get(i) : null;
|
||||||
debug.append(
|
debug.append(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
" [%d] '%c' (0x%02X) -> %s\n",
|
" [%d] '%c' (0x%02X) -> %s\n",
|
||||||
i,
|
i,
|
||||||
c,
|
c,
|
||||||
(int) c,
|
(int) c,
|
||||||
pos != null
|
pos != null
|
||||||
? String.format("(%.1f,%.1f)", pos.getX(), pos.getY())
|
? String.format(
|
||||||
|
Locale.ROOT, "(%.1f,%.1f)", pos.getX(), pos.getY())
|
||||||
: "null"));
|
: "null"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package stirling.software.SPDF.service;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ public class ApiDocService {
|
|||||||
: RegexPatternUtils.getInstance().getApiDocInputTypePattern())
|
: RegexPatternUtils.getInstance().getApiDocInputTypePattern())
|
||||||
.matcher(description);
|
.matcher(description);
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String type = matcher.group(1).toUpperCase();
|
String type = matcher.group(1).toUpperCase(Locale.ROOT);
|
||||||
if (outputToFileTypes.containsKey(type)) {
|
if (outputToFileTypes.containsKey(type)) {
|
||||||
return outputToFileTypes.get(type);
|
return outputToFileTypes.get(type);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.service;
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@ -70,7 +71,10 @@ public class MetricsAggregatorService {
|
|||||||
|
|
||||||
String key =
|
String key =
|
||||||
String.format(
|
String.format(
|
||||||
"http_requests_%s_%s", method, uri.replace("/", "_"));
|
Locale.ROOT,
|
||||||
|
"http_requests_%s_%s",
|
||||||
|
method,
|
||||||
|
uri.replace("/", "_"));
|
||||||
double currentCount = counter.count();
|
double currentCount = counter.count();
|
||||||
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
|
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
|
||||||
double difference = currentCount - lastCount;
|
double difference = currentCount - lastCount;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -92,7 +93,7 @@ public class SignatureService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isImageFile(Path path) {
|
private boolean isImageFile(Path path) {
|
||||||
String fileName = path.getFileName().toString().toLowerCase();
|
String fileName = path.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||||
return fileName.endsWith(".jpg")
|
return fileName.endsWith(".jpg")
|
||||||
|| fileName.endsWith(".jpeg")
|
|| fileName.endsWith(".jpeg")
|
||||||
|| fileName.endsWith(".png")
|
|| fileName.endsWith(".png")
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.utils.text;
|
package stirling.software.SPDF.utils.text;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDSimpleFont;
|
import org.apache.pdfbox.pdmodel.font.PDSimpleFont;
|
||||||
@ -24,7 +25,8 @@ public class TextEncodingHelper {
|
|||||||
byte[] encoded = font.encode(text);
|
byte[] encoded = font.encode(text);
|
||||||
if (encoded.length > 0) {
|
if (encoded.length > 0) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"Text '{}' has good full-string encoding for font {} - permissively allowing",
|
"Text '{}' has good full-string encoding for font {} - permissively"
|
||||||
|
+ " allowing",
|
||||||
text,
|
text,
|
||||||
font.getName() != null ? font.getName() : "Unknown");
|
font.getName() != null ? font.getName() : "Unknown");
|
||||||
return true;
|
return true;
|
||||||
@ -61,6 +63,7 @@ public class TextEncodingHelper {
|
|||||||
for (int i = 0; i < text.length(); ) {
|
for (int i = 0; i < text.length(); ) {
|
||||||
int codePoint = text.codePointAt(i);
|
int codePoint = text.codePointAt(i);
|
||||||
String charStr = new String(Character.toChars(codePoint));
|
String charStr = new String(Character.toChars(codePoint));
|
||||||
|
String hex = String.format(Locale.ROOT, "%04X", codePoint); // U+%04X
|
||||||
totalCodePoints++;
|
totalCodePoints++;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -71,28 +74,23 @@ public class TextEncodingHelper {
|
|||||||
|
|
||||||
if (charWidth >= 0) {
|
if (charWidth >= 0) {
|
||||||
successfulCodePoints++;
|
successfulCodePoints++;
|
||||||
log.debug(
|
log.debug("Code point '{}' (U+{}) encoded successfully", charStr, hex);
|
||||||
"Code point '{}' (U+{}) encoded successfully",
|
|
||||||
charStr,
|
|
||||||
Integer.toHexString(codePoint).toUpperCase());
|
|
||||||
} else {
|
} else {
|
||||||
log.debug(
|
log.debug(
|
||||||
"Code point '{}' (U+{}) has invalid width: {}",
|
"Code point '{}' (U+{}) has invalid width: {}",
|
||||||
charStr,
|
charStr,
|
||||||
Integer.toHexString(codePoint).toUpperCase(),
|
hex,
|
||||||
charWidth);
|
charWidth);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug(
|
log.debug(
|
||||||
"Code point '{}' (U+{}) encoding failed - empty result",
|
"Code point '{}' (U+{}) encoding failed - empty result", charStr, hex);
|
||||||
charStr,
|
|
||||||
Integer.toHexString(codePoint).toUpperCase());
|
|
||||||
}
|
}
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
} catch (IOException | IllegalArgumentException e) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"Code point '{}' (U+{}) validation failed: {}",
|
"Code point '{}' (U+{}) validation failed: {}",
|
||||||
charStr,
|
charStr,
|
||||||
Integer.toHexString(codePoint).toUpperCase(),
|
hex,
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,16 +98,20 @@ public class TextEncodingHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double successRate =
|
double successRate =
|
||||||
totalCodePoints > 0 ? (double) successfulCodePoints / totalCodePoints : 0;
|
totalCodePoints > 0 ? (double) successfulCodePoints / totalCodePoints : 0.0;
|
||||||
|
String pct =
|
||||||
|
String.format(
|
||||||
|
Locale.ROOT, "%.1f%%", successRate * 100); // Pre-formatting percentage!
|
||||||
|
|
||||||
boolean isAcceptable = successRate >= 0.95;
|
boolean isAcceptable = successRate >= 0.95;
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"Array validation for '{}': {}/{} code points successful ({:.1f}%) - {}",
|
"Array validation for '{}': {}/{} code points successful ({}) - {}",
|
||||||
text,
|
text,
|
||||||
successfulCodePoints,
|
successfulCodePoints,
|
||||||
totalCodePoints,
|
totalCodePoints,
|
||||||
successRate * 100,
|
pct,
|
||||||
isAcceptable ? "ALLOWING" : "rejecting");
|
(isAcceptable ? "ALLOWING" : "rejecting"));
|
||||||
|
|
||||||
return isAcceptable;
|
return isAcceptable;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@ -49,13 +50,16 @@ class TextFinderTest {
|
|||||||
expectedCount,
|
expectedCount,
|
||||||
foundTexts.size(),
|
foundTexts.size(),
|
||||||
String.format(
|
String.format(
|
||||||
"Expected %d matches for search term '%s'", expectedCount, searchTerm));
|
Locale.ROOT,
|
||||||
|
"Expected %d matches for search term '%s'",
|
||||||
|
expectedCount,
|
||||||
|
searchTerm));
|
||||||
|
|
||||||
if (expectedTexts != null) {
|
if (expectedTexts != null) {
|
||||||
for (String expectedText : expectedTexts) {
|
for (String expectedText : expectedTexts) {
|
||||||
assertTrue(
|
assertTrue(
|
||||||
foundTexts.stream().anyMatch(text -> text.getText().equals(expectedText)),
|
foundTexts.stream().anyMatch(text -> text.getText().equals(expectedText)),
|
||||||
String.format("Expected to find text: '%s'", expectedText));
|
String.format(Locale.ROOT, "Expected to find text: '%s'", expectedText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +275,10 @@ class TextFinderTest {
|
|||||||
// Each pattern should find at least one match in our test content
|
// Each pattern should find at least one match in our test content
|
||||||
assertFalse(
|
assertFalse(
|
||||||
foundTexts.isEmpty(),
|
foundTexts.isEmpty(),
|
||||||
String.format("Pattern '%s' should find at least one match", regexPattern));
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"Pattern '%s' should find at least one match",
|
||||||
|
regexPattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.time.Instant;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
@ -307,8 +308,8 @@ public class AuditUtils {
|
|||||||
|
|
||||||
// For HTTP methods, infer based on controller and path
|
// For HTTP methods, infer based on controller and path
|
||||||
if (httpMethod != null && path != null) {
|
if (httpMethod != null && path != null) {
|
||||||
String cls = controller.getSimpleName().toLowerCase();
|
String cls = controller.getSimpleName().toLowerCase(Locale.ROOT);
|
||||||
String pkg = controller.getPackage().getName().toLowerCase();
|
String pkg = controller.getPackage().getName().toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
if ("GET".equals(httpMethod)) return AuditEventType.HTTP_REQUEST;
|
if ("GET".equals(httpMethod)) return AuditEventType.HTTP_REQUEST;
|
||||||
|
|
||||||
@ -374,8 +375,8 @@ public class AuditUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise infer from controller and path
|
// Otherwise infer from controller and path
|
||||||
String cls = controller.getSimpleName().toLowerCase();
|
String cls = controller.getSimpleName().toLowerCase(Locale.ROOT);
|
||||||
String pkg = controller.getPackage().getName().toLowerCase();
|
String pkg = controller.getPackage().getName().toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
if ("GET".equals(httpMethod)) return AuditEventType.HTTP_REQUEST;
|
if ("GET".equals(httpMethod)) return AuditEventType.HTTP_REQUEST;
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.security.cert.X509Certificate;
|
|||||||
import java.security.interfaces.RSAPrivateKey;
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
@ -145,7 +146,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
registrationId = oAuthToken.getAuthorizedClientRegistrationId();
|
registrationId = oAuthToken.getAuthorizedClientRegistrationId();
|
||||||
|
|
||||||
// Redirect based on OAuth2 provider
|
// Redirect based on OAuth2 provider
|
||||||
switch (registrationId.toLowerCase()) {
|
switch (registrationId.toLowerCase(Locale.ROOT)) {
|
||||||
case "keycloak" -> {
|
case "keycloak" -> {
|
||||||
KeycloakProvider keycloak = oauth.getClient().getKeycloak();
|
KeycloakProvider keycloak = oauth.getClient().getKeycloak();
|
||||||
|
|
||||||
|
|||||||
@ -142,7 +142,7 @@ public class InitialSecuritySetup {
|
|||||||
if (internalApiUserOpt.isPresent()) {
|
if (internalApiUserOpt.isPresent()) {
|
||||||
User internalApiUser = internalApiUserOpt.get();
|
User internalApiUser = internalApiUserOpt.get();
|
||||||
// move to team internal API user
|
// move to team internal API user
|
||||||
if (!internalApiUser.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
|
if (!TeamService.INTERNAL_TEAM_NAME.equals(internalApiUser.getTeam().getName())) {
|
||||||
log.info(
|
log.info(
|
||||||
"Moving internal API user to team: {}", TeamService.INTERNAL_TEAM_NAME);
|
"Moving internal API user to team: {}", TeamService.INTERNAL_TEAM_NAME);
|
||||||
Team internalTeam = teamService.getOrCreateInternalTeam();
|
Team internalTeam = teamService.getOrCreateInternalTeam();
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -89,7 +90,8 @@ public class AccountWebController {
|
|||||||
if (oauth.isSettingsValid()) {
|
if (oauth.isSettingsValid()) {
|
||||||
String firstChar = String.valueOf(oauth.getProvider().charAt(0));
|
String firstChar = String.valueOf(oauth.getProvider().charAt(0));
|
||||||
String clientName =
|
String clientName =
|
||||||
oauth.getProvider().replaceFirst(firstChar, firstChar.toUpperCase());
|
oauth.getProvider()
|
||||||
|
.replaceFirst(firstChar, firstChar.toUpperCase(Locale.ROOT));
|
||||||
providerList.put(OAUTH_2_AUTHORIZATION + oauth.getProvider(), clientName);
|
providerList.put(OAUTH_2_AUTHORIZATION + oauth.getProvider(), clientName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.proprietary.security.configuration;
|
package stirling.software.proprietary.security.configuration;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@ -132,7 +134,7 @@ public class DatabaseConfig {
|
|||||||
private String getDriverClassName(String driverName) throws UnsupportedProviderException {
|
private String getDriverClassName(String driverName) throws UnsupportedProviderException {
|
||||||
try {
|
try {
|
||||||
ApplicationProperties.Driver driver =
|
ApplicationProperties.Driver driver =
|
||||||
ApplicationProperties.Driver.valueOf(driverName.toUpperCase());
|
ApplicationProperties.Driver.valueOf(driverName.toUpperCase(Locale.ROOT));
|
||||||
|
|
||||||
return switch (driver) {
|
return switch (driver) {
|
||||||
case H2 -> {
|
case H2 -> {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.net.http.HttpClient;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
|
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
|
||||||
import org.bouncycastle.crypto.signers.Ed25519Signer;
|
import org.bouncycastle.crypto.signers.Ed25519Signer;
|
||||||
@ -185,7 +186,7 @@ public class KeygenLicenseVerifier {
|
|||||||
byte[] signatureBytes = Base64.getDecoder().decode(encodedSignature);
|
byte[] signatureBytes = Base64.getDecoder().decode(encodedSignature);
|
||||||
|
|
||||||
// Create the signing data format - prefix with "license/"
|
// Create the signing data format - prefix with "license/"
|
||||||
String signingData = String.format("license/%s", encryptedData);
|
String signingData = String.format(Locale.ROOT, "license/%s", encryptedData);
|
||||||
byte[] signingDataBytes = signingData.getBytes();
|
byte[] signingDataBytes = signingData.getBytes();
|
||||||
|
|
||||||
log.info("Signing data length: {} bytes", signingDataBytes.length);
|
log.info("Signing data length: {} bytes", signingDataBytes.length);
|
||||||
@ -335,7 +336,7 @@ public class KeygenLicenseVerifier {
|
|||||||
.decode(encodedSignature.replace('-', '+').replace('_', '/'));
|
.decode(encodedSignature.replace('-', '+').replace('_', '/'));
|
||||||
|
|
||||||
// For ED25519_SIGN format, the signing data is "key/" + encodedPayload
|
// For ED25519_SIGN format, the signing data is "key/" + encodedPayload
|
||||||
String signingData = String.format("key/%s", encodedPayload);
|
String signingData = String.format(Locale.ROOT, "key/%s", encodedPayload);
|
||||||
byte[] dataBytes = signingData.getBytes();
|
byte[] dataBytes = signingData.getBytes();
|
||||||
|
|
||||||
byte[] publicKeyBytes = Hex.decode(PUBLIC_KEY);
|
byte[] publicKeyBytes = Hex.decode(PUBLIC_KEY);
|
||||||
@ -510,8 +511,10 @@ public class KeygenLicenseVerifier {
|
|||||||
String licenseKey, String machineFingerprint, LicenseContext context) throws Exception {
|
String licenseKey, String machineFingerprint, LicenseContext context) throws Exception {
|
||||||
String requestBody =
|
String requestBody =
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
|
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
|
||||||
licenseKey, machineFingerprint);
|
licenseKey,
|
||||||
|
machineFingerprint);
|
||||||
HttpRequest request =
|
HttpRequest request =
|
||||||
HttpRequest.newBuilder()
|
HttpRequest.newBuilder()
|
||||||
.uri(
|
.uri(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import java.io.IOException;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@ -185,6 +186,7 @@ public class AdminSettingsController {
|
|||||||
|
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"Successfully updated %d setting(s). Changes will take effect on"
|
"Successfully updated %d setting(s). Changes will take effect on"
|
||||||
+ " application restart.",
|
+ " application restart.",
|
||||||
updatedCount));
|
updatedCount));
|
||||||
@ -297,9 +299,11 @@ public class AdminSettingsController {
|
|||||||
String escapedSectionName = HtmlUtils.htmlEscape(sectionName);
|
String escapedSectionName = HtmlUtils.htmlEscape(sectionName);
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"Successfully updated %d setting(s) in section '%s'. Changes will take"
|
"Successfully updated %d setting(s) in section '%s'. Changes will take"
|
||||||
+ " effect on application restart.",
|
+ " effect on application restart.",
|
||||||
updatedCount, escapedSectionName));
|
updatedCount,
|
||||||
|
escapedSectionName));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Failed to save section settings to file: {}", e.getMessage(), e);
|
log.error("Failed to save section settings to file: {}", e.getMessage(), e);
|
||||||
@ -388,6 +392,7 @@ public class AdminSettingsController {
|
|||||||
String escapedKey = HtmlUtils.htmlEscape(key);
|
String escapedKey = HtmlUtils.htmlEscape(key);
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(
|
||||||
String.format(
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
"Successfully updated setting '%s'. Changes will take effect on"
|
"Successfully updated setting '%s'. Changes will take effect on"
|
||||||
+ " application restart.",
|
+ " application restart.",
|
||||||
escapedKey));
|
escapedKey));
|
||||||
@ -410,7 +415,7 @@ public class AdminSettingsController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return switch (sectionName.toLowerCase()) {
|
return switch (sectionName.toLowerCase(Locale.ROOT)) {
|
||||||
case "security" -> applicationProperties.getSecurity();
|
case "security" -> applicationProperties.getSecurity();
|
||||||
case "system" -> applicationProperties.getSystem();
|
case "system" -> applicationProperties.getSystem();
|
||||||
case "ui" -> applicationProperties.getUi();
|
case "ui" -> applicationProperties.getUi();
|
||||||
@ -473,7 +478,7 @@ public class AdminSettingsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure first part is a valid section name
|
// Ensure first part is a valid section name
|
||||||
if (parts.length > 0 && !VALID_SECTION_NAMES.contains(parts[0].toLowerCase())) {
|
if (parts.length > 0 && !VALID_SECTION_NAMES.contains(parts[0].toLowerCase(Locale.ROOT))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,8 +583,8 @@ public class AdminSettingsController {
|
|||||||
|
|
||||||
/** Check if a field name indicates sensitive data with full path context */
|
/** Check if a field name indicates sensitive data with full path context */
|
||||||
private boolean isSensitiveFieldWithPath(String fieldName, String fullPath) {
|
private boolean isSensitiveFieldWithPath(String fieldName, String fullPath) {
|
||||||
String lowerField = fieldName.toLowerCase();
|
String lowerField = fieldName.toLowerCase(Locale.ROOT);
|
||||||
String lowerPath = fullPath.toLowerCase();
|
String lowerPath = fullPath.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
// Don't mask premium.key specifically
|
// Don't mask premium.key specifically
|
||||||
if ("key".equals(lowerField) && "premium.key".equals(lowerPath)) {
|
if ("key".equals(lowerField) && "premium.key".equals(lowerPath)) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package stirling.software.proprietary.security.model;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -84,7 +85,7 @@ public class User implements UserDetails, Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticationType(AuthenticationType authenticationType) {
|
public void setAuthenticationType(AuthenticationType authenticationType) {
|
||||||
this.authenticationType = authenticationType.toString().toLowerCase();
|
this.authenticationType = authenticationType.toString().toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addAuthorities(Set<Authority> authorities) {
|
public void addAuthorities(Set<Authority> authorities) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import static stirling.software.common.util.ValidationUtils.isStringEmpty;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -177,7 +178,7 @@ public class OAuth2Configuration {
|
|||||||
|
|
||||||
String name = oauth.getProvider();
|
String name = oauth.getProvider();
|
||||||
String firstChar = String.valueOf(name.charAt(0));
|
String firstChar = String.valueOf(name.charAt(0));
|
||||||
String clientName = name.replaceFirst(firstChar, firstChar.toUpperCase());
|
String clientName = name.replaceFirst(firstChar, firstChar.toUpperCase(Locale.ROOT));
|
||||||
|
|
||||||
Provider oidcProvider =
|
Provider oidcProvider =
|
||||||
new Provider(
|
new Provider(
|
||||||
@ -187,7 +188,8 @@ public class OAuth2Configuration {
|
|||||||
oauth.getClientId(),
|
oauth.getClientId(),
|
||||||
oauth.getClientSecret(),
|
oauth.getClientSecret(),
|
||||||
oauth.getScopes(),
|
oauth.getScopes(),
|
||||||
UsernameAttribute.valueOf(oauth.getUseAsUsername().toUpperCase()),
|
UsernameAttribute.valueOf(
|
||||||
|
oauth.getUseAsUsername().toUpperCase(Locale.ROOT)),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null);
|
null);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package stirling.software.proprietary.security.service;
|
package stirling.software.proprietary.security.service;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
@ -44,7 +45,7 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
OidcUser user = delegate.loadUser(userRequest);
|
OidcUser user = delegate.loadUser(userRequest);
|
||||||
OAUTH2 oauth2 = securityProperties.getOauth2();
|
OAUTH2 oauth2 = securityProperties.getOauth2();
|
||||||
UsernameAttribute usernameAttribute =
|
UsernameAttribute usernameAttribute =
|
||||||
UsernameAttribute.valueOf(oauth2.getUseAsUsername().toUpperCase());
|
UsernameAttribute.valueOf(oauth2.getUseAsUsername().toUpperCase(Locale.ROOT));
|
||||||
String usernameAttributeKey = usernameAttribute.getName();
|
String usernameAttributeKey = usernameAttribute.getName();
|
||||||
|
|
||||||
// todo: save user by OIDC ID instead of username
|
// todo: save user by OIDC ID instead of username
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.proprietary.security.service;
|
package stirling.software.proprietary.security.service;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
@ -59,7 +61,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationType userAuthenticationType =
|
AuthenticationType userAuthenticationType =
|
||||||
AuthenticationType.valueOf(authTypeStr.toUpperCase());
|
AuthenticationType.valueOf(authTypeStr.toUpperCase(Locale.ROOT));
|
||||||
if (!user.hasPassword() && userAuthenticationType == AuthenticationType.WEB) {
|
if (!user.hasPassword() && userAuthenticationType == AuthenticationType.WEB) {
|
||||||
throw new IllegalArgumentException("Password must not be null");
|
throw new IllegalArgumentException("Password must not be null");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package stirling.software.proprietary.security.service;
|
package stirling.software.proprietary.security.service;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -45,17 +46,19 @@ public class LoginAttemptService {
|
|||||||
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attemptsCache.remove(key.toLowerCase());
|
String normalizedKey = key.toLowerCase(Locale.ROOT);
|
||||||
|
attemptsCache.remove(normalizedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loginFailed(String key) {
|
public void loginFailed(String key) {
|
||||||
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
|
String normalizedKey = key.toLowerCase(Locale.ROOT);
|
||||||
|
AttemptCounter attemptCounter = attemptsCache.get(normalizedKey);
|
||||||
if (attemptCounter == null) {
|
if (attemptCounter == null) {
|
||||||
attemptCounter = new AttemptCounter();
|
attemptCounter = new AttemptCounter();
|
||||||
attemptsCache.put(key.toLowerCase(), attemptCounter);
|
attemptsCache.put(normalizedKey, attemptCounter);
|
||||||
} else {
|
} else {
|
||||||
if (attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) {
|
if (attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) {
|
||||||
attemptCounter.reset();
|
attemptCounter.reset();
|
||||||
@ -68,7 +71,8 @@ public class LoginAttemptService {
|
|||||||
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
|
String normalizedKey = key.toLowerCase(Locale.ROOT);
|
||||||
|
AttemptCounter attemptCounter = attemptsCache.get(normalizedKey);
|
||||||
if (attemptCounter == null) {
|
if (attemptCounter == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -80,7 +84,8 @@ public class LoginAttemptService {
|
|||||||
// Arbitrarily high number if tracking is disabled
|
// Arbitrarily high number if tracking is disabled
|
||||||
return Integer.MAX_VALUE;
|
return Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
|
String normalizedKey = key.toLowerCase(Locale.ROOT);
|
||||||
|
AttemptCounter attemptCounter = attemptsCache.get(normalizedKey);
|
||||||
if (attemptCounter == null) {
|
if (attemptCounter == null) {
|
||||||
return MAX_ATTEMPT;
|
return MAX_ATTEMPT;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -118,6 +119,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
return addApiKeyToUser(username);
|
return addApiKeyToUser(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getApiKeyForUser(String username) {
|
public String getApiKeyForUser(String username) {
|
||||||
User user =
|
User user =
|
||||||
findByUsernameIgnoreCase(username)
|
findByUsernameIgnoreCase(username)
|
||||||
@ -495,9 +497,10 @@ public class UserService implements UserServiceInterface {
|
|||||||
.matches();
|
.matches();
|
||||||
|
|
||||||
List<String> notAllowedUserList = new ArrayList<>();
|
List<String> notAllowedUserList = new ArrayList<>();
|
||||||
notAllowedUserList.add("ALL_USERS".toLowerCase());
|
notAllowedUserList.add("ALL_USERS".toLowerCase(Locale.ROOT));
|
||||||
notAllowedUserList.add("anonymoususer");
|
notAllowedUserList.add("anonymoususer");
|
||||||
boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());
|
String normalizedUsername = username.toLowerCase(Locale.ROOT);
|
||||||
|
boolean notAllowedUser = notAllowedUserList.contains(normalizedUsername);
|
||||||
return (isValidSimpleUsername || isValidEmail) && !notAllowedUser;
|
return (isValidSimpleUsername || isValidEmail) && !notAllowedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,6 +548,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getCurrentUsername() {
|
public String getCurrentUsername() {
|
||||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
|
||||||
@ -601,6 +605,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public long getTotalUsersCount() {
|
public long getTotalUsersCount() {
|
||||||
// Count all users in the database
|
// Count all users in the database
|
||||||
long userCount = userRepository.count();
|
long userCount = userRepository.count();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user