package stirling.software.SPDF.utils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.NetworkInterface; import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.UUID; import org.simpleyaml.configuration.file.YamlFile; import org.simpleyaml.configuration.file.YamlFileWrapper; import org.simpleyaml.configuration.implementation.SimpleYamlImplementation; import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.multipart.MultipartFile; import com.fathzer.soft.javaluator.DoubleEvaluator; import io.github.pixee.security.HostValidator; import io.github.pixee.security.Urls; public class GeneralUtils { private static final Logger logger = LoggerFactory.getLogger(GeneralUtils.class); public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException { File tempFile = Files.createTempFile("temp", null).toFile(); try (FileOutputStream os = new FileOutputStream(tempFile)) { os.write(multipartFile.getBytes()); } return tempFile; } public static void deleteDirectory(Path path) throws IOException { Files.walkFileTree( path, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.deleteIfExists(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.deleteIfExists(dir); return FileVisitResult.CONTINUE; } }); } public static String convertToFileName(String name) { String safeName = name.replaceAll("[^a-zA-Z0-9]", "_"); if (safeName.length() > 50) { safeName = safeName.substring(0, 50); } return safeName; } public static boolean isValidURL(String urlStr) { try { Urls.create( urlStr, Urls.HTTP_PROTOCOLS, HostValidator.DENY_COMMON_INFRASTRUCTURE_TARGETS); return true; } catch (MalformedURLException e) { return false; } } public static boolean isURLReachable(String urlStr) { try { // Parse the URL URL url = URI.create(urlStr).toURL(); // Allow only http and https protocols String protocol = url.getProtocol(); if (!protocol.equals("http") && !protocol.equals("https")) { return false; // Disallow other protocols } // Check if the host is a local address String host = url.getHost(); if (isLocalAddress(host)) { return false; // Exclude local addresses } // Check if the URL is reachable HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("HEAD"); // connection.setConnectTimeout(5000); // Set connection timeout // connection.setReadTimeout(5000); // Set read timeout int responseCode = connection.getResponseCode(); return (200 <= responseCode && responseCode <= 399); } catch (Exception e) { return false; // Return false in case of any exception } } private static boolean isLocalAddress(String host) { try { // Resolve DNS to IP address InetAddress address = InetAddress.getByName(host); // Check for local addresses return address.isAnyLocalAddress() || // Matches 0.0.0.0 or similar address.isLoopbackAddress() || // Matches 127.0.0.1 or ::1 address.isSiteLocalAddress() || // Matches private IPv4 ranges: 192.168.x.x, 10.x.x.x, 172.16.x.x to // 172.31.x.x address.getHostAddress() .startsWith("fe80:"); // Matches link-local IPv6 addresses } catch (Exception e) { return false; // Return false for invalid or unresolved addresses } } public static File multipartToFile(MultipartFile multipart) throws IOException { Path tempFile = Files.createTempFile("overlay-", ".pdf"); try (InputStream in = multipart.getInputStream(); FileOutputStream out = new FileOutputStream(tempFile.toFile())) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } return tempFile.toFile(); } public static Long convertSizeToBytes(String sizeStr) { if (sizeStr == null) { return null; } sizeStr = sizeStr.trim().toUpperCase(); sizeStr = sizeStr.replace(",", ".").replace(" ", ""); try { if (sizeStr.endsWith("KB")) { return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); } else if (sizeStr.endsWith("MB")) { return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024); } else if (sizeStr.endsWith("GB")) { return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024); } else if (sizeStr.endsWith("B")) { return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); } else { // Assume MB if no unit is specified return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); } } catch (NumberFormatException e) { // The numeric part of the input string cannot be parsed, handle this case } return null; } public static List parsePageList(String pages, int totalPages, boolean oneBased) { if (pages == null) { return List.of(1); // Default to first page if input is null } try { return parsePageList(pages.split(","), totalPages, oneBased); } catch (NumberFormatException e) { return List.of(1); // Default to first page if input is invalid } } public static List parsePageList(String[] pages, int totalPages) { return parsePageList(pages, totalPages, false); } public static List parsePageList(String[] pages, int totalPages, boolean oneBased) { List result = new ArrayList<>(); int offset = oneBased ? 1 : 0; for (String page : pages) { if ("all".equalsIgnoreCase(page)) { for (int i = 0; i < totalPages; i++) { result.add(i + offset); } } else if (page.contains(",")) { // Split the string into parts, could be single pages or ranges String[] parts = page.split(","); for (String part : parts) { result.addAll(handlePart(part, totalPages, offset)); } } else { result.addAll(handlePart(page, totalPages, offset)); } } return new ArrayList<>( new java.util.LinkedHashSet<>(result)); // Remove duplicates and maintain order } public static List evaluateNFunc(String expression, int maxValue) { List results = new ArrayList<>(); DoubleEvaluator evaluator = new DoubleEvaluator(); // Validate the expression if (!expression.matches("[0-9n+\\-*/() ]+")) { throw new IllegalArgumentException("Invalid expression"); } int n = 0; while (true) { // Replace 'n' with the current value of n, correctly handling numbers before // 'n' String sanitizedExpression = insertMultiplicationBeforeN(expression, n); Double result = evaluator.evaluate(sanitizedExpression); // Check if the result is null or not within bounds if (result == null || result <= 0 || result.intValue() > maxValue) { if (n != 0) break; } else { results.add(result.intValue()); } n++; } return results; } private static String insertMultiplicationBeforeN(String expression, int nValue) { // Insert multiplication between a number and 'n' (e.g., "4n" becomes "4*n") String withMultiplication = expression.replaceAll("(\\d)n", "$1*n"); // Now replace 'n' with its current value return withMultiplication.replace("n", String.valueOf(nValue)); } private static List handlePart(String part, int totalPages, int offset) { List partResult = new ArrayList<>(); // First check for n-syntax because it should not be processed as a range if (part.contains("n")) { partResult = evaluateNFunc(part, totalPages); // Adjust the results according to the offset for (int i = 0; i < partResult.size(); i++) { int adjustedValue = partResult.get(i) - 1 + offset; partResult.set(i, adjustedValue); } } else if (part.contains("-")) { // Process ranges only if it's not n-syntax String[] rangeParts = part.split("-"); try { int start = Integer.parseInt(rangeParts[0]); int end = Integer.parseInt(rangeParts[1]); for (int i = start; i <= end; i++) { if (i >= 1 && i <= totalPages) { partResult.add(i - 1 + offset); } } } catch (NumberFormatException e) { // Range is invalid, ignore this part } } else { // This is a single page number try { int pageNum = Integer.parseInt(part.trim()); if (pageNum >= 1 && pageNum <= totalPages) { partResult.add(pageNum - 1 + offset); } } catch (NumberFormatException ignored) { // Ignore invalid numbers } } return partResult; } public static boolean createDir(String path) { Path folder = Paths.get(path); if (!Files.exists(folder)) { try { Files.createDirectories(folder); } catch (IOException e) { logger.error("exception", e); return false; } } return true; } public static boolean isValidUUID(String uuid) { if (uuid == null) { return false; } try { UUID.fromString(uuid); return true; } catch (IllegalArgumentException e) { return false; } } public static void saveKeyToConfig(String id, String key) throws IOException { saveKeyToConfig(id, key, true); } public static void saveKeyToConfig(String id, boolean key) throws IOException { saveKeyToConfig(id, key, true); } public static void saveKeyToConfig(String id, String key, boolean autoGenerated) throws IOException { Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml final YamlFile settingsYml = new YamlFile(path.toFile()); DumperOptions yamlOptionssettingsYml = ((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions(); yamlOptionssettingsYml.setSplitLines(false); settingsYml.loadWithComments(); YamlFileWrapper writer = settingsYml.path(id).set(key); if (autoGenerated) { writer.comment("# Automatically Generated Settings (Do Not Edit Directly)"); } settingsYml.save(); } public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated) throws IOException { Path path = Paths.get("configs", "settings.yml"); final YamlFile settingsYml = new YamlFile(path.toFile()); DumperOptions yamlOptionssettingsYml = ((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions(); yamlOptionssettingsYml.setSplitLines(false); settingsYml.loadWithComments(); YamlFileWrapper writer = settingsYml.path(id).set(key); if (autoGenerated) { writer.comment("# Automatically Generated Settings (Do Not Edit Directly)"); } settingsYml.save(); } public static String generateMachineFingerprint() { try { // Get the MAC address StringBuilder sb = new StringBuilder(); InetAddress ip = InetAddress.getLocalHost(); NetworkInterface network = NetworkInterface.getByInetAddress(ip); if (network == null) { Enumeration networks = NetworkInterface.getNetworkInterfaces(); while (networks.hasMoreElements()) { NetworkInterface net = networks.nextElement(); byte[] mac = net.getHardwareAddress(); if (mac != null) { for (int i = 0; i < mac.length; i++) { sb.append(String.format("%02X", mac[i])); } break; // Use the first network interface with a MAC address } } } else { byte[] mac = network.getHardwareAddress(); if (mac != null) { for (int i = 0; i < mac.length; i++) { sb.append(String.format("%02X", mac[i])); } } } // Hash the MAC address for privacy and consistency MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8)); StringBuilder fingerprint = new StringBuilder(); for (byte b : hash) { fingerprint.append(String.format("%02x", b)); } return fingerprint.toString(); } catch (Exception e) { return "GenericID"; } } public static boolean isVersionHigher(String currentVersion, String compareVersion) { if (currentVersion == null || compareVersion == null) { return false; } // Split versions into components String[] current = currentVersion.split("\\."); String[] compare = compareVersion.split("\\."); // Get the length of the shorter version array int length = Math.min(current.length, compare.length); // Compare each component for (int i = 0; i < length; i++) { int currentPart = Integer.parseInt(current[i]); int comparePart = Integer.parseInt(compare[i]); if (currentPart > comparePart) { return true; } if (currentPart < comparePart) { return false; } } // If all components so far are equal, the longer version is considered higher return current.length > compare.length; } }