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:
Ludy 2025-11-04 11:30:51 +01:00 committed by GitHub
parent 7f801157c8
commit 6e82f124a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 478 additions and 348 deletions

View File

@ -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

View File

@ -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();

View File

@ -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);
} }

View File

@ -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);

View File

@ -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);

View File

@ -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);
} }

View File

@ -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) {

View File

@ -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);

View File

@ -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")) {

View File

@ -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);
} }

View File

@ -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)

View File

@ -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();
} }

View File

@ -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();

View File

@ -109,12 +109,13 @@ 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">
<title>%s</title> <title>%s</title>
<style> <style>
""", """,
sanitizeText(content.getSubject(), customHtmlSanitizer))); sanitizeText(content.getSubject(), customHtmlSanitizer)));
appendEnhancedStyles(html); appendEnhancedStyles(html);
@ -127,14 +128,15 @@ 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">
<h1>%s</h1> <h1>%s</h1>
<div class="email-meta"> <div class="email-meta">
<div><strong>From:</strong> %s</div> <div><strong>From:</strong> %s</div>
<div><strong>To:</strong> %s</div> <div><strong>To:</strong> %s</div>
""", """,
sanitizeText(content.getSubject(), customHtmlSanitizer), sanitizeText(content.getSubject(), customHtmlSanitizer),
sanitizeText(content.getFrom(), customHtmlSanitizer), sanitizeText(content.getFrom(), customHtmlSanitizer),
sanitizeText(content.getTo(), customHtmlSanitizer))); sanitizeText(content.getTo(), customHtmlSanitizer)));
@ -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,127 +258,128 @@ 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;
font-size: %dpx; font-size: %dpx;
line-height: %s; line-height: %s;
color: %s; color: %s;
margin: 0; margin: 0;
padding: 16px; padding: 16px;
background-color: %s; background-color: %s;
} }
.email-container { .email-container {
width: 100%%; width: 100%%;
max-width: 100%%; max-width: 100%%;
margin: 0 auto; margin: 0 auto;
} }
.email-header { .email-header {
padding-bottom: 10px; padding-bottom: 10px;
border-bottom: 1px solid %s; border-bottom: 1px solid %s;
margin-bottom: 10px; margin-bottom: 10px;
} }
.email-header h1 { .email-header h1 {
margin: 0 0 10px 0; margin: 0 0 10px 0;
font-size: %dpx; font-size: %dpx;
font-weight: bold; font-weight: bold;
} }
.email-meta div { .email-meta div {
margin-bottom: 2px; margin-bottom: 2px;
font-size: %dpx; font-size: %dpx;
} }
.email-body { .email-body {
word-wrap: break-word; word-wrap: break-word;
} }
.attachment-section { .attachment-section {
margin-top: 15px; margin-top: 15px;
padding: 10px; padding: 10px;
background-color: %s; background-color: %s;
border: 1px solid %s; border: 1px solid %s;
border-radius: 3px; border-radius: 3px;
} }
.attachment-section h3 { .attachment-section h3 {
margin: 0 0 8px 0; margin: 0 0 8px 0;
font-size: %dpx; font-size: %dpx;
} }
.attachment-item { .attachment-item {
padding: 5px 0; padding: 5px 0;
} }
.attachment-icon { .attachment-icon {
margin-right: 5px; margin-right: 5px;
} }
.attachment-details, .attachment-type { .attachment-details, .attachment-type {
font-size: %dpx; font-size: %dpx;
color: #555555; color: #555555;
} }
.attachment-inclusion-note, .attachment-info-note { .attachment-inclusion-note, .attachment-info-note {
margin-top: 8px; margin-top: 8px;
padding: 6px; padding: 6px;
font-size: %dpx; font-size: %dpx;
border-radius: 3px; border-radius: 3px;
} }
.attachment-inclusion-note { .attachment-inclusion-note {
background-color: #e6ffed; background-color: #e6ffed;
border: 1px solid #d4f7dc; border: 1px solid #d4f7dc;
color: #006420; color: #006420;
} }
.attachment-info-note { .attachment-info-note {
background-color: #fff9e6; background-color: #fff9e6;
border: 1px solid #fff0c2; border: 1px solid #fff0c2;
color: #664d00; color: #664d00;
} }
.attachment-link-container { .attachment-link-container {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 8px; padding: 8px;
background-color: #f8f9fa; background-color: #f8f9fa;
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
border-radius: 4px; border-radius: 4px;
margin: 4px 0; margin: 4px 0;
} }
.attachment-link-container:hover { .attachment-link-container:hover {
background-color: #e9ecef; background-color: #e9ecef;
} }
.attachment-note { .attachment-note {
font-size: %dpx; font-size: %dpx;
color: #6c757d; color: #6c757d;
font-style: italic; font-style: italic;
margin-left: 8px; margin-left: 8px;
} }
.no-content { .no-content {
padding: 20px; padding: 20px;
text-align: center; text-align: center;
color: #666; color: #666;
font-style: italic; font-style: italic;
} }
.text-body { .text-body {
white-space: pre-wrap; white-space: pre-wrap;
} }
img { img {
max-width: 100%%; max-width: 100%%;
height: auto; height: auto;
display: block; display: block;
} }
""", """,
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE,
DEFAULT_LINE_HEIGHT, DEFAULT_LINE_HEIGHT,
@ -420,13 +430,14 @@ 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>
<span class="attachment-name">%s</span> <span class="attachment-name">%s</span>
<span class="attachment-details">(%s%s)</span> <span class="attachment-details">(%s%s)</span>
</div> </div>
""", """,
attachmentId, attachmentId,
escapeHtml(embeddedFilename), escapeHtml(embeddedFilename),
escapeHtml(EmlParser.safeMimeDecode(attachment.getFilename())), escapeHtml(EmlParser.safeMimeDecode(attachment.getFilename())),
@ -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 {

View File

@ -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);
} }

View File

@ -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 =

View File

@ -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));
} }
} }
} }

View File

@ -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

View File

@ -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;

View File

@ -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);
} }
} }

View File

@ -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);
} }
} }

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")) {

View File

@ -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);

View File

@ -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);

View File

@ -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()

View File

@ -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,

View File

@ -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);

View File

@ -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) {

View File

@ -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;
} }

View File

@ -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) {

View File

@ -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));

View File

@ -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(
() -> () ->

View File

@ -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;

View File

@ -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;

View File

@ -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";
}) })

View File

@ -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) {

View File

@ -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

View File

@ -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);
} }
} }

View File

@ -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"));
} }

View File

@ -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);
} }

View File

@ -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;

View File

@ -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")

View File

@ -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;
} }

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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();

View File

@ -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);
} }

View File

@ -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 -> {

View File

@ -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(

View File

@ -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)) {

View File

@ -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) {

View File

@ -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);

View File

@ -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

View File

@ -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");
} }

View File

@ -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;
} }

View File

@ -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();