diff --git a/app/common/src/main/java/stirling/software/common/configuration/InstallationPathConfig.java b/app/common/src/main/java/stirling/software/common/configuration/InstallationPathConfig.java index 860e0280638..af29fe6b8da 100644 --- a/app/common/src/main/java/stirling/software/common/configuration/InstallationPathConfig.java +++ b/app/common/src/main/java/stirling/software/common/configuration/InstallationPathConfig.java @@ -2,6 +2,7 @@ import java.io.File; import java.nio.file.Paths; +import java.util.Locale; import lombok.extern.slf4j.Slf4j; @@ -59,7 +60,7 @@ public class InstallationPathConfig { private static String initializeBasePath() { 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")) { return Paths.get( System.getenv("APPDATA"), // parent path diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index 1f734f5a644..91d39a1ff93 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Locale; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -286,7 +287,7 @@ public static class Client { private KeycloakProvider keycloak = new KeycloakProvider(); public Provider get(String registrationId) throws UnsupportedProviderException { - return switch (registrationId.toLowerCase()) { + return switch (registrationId.toLowerCase(Locale.ROOT)) { case "google" -> getGoogle(); case "github" -> getGithub(); case "keycloak" -> getKeycloak(); diff --git a/app/common/src/main/java/stirling/software/common/model/FileInfo.java b/app/common/src/main/java/stirling/software/common/model/FileInfo.java index 2e3e59e83bc..e8942029698 100644 --- a/app/common/src/main/java/stirling/software/common/model/FileInfo.java +++ b/app/common/src/main/java/stirling/software/common/model/FileInfo.java @@ -30,11 +30,11 @@ public Path getFilePathAsPath() { // Formats the file size into a human-readable string. public String getFormattedFileSize() { 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) { - 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) { - return String.format(Locale.US, "%.2f KB", fileSize / 1024.0); + return String.format(Locale.ROOT, "%.2f KB", fileSize / 1024.0); } else { return String.format("%d Bytes", fileSize); } diff --git a/app/common/src/main/java/stirling/software/common/service/CustomPDFDocumentFactory.java b/app/common/src/main/java/stirling/software/common/service/CustomPDFDocumentFactory.java index d106a27298e..8d7419eb715 100644 --- a/app/common/src/main/java/stirling/software/common/service/CustomPDFDocumentFactory.java +++ b/app/common/src/main/java/stirling/software/common/service/CustomPDFDocumentFactory.java @@ -7,6 +7,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.Locale; import java.util.concurrent.atomic.AtomicLong; import org.apache.pdfbox.Loader; @@ -249,7 +250,7 @@ public StreamCacheCreateFunction getStreamCacheFunction(long contentSize) { log.debug( "Memory status - Free: {}MB ({}%), Used: {}MB, Max: {}MB", actualFreeMemory / (1024 * 1024), - String.format("%.2f", freeMemoryPercent), + String.format(Locale.ROOT, "%.2f", freeMemoryPercent), usedMemory / (1024 * 1024), maxMemory / (1024 * 1024)); @@ -258,7 +259,7 @@ public StreamCacheCreateFunction getStreamCacheFunction(long contentSize) { || actualFreeMemory < MIN_FREE_MEMORY_BYTES) { log.debug( "Low memory detected ({}%), forcing file-based cache", - String.format("%.2f", freeMemoryPercent)); + String.format(Locale.ROOT, "%.2f", freeMemoryPercent)); return createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly()); } else if (contentSize < SMALL_FILE_THRESHOLD) { log.debug("Using memory-only cache for small document ({}KB)", contentSize / 1024); diff --git a/app/common/src/main/java/stirling/software/common/service/JobExecutorService.java b/app/common/src/main/java/stirling/software/common/service/JobExecutorService.java index 6f595857aa9..8cbe0a1be71 100644 --- a/app/common/src/main/java/stirling/software/common/service/JobExecutorService.java +++ b/app/common/src/main/java/stirling/software/common/service/JobExecutorService.java @@ -1,6 +1,7 @@ package stirling.software.common.service; import java.io.IOException; +import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -440,7 +441,7 @@ private long parseSessionTimeout(String timeout) { double numericValue = Double.parseDouble(value); - return switch (unit.toLowerCase()) { + return switch (unit.toLowerCase(Locale.ROOT)) { case "s" -> (long) (numericValue * 1000); case "m" -> (long) (numericValue * 60 * 1000); case "h" -> (long) (numericValue * 60 * 60 * 1000); diff --git a/app/common/src/main/java/stirling/software/common/service/PostHogService.java b/app/common/src/main/java/stirling/software/common/service/PostHogService.java index 070dea694ba..e788af9fbd8 100644 --- a/app/common/src/main/java/stirling/software/common/service/PostHogService.java +++ b/app/common/src/main/java/stirling/software/common/service/PostHogService.java @@ -397,7 +397,7 @@ private String getMacAddress() { if (hardwareAddress != null) { String[] hexadecimal = new String[hardwareAddress.length]; 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); } diff --git a/app/common/src/main/java/stirling/software/common/service/ResourceMonitor.java b/app/common/src/main/java/stirling/software/common/service/ResourceMonitor.java index 0e8073d8fb8..b2292395977 100644 --- a/app/common/src/main/java/stirling/software/common/service/ResourceMonitor.java +++ b/app/common/src/main/java/stirling/software/common/service/ResourceMonitor.java @@ -5,6 +5,7 @@ import java.lang.management.OperatingSystemMXBean; import java.time.Duration; import java.time.Instant; +import java.util.Locale; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -173,8 +174,8 @@ private void updateResourceMetrics() { log.info("System resource status changed from {} to {}", oldStatus, newStatus); log.info( "Current metrics - CPU: {}%, Memory: {}%, Free Memory: {} MB", - String.format("%.1f", cpuUsage * 100), - String.format("%.1f", memoryUsage * 100), + String.format(Locale.ROOT, "%.1f", cpuUsage * 100), + String.format(Locale.ROOT, "%.1f", memoryUsage * 100), freeMemory / (1024 * 1024)); } } catch (Exception e) { diff --git a/app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java b/app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java index 1f81fb4d424..ce19ff82607 100644 --- a/app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java +++ b/app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java @@ -5,6 +5,7 @@ import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; +import java.util.Locale; import java.util.regex.Pattern; import org.springframework.stereotype.Service; @@ -83,7 +84,7 @@ private boolean isMaxSecurityAllowed( return false; } - return config.getAllowedDomains().contains(host.toLowerCase()); + return config.getAllowedDomains().contains(host.toLowerCase(Locale.ROOT)); } catch (Exception e) { log.debug("Failed to parse URL for MAX security check: {}", url, e); @@ -101,7 +102,7 @@ private boolean isMediumSecurityAllowed( return false; } - String hostLower = host.toLowerCase(); + String hostLower = host.toLowerCase(Locale.ROOT); // Check explicit blocked domains if (config.getBlockedDomains().contains(hostLower)) { @@ -111,7 +112,7 @@ private boolean isMediumSecurityAllowed( // Check internal TLD patterns 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); return false; } @@ -123,9 +124,11 @@ private boolean isMediumSecurityAllowed( config.getAllowedDomains().stream() .anyMatch( domain -> - hostLower.equals(domain.toLowerCase()) + hostLower.equals(domain.toLowerCase(Locale.ROOT)) || hostLower.endsWith( - "." + domain.toLowerCase())); + "." + + domain.toLowerCase( + Locale.ROOT))); if (!isAllowed) { log.debug("URL not in allowed domains list: {}", url); diff --git a/app/common/src/main/java/stirling/software/common/service/TaskManager.java b/app/common/src/main/java/stirling/software/common/service/TaskManager.java index 902b2bfd101..5d2a9edca8c 100644 --- a/app/common/src/main/java/stirling/software/common/service/TaskManager.java +++ b/app/common/src/main/java/stirling/software/common/service/TaskManager.java @@ -7,6 +7,7 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; @@ -101,14 +102,16 @@ public void setFileResult( if (!extractedFiles.isEmpty()) { jobResult.completeWithFiles(extractedFiles); 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, extractedFiles.size()); return; } } catch (Exception e) { 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, e.getMessage()); } @@ -342,12 +345,12 @@ public void shutdown() { /** Check if a file is a ZIP file based on content type and filename */ private boolean isZipFile(String contentType, String fileName) { if (contentType != null - && (contentType.equals("application/zip") - || contentType.equals("application/x-zip-compressed"))) { + && ("application/zip".equals(contentType) + || "application/x-zip-compressed".equals(contentType))) { return true; } - if (fileName != null && fileName.toLowerCase().endsWith(".zip")) { + if (fileName != null && fileName.toLowerCase(Locale.ROOT).endsWith(".zip")) { return true; } @@ -414,7 +417,7 @@ private String determineContentType(String fileName) { return MediaType.APPLICATION_OCTET_STREAM_VALUE; } - String lowerName = fileName.toLowerCase(); + String lowerName = fileName.toLowerCase(Locale.ROOT); if (lowerName.endsWith(".pdf")) { return MediaType.APPLICATION_PDF_VALUE; } else if (lowerName.endsWith(".txt")) { diff --git a/app/common/src/main/java/stirling/software/common/util/CbrUtils.java b/app/common/src/main/java/stirling/software/common/util/CbrUtils.java index 429d22407d4..98adfb79d98 100644 --- a/app/common/src/main/java/stirling/software/common/util/CbrUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/CbrUtils.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Locale; import org.apache.commons.io.FilenameUtils; import org.apache.pdfbox.pdmodel.PDDocument; @@ -60,10 +61,9 @@ public byte[] convertCbrToPdf( e.getMessage()); throw ExceptionUtils.createIllegalArgumentException( "error.invalidFormat", - "Invalid or corrupted CBR/RAR archive. " - + "The file may be corrupted, use an unsupported RAR format (RAR5+), " - + "or may not be a valid RAR archive. " - + "Please ensure the file is a valid RAR archive."); + "Invalid or corrupted CBR/RAR archive. The file may be corrupted, use" + + " an unsupported RAR format (RAR5+), or may not be a valid RAR" + + " archive. Please ensure the file is a valid RAR archive."); } catch (RarException e) { log.warn("Failed to open CBR/RAR archive: {}", e.getMessage()); String errorMessage; @@ -73,13 +73,14 @@ public byte[] convertCbrToPdf( errorMessage = "Encrypted CBR/RAR archives are not supported."; } else if (exMessage.isEmpty()) { errorMessage = - "Invalid CBR/RAR archive. " - + "The file may be encrypted, corrupted, or use an unsupported format."; + "Invalid CBR/RAR archive. The file may be encrypted, corrupted, or" + + " use an unsupported format."; } else { errorMessage = "Invalid CBR/RAR archive: " + 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( "error.invalidFormat", errorMessage); @@ -121,7 +122,8 @@ public byte[] convertCbrToPdf( if (imageEntries.isEmpty()) { throw ExceptionUtils.createIllegalArgumentException( "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) { @@ -146,7 +148,8 @@ public byte[] convertCbrToPdf( if (document.getNumberOfPages() == 0) { throw ExceptionUtils.createIllegalArgumentException( "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(); @@ -159,7 +162,6 @@ public byte[] convertCbrToPdf( return GeneralUtils.optimizePdfWithGhostscript(pdfBytes); } catch (IOException e) { log.warn("Ghostscript optimization failed, returning unoptimized PDF", e); - return pdfBytes; } } @@ -178,7 +180,7 @@ private void validateCbrFile(MultipartFile file) { 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)) { throw new IllegalArgumentException("File must be a CBR or RAR archive"); } @@ -190,7 +192,7 @@ public boolean isCbrFile(MultipartFile file) { return false; } - String extension = FilenameUtils.getExtension(filename).toLowerCase(); + String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT); return "cbr".equals(extension) || "rar".equals(extension); } diff --git a/app/common/src/main/java/stirling/software/common/util/CbzUtils.java b/app/common/src/main/java/stirling/software/common/util/CbzUtils.java index 5eb620e8e81..1b0910e396b 100644 --- a/app/common/src/main/java/stirling/software/common/util/CbzUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/CbzUtils.java @@ -8,6 +8,7 @@ import java.util.Comparator; import java.util.Enumeration; import java.util.List; +import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; @@ -119,7 +120,6 @@ public byte[] convertCbzToPdf( return GeneralUtils.optimizePdfWithGhostscript(pdfBytes); } catch (IOException e) { log.warn("Ghostscript optimization failed, returning unoptimized PDF", e); - return pdfBytes; } } @@ -138,7 +138,7 @@ private void validateCbzFile(MultipartFile file) { 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)) { throw new IllegalArgumentException("File must be a CBZ or ZIP archive"); } @@ -150,7 +150,7 @@ public boolean isCbzFile(MultipartFile file) { return false; } - String extension = FilenameUtils.getExtension(filename).toLowerCase(); + String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT); return "cbz".equals(extension) || "zip".equals(extension); } @@ -160,7 +160,7 @@ public static boolean isComicBookFile(MultipartFile file) { return false; } - String extension = FilenameUtils.getExtension(filename).toLowerCase(); + String extension = FilenameUtils.getExtension(filename).toLowerCase(Locale.ROOT); return "cbz".equals(extension) || "zip".equals(extension) || "cbr".equals(extension) diff --git a/app/common/src/main/java/stirling/software/common/util/ChecksumUtils.java b/app/common/src/main/java/stirling/software/common/util/ChecksumUtils.java index d9749deea94..ab39ca5da86 100644 --- a/app/common/src/main/java/stirling/software/common/util/ChecksumUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/ChecksumUtils.java @@ -58,14 +58,11 @@ public static String checksum(Path path, String algorithm) throws IOException { * @throws IOException if reading from the stream fails */ public static String checksum(InputStream is, String algorithm) throws IOException { - switch (algorithm.toUpperCase(Locale.ROOT)) { - case "CRC32": - return checksumChecksum(is, new CRC32()); - case "ADLER32": - return checksumChecksum(is, new Adler32()); - default: - return toHex(checksumBytes(is, algorithm)); - } + return switch (algorithm.toUpperCase(Locale.ROOT)) { + case "CRC32" -> checksumChecksum(is, new CRC32()); + case "ADLER32" -> checksumChecksum(is, new Adler32()); + default -> toHex(checksumBytes(is, algorithm)); + }; } /** @@ -98,14 +95,13 @@ public static String checksumBase64(Path path, String algorithm) throws IOExcept * @throws IOException if reading from the stream fails */ public static String checksumBase64(InputStream is, String algorithm) throws IOException { - switch (algorithm.toUpperCase(Locale.ROOT)) { - case "CRC32": - return Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new CRC32())); - case "ADLER32": - return Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new Adler32())); - default: - return Base64.getEncoder().encodeToString(checksumBytes(is, algorithm)); - } + return switch (algorithm.toUpperCase(Locale.ROOT)) { + case "CRC32" -> + Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new CRC32())); + case "ADLER32" -> + Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new Adler32())); + default -> Base64.getEncoder().encodeToString(checksumBytes(is, algorithm)); + }; } /** @@ -179,7 +175,7 @@ public static Map checksums(InputStream is, String... algorithms for (Map.Entry entry : checksums.entrySet()) { // Keep value as long and mask to ensure unsigned hex formatting. 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; } @@ -258,7 +254,7 @@ private static String checksumChecksum(InputStream is, Checksum checksum) throws } // Keep as long and mask to ensure correct unsigned representation. long unsigned32 = checksum.getValue() & UNSIGNED_32_BIT_MASK; - return String.format("%08x", unsigned32); + return String.format(Locale.ROOT, "%08x", unsigned32); } /** @@ -294,7 +290,7 @@ private static byte[] checksumChecksumBytes(InputStream is, Checksum checksum) private static String toHex(byte[] hash) { StringBuilder sb = new StringBuilder(hash.length * 2); for (byte b : hash) { - sb.append(String.format("%02x", b)); + sb.append(String.format(Locale.ROOT, "%02x", b)); } return sb.toString(); } diff --git a/app/common/src/main/java/stirling/software/common/util/EmlParser.java b/app/common/src/main/java/stirling/software/common/util/EmlParser.java index ec71fbb19c4..642bc3a5e1d 100644 --- a/app/common/src/main/java/stirling/software/common/util/EmlParser.java +++ b/app/common/src/main/java/stirling/software/common/util/EmlParser.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Properties; import java.util.regex.Pattern; @@ -229,7 +230,8 @@ private static void processMessageContent( Method getContentType = message.getClass().getMethod("getContentType"); 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); } else { content.setTextBody(stringContent); @@ -296,7 +298,7 @@ private static void processPart( String contentType = (String) getContentType.invoke(part); 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) { Object partContent = getContent.invoke(part); @@ -422,7 +424,7 @@ private static String extractBasicHeader(String emlContent, String headerName) { RegexPatternUtils.getInstance().getNewlineSplitPattern().split(emlContent); for (int i = 0; i < lines.length; i++) { String line = lines[i]; - if (line.toLowerCase().startsWith(headerName.toLowerCase())) { + if (line.toLowerCase(Locale.ROOT).startsWith(headerName.toLowerCase(Locale.ROOT))) { StringBuilder value = new StringBuilder(line.substring(headerName.length()).trim()); for (int j = i + 1; j < lines.length; j++) { @@ -444,7 +446,7 @@ private static String extractBasicHeader(String emlContent, String headerName) { private static String extractHtmlBody(String emlContent) { try { - String lowerContent = emlContent.toLowerCase(); + String lowerContent = emlContent.toLowerCase(Locale.ROOT); int htmlStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_HTML); if (htmlStart == -1) return null; @@ -463,7 +465,7 @@ private static String extractHtmlBody(String emlContent) { private static String extractTextBody(String emlContent) { try { - String lowerContent = emlContent.toLowerCase(); + String lowerContent = emlContent.toLowerCase(Locale.ROOT); int textStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_PLAIN); if (textStart == -1) { int bodyStart = emlContent.indexOf("\r\n\r\n"); @@ -516,7 +518,7 @@ private static List extractAttachmentsBasic(String emlContent) String currentEncoding = ""; for (String line : lines) { - String lowerLine = line.toLowerCase().trim(); + String lowerLine = line.toLowerCase(Locale.ROOT).trim(); if (line.trim().isEmpty()) { inHeaders = false; @@ -554,9 +556,12 @@ private static List extractAttachmentsBasic(String emlContent) } private static boolean isAttachment(String disposition, String filename, String contentType) { - return (disposition.toLowerCase().contains(DISPOSITION_ATTACHMENT) && !filename.isEmpty()) - || (!filename.isEmpty() && !contentType.toLowerCase().startsWith("text/")) - || (contentType.toLowerCase().contains("application/") && !filename.isEmpty()); + return (disposition.toLowerCase(Locale.ROOT).contains(DISPOSITION_ATTACHMENT) + && !filename.isEmpty()) + || (!filename.isEmpty() + && !contentType.toLowerCase(Locale.ROOT).startsWith("text/")) + || (contentType.toLowerCase(Locale.ROOT).contains("application/") + && !filename.isEmpty()); } private static String extractFilenameFromDisposition(String disposition) { @@ -565,8 +570,8 @@ private static String extractFilenameFromDisposition(String disposition) { } // Handle filename*= (RFC 2231 encoded filename) - if (disposition.toLowerCase().contains("filename*=")) { - int filenameStarStart = disposition.toLowerCase().indexOf("filename*=") + 10; + if (disposition.toLowerCase(Locale.ROOT).contains("filename*=")) { + int filenameStarStart = disposition.toLowerCase(Locale.ROOT).indexOf("filename*=") + 10; int filenameStarEnd = disposition.indexOf(";", filenameStarStart); if (filenameStarEnd == -1) filenameStarEnd = disposition.length(); String extendedFilename = @@ -586,7 +591,7 @@ private static String extractFilenameFromDisposition(String disposition) { } // Handle regular filename= - int filenameStart = disposition.toLowerCase().indexOf("filename=") + 9; + int filenameStart = disposition.toLowerCase(Locale.ROOT).indexOf("filename=") + 9; int filenameEnd = disposition.indexOf(";", filenameStart); if (filenameEnd == -1) filenameEnd = disposition.length(); String filename = disposition.substring(filenameStart, filenameEnd).trim(); diff --git a/app/common/src/main/java/stirling/software/common/util/EmlProcessingUtils.java b/app/common/src/main/java/stirling/software/common/util/EmlProcessingUtils.java index 55035fe9f82..dc2c03478e5 100644 --- a/app/common/src/main/java/stirling/software/common/util/EmlProcessingUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/EmlProcessingUtils.java @@ -109,12 +109,13 @@ public static String generateEnhancedEmailHtml( html.append( String.format( + Locale.ROOT, """ - - - %s -