diff --git a/gxcompress/pom.xml b/gxcompress/pom.xml
new file mode 100644
index 000000000..ebeb9b276
--- /dev/null
+++ b/gxcompress/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+ com.genexus
+ parent
+ ${revision}${changelist}
+
+
+ gxcompress
+ GeneXus compression and decompression module
+
+
+
+ org.apache.commons
+ commons-compress
+ 1.27.1
+
+
+ org.tukaani
+ xz
+ 1.10
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}
+
+
+ ${project.groupId}
+ gxcommon
+ ${project.version}
+
+
+ com.genexus
+ gxclassR
+ ${project.version}
+ test
+
+
+
+
\ No newline at end of file
diff --git a/gxcompress/src/main/java/com/genexus/compression/Compression.java b/gxcompress/src/main/java/com/genexus/compression/Compression.java
new file mode 100644
index 000000000..51bd5fd20
--- /dev/null
+++ b/gxcompress/src/main/java/com/genexus/compression/Compression.java
@@ -0,0 +1,42 @@
+package com.genexus.compression;
+
+import com.genexus.GXBaseCollection;
+import com.genexus.SdtMessages_Message;
+
+import java.util.ArrayList;
+
+public class Compression {
+
+ private String destinationPath;
+ private CompressionConfiguration compressionConfiguration;
+ private GXBaseCollection[] messages;
+ private ArrayList filesToCompress;
+
+ public Compression() {}
+
+ public Compression(String destinationPath, CompressionConfiguration configuration, GXBaseCollection[] messages) {
+ this.destinationPath = destinationPath;
+ this.compressionConfiguration = configuration;
+ this.messages = messages;
+ filesToCompress = new ArrayList<>();
+ }
+
+ public void setDestinationPath(String path) {
+ this.destinationPath = path;
+ }
+
+ public void addElement(String filePath) {
+ filesToCompress.add(filePath);
+ }
+
+ public Boolean save() {
+ return GXCompressor.compress(filesToCompress, destinationPath, compressionConfiguration, messages);
+ }
+
+ public void clear() {
+ destinationPath = "";
+ filesToCompress = new ArrayList<>();
+ messages = null;
+ compressionConfiguration = new CompressionConfiguration();
+ }
+}
diff --git a/gxcompress/src/main/java/com/genexus/compression/CompressionConfiguration.java b/gxcompress/src/main/java/com/genexus/compression/CompressionConfiguration.java
new file mode 100644
index 000000000..f5c768935
--- /dev/null
+++ b/gxcompress/src/main/java/com/genexus/compression/CompressionConfiguration.java
@@ -0,0 +1,10 @@
+package com.genexus.compression;
+
+public class CompressionConfiguration {
+ public long maxCombinedFileSize = -1;
+ public long maxIndividualFileSize = -1;
+ public int maxFileCount = -1;
+ public String targetDirectory = "";
+
+ public CompressionConfiguration() {}
+}
diff --git a/gxcompress/src/main/java/com/genexus/compression/CompressionUtils.java b/gxcompress/src/main/java/com/genexus/compression/CompressionUtils.java
new file mode 100644
index 000000000..ad4963cbd
--- /dev/null
+++ b/gxcompress/src/main/java/com/genexus/compression/CompressionUtils.java
@@ -0,0 +1,310 @@
+package com.genexus.compression;
+
+import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
+import org.apache.commons.compress.archivers.sevenz.SevenZFile;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import static org.apache.commons.io.FileUtils.getFile;
+import static org.apache.commons.io.FilenameUtils.getExtension;
+
+
+public class CompressionUtils {
+
+ /**
+ * Counts the number of entries in an archive file.
+ *
+ * @param archiveFile The archive file to analyze
+ * @return The number of entries in the archive
+ * @throws IOException If an I/O error occurs
+ */
+ public static int countArchiveEntries(File archiveFile) throws IOException {
+ String extension = getExtension(archiveFile.getName()).toLowerCase();
+ int count = 0;
+
+ switch (extension) {
+ case "zip":
+ try (ZipFile zipFile = new ZipFile(archiveFile)) {
+ return zipFile.size();
+ }
+ case "7z":
+ try (SevenZFile sevenZFile = getSevenZFile(archiveFile.getAbsolutePath())) {
+ while (sevenZFile.getNextEntry() != null) {
+ count++;
+ }
+ return count;
+ }
+ case "tar":
+ try (TarArchiveInputStream tarStream = new TarArchiveInputStream(Files.newInputStream(archiveFile.toPath()))) {
+ while (tarStream.getNextEntry() != null) {
+ count++;
+ }
+ return count;
+ }
+ case "gz":
+ return 1;
+ case "jar":
+ try (JarFile jarFile = new JarFile(archiveFile)) {
+ return jarFile.size();
+ }
+ default:
+ throw new IllegalArgumentException("Unsupported archive format: " + extension);
+ }
+ }
+
+ /**
+ * Checks if an archive is safe to extract (no path traversal/zip slip).
+ *
+ * @param archiveFile The archive file to check
+ * @param targetDir The target directory for extraction
+ * @return true if the archive is safe, false otherwise
+ * @throws IOException If an I/O error occurs
+ */
+ public static boolean isArchiveSafe(File archiveFile, String targetDir) throws IOException {
+ String extension = getExtension(archiveFile.getName()).toLowerCase();
+ File targetPath = new File(targetDir).getCanonicalFile();
+
+ switch (extension) {
+ case "zip":
+ try (ZipFile zipFile = new ZipFile(archiveFile)) {
+ Enumeration extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ File destinationFile = new File(targetPath, entry.getName()).getCanonicalFile();
+ if (!destinationFile.getPath().startsWith(targetPath.getPath() + File.separator) &&
+ !destinationFile.getPath().equals(targetPath.getPath())) {
+ return false;
+ }
+ }
+ }
+ return true;
+ case "7z":
+ try (SevenZFile sevenZFile = getSevenZFile(archiveFile.getAbsolutePath())) {
+ SevenZArchiveEntry entry;
+ while ((entry = sevenZFile.getNextEntry()) != null) {
+ File destinationFile = new File(targetPath, entry.getName()).getCanonicalFile();
+ if (!destinationFile.getPath().startsWith(targetPath.getPath() + File.separator) &&
+ !destinationFile.getPath().equals(targetPath.getPath())) {
+ return false;
+ }
+ }
+ }
+ return true;
+ case "tar":
+ try (TarArchiveInputStream tarStream = new TarArchiveInputStream(Files.newInputStream(archiveFile.toPath()))) {
+ TarArchiveEntry entry;
+ while ((entry = tarStream.getNextEntry()) != null) {
+ File destinationFile = new File(targetPath, entry.getName()).getCanonicalFile();
+ if (!destinationFile.getPath().startsWith(targetPath.getPath() + File.separator) &&
+ !destinationFile.getPath().equals(targetPath.getPath())) {
+ return false;
+ }
+ }
+ }
+ return true;
+ case "gz":
+ String fileName = archiveFile.getName();
+ if (fileName.endsWith(".gz") && fileName.length() > 3) {
+ String extractedName = fileName.substring(0, fileName.length() - 3);
+ File destinationFile = new File(targetPath, extractedName).getCanonicalFile();
+ return destinationFile.getPath().startsWith(targetPath.getPath() + File.separator) ||
+ destinationFile.getPath().equals(targetPath.getPath());
+ }
+ return true;
+ case "jar":
+ try (JarFile jarFile = new JarFile(archiveFile)) {
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ File destinationFile = new File(targetPath, entry.getName()).getCanonicalFile();
+ if (!destinationFile.getPath().startsWith(targetPath.getPath() + File.separator) &&
+ !destinationFile.getPath().equals(targetPath.getPath())) {
+ return false;
+ }
+ }
+ }
+ return true;
+ default:
+ throw new IllegalArgumentException("Unsupported archive format: " + extension);
+ }
+ }
+
+ /**
+ * Gets the maximum file size of any entry in the archive.
+ *
+ * @param archiveFile The archive file to analyze
+ * @return The size of the largest file in the archive
+ * @throws IOException If an I/O error occurs
+ */
+ public static long getMaxFileSize(File archiveFile) throws IOException {
+ String extension = getExtension(archiveFile.getName()).toLowerCase();
+ long maxSize = 0;
+
+ switch (extension) {
+ case "zip":
+ try (ZipFile zipFile = new ZipFile(archiveFile)) {
+ Enumeration extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (!entry.isDirectory() && entry.getSize() > maxSize) {
+ maxSize = entry.getSize();
+ }
+ }
+ }
+ break;
+ case "7z":
+ try (SevenZFile sevenZFile = getSevenZFile(archiveFile.getAbsolutePath())) {
+ SevenZArchiveEntry entry;
+ while ((entry = sevenZFile.getNextEntry()) != null) {
+ if (!entry.isDirectory() && entry.getSize() > maxSize) {
+ maxSize = entry.getSize();
+ }
+ }
+ }
+ break;
+ case "tar":
+ try (TarArchiveInputStream tarStream = new TarArchiveInputStream(Files.newInputStream(archiveFile.toPath()))) {
+ TarArchiveEntry entry;
+ while ((entry = tarStream.getNextEntry()) != null) {
+ if (!entry.isDirectory() && entry.getSize() > maxSize) {
+ maxSize = entry.getSize();
+ }
+ }
+ }
+ break;
+ case "gz":
+ try (GZIPInputStream gzStream = new GZIPInputStream(Files.newInputStream(archiveFile.toPath()))) {
+ byte[] buffer = new byte[8192];
+ long size = 0;
+ int n;
+ while ((n = gzStream.read(buffer)) != -1) {
+ size += n;
+ }
+ maxSize = size;
+ }
+ break;
+ case "jar":
+ try (JarFile jarFile = new JarFile(archiveFile)) {
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ if (!entry.isDirectory() && entry.getSize() > maxSize) {
+ maxSize = entry.getSize();
+ }
+ }
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported archive format: " + extension);
+ }
+
+ return maxSize;
+ }
+
+ /**
+ * Estimates the total size of all files after decompression.
+ *
+ * @param archiveFile The archive file to analyze
+ * @return The estimated total size after decompression
+ * @throws IOException If an I/O error occurs
+ */
+ public static long estimateDecompressedSize(File archiveFile) throws IOException {
+ String extension = getExtension(archiveFile.getName()).toLowerCase();
+ long totalSize = 0;
+
+ switch (extension) {
+ case "zip":
+ try (ZipFile zipFile = new ZipFile(archiveFile)) {
+ Enumeration extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (!entry.isDirectory()) {
+ long size = entry.getSize();
+ if (size != -1) {
+ totalSize += size;
+ } else {
+ totalSize += entry.getCompressedSize() * 3;
+ }
+ }
+ }
+ }
+ break;
+ case "7z":
+ try (SevenZFile sevenZFile = getSevenZFile(archiveFile.getAbsolutePath())) {
+ SevenZArchiveEntry entry;
+ while ((entry = sevenZFile.getNextEntry()) != null) {
+ if (!entry.isDirectory()) {
+ totalSize += entry.getSize();
+ }
+ }
+ }
+ break;
+ case "tar":
+ try (TarArchiveInputStream tarStream = new TarArchiveInputStream(Files.newInputStream(archiveFile.toPath()))) {
+ TarArchiveEntry entry;
+ while ((entry = tarStream.getNextEntry()) != null) {
+ if (!entry.isDirectory()) {
+ totalSize += entry.getSize();
+ }
+ }
+ }
+ break;
+ case "gz":
+ try (RandomAccessFile raf = new RandomAccessFile(archiveFile, "r")) {
+ raf.seek(raf.length() - 4);
+ int b4 = raf.read();
+ int b3 = raf.read();
+ int b2 = raf.read();
+ int b1 = raf.read();
+ if (b1 != -1 && b2 != -1 && b3 != -1 && b4 != -1) {
+ long size = ((long) b1 << 24) | ((long) b2 << 16) | ((long) b3 << 8) | b4;
+ if (size > 0) {
+ totalSize = size;
+ } else {
+ totalSize = archiveFile.length() * 5;
+ }
+ } else {
+ totalSize = archiveFile.length() * 5;
+ }
+ } catch (Exception e) {
+ totalSize = archiveFile.length() * 5;
+ }
+ break;
+ case "jar":
+ try (JarFile jarFile = new JarFile(archiveFile)) {
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ if (!entry.isDirectory()) {
+ long size = entry.getSize();
+ if (size != -1) {
+ totalSize += size;
+ } else {
+ totalSize += entry.getCompressedSize() * 3;
+ }
+ }
+ }
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported archive format: " + extension);
+ }
+
+ return totalSize;
+ }
+
+ private static SevenZFile getSevenZFile(final String specialPath) throws IOException {
+ return SevenZFile.builder().setFile(getFile(specialPath)).get();
+ }
+}
\ No newline at end of file
diff --git a/gxcompress/src/main/java/com/genexus/compression/GXCompressor.java b/gxcompress/src/main/java/com/genexus/compression/GXCompressor.java
new file mode 100644
index 000000000..796a1fb43
--- /dev/null
+++ b/gxcompress/src/main/java/com/genexus/compression/GXCompressor.java
@@ -0,0 +1,799 @@
+package com.genexus.compression;
+
+import com.genexus.CommonUtil;
+import com.genexus.GXBaseCollection;
+import com.genexus.SdtMessages_Message;
+import com.genexus.StructSdtMessages_Message;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
+import org.apache.logging.log4j.Logger;
+
+import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
+import org.apache.commons.compress.archivers.sevenz.SevenZFile;
+import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.zip.*;
+
+import static org.apache.commons.io.FilenameUtils.getExtension;
+
+public class GXCompressor {
+
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(GXCompressor.class);
+
+ private static final String GENERIC_ERROR = "An error occurred during the compression/decompression process: ";
+ private static final String NO_FILES_ADDED = "No files have been added for compression.";
+ private static final String FILE_NOT_EXISTS = "File does not exist: ";
+ private static final String UNSUPPORTED_FORMAT = " is an unsupported format. Supported formats are zip, 7z, tar, gz and jar.";
+ private static final String EMPTY_FILE = "The selected file is empty: ";
+ private static final String PURGED_ARCHIVE = "After performing security checks, no valid files where left to compress";
+ private static final String DIRECTORY_ATTACK = "Potential directory traversal attack detected: ";
+ private static final String MAX_FILESIZE_EXCEEDED = "The file(s) selected for (de)compression exceed the maximum permitted file size of ";
+ private static final String TOO_MANY_FILES = "Too many files have been added for (de)compression. Maximum allowed is ";
+ private static final String ZIP_SLIP_DETECTED = "Zip slip or path traversal attack detected in archive: ";
+ private static final String BIG_SINGLE_FILE = "Individual file exceeds maximum allowed size: ";
+ private static final String PROCESSING_ERROR = "Error checking archive safety for file: ";
+ private static final String ARCHIVE_SIZE_ESTIMATION_ERROR = "";
+
+ private static void storageMessages(String error, GXBaseCollection messages) {
+ try {
+ StructSdtMessages_Message struct = new StructSdtMessages_Message();
+ struct.setDescription(error);
+ struct.setType((byte) 1);
+ SdtMessages_Message msg = new SdtMessages_Message(struct);
+ messages.add(msg);
+ } catch (Exception e) {
+ log.error("Failed to store the following error message: {}", error, e);
+ }
+ }
+
+ /**
+ * Compresses specified files into an archive at the given path based on configuration parameters.
+ *
+ * @param files List of file paths to compress
+ * @param path Target path for the compressed archive
+ * @param configuration Configuration parameters for compression
+ * @param messages Collection to store output messages
+ * @return Boolean indicating success or failure of compression operation
+ */
+ public static Boolean compress(ArrayList files, String path, CompressionConfiguration configuration, GXBaseCollection[] messages) {
+ if (files.isEmpty()) {
+ log.error(NO_FILES_ADDED);
+ storageMessages(NO_FILES_ADDED, messages[0]);
+ return false;
+ }
+ List validFiles = new ArrayList<>();
+ long totalSize = 0;
+ for (String filePath : files) {
+ File file = new File(filePath);
+ try {
+ String normalizedPath = file.getCanonicalPath();
+ if (!file.exists()) {
+ log.error("{}{}", FILE_NOT_EXISTS, filePath);
+ storageMessages(FILE_NOT_EXISTS + filePath, messages[0]);
+ continue;
+ }
+ File absFile = file.getAbsoluteFile();
+ if (!normalizedPath.startsWith(absFile.getParentFile().getCanonicalPath())) {
+ log.error(DIRECTORY_ATTACK + "{}", filePath);
+ storageMessages(DIRECTORY_ATTACK + filePath, messages[0]);
+ return false;
+ }
+ long fileSize = file.length();
+ if (configuration.maxIndividualFileSize > -1 && fileSize > configuration.maxIndividualFileSize) {
+ log.error(BIG_SINGLE_FILE + filePath);
+ storageMessages(BIG_SINGLE_FILE + filePath, messages[0]);
+ continue;
+ }
+ totalSize += fileSize;
+ validFiles.add(file);
+ } catch (IOException e) {
+ log.error("Error normalizing path for file: {}", filePath, e);
+ storageMessages("Error normalizing path for file: " + filePath, messages[0]);
+ return false;
+ }
+ }
+ if (validFiles.isEmpty()) {
+ log.error(PURGED_ARCHIVE);
+ storageMessages(PURGED_ARCHIVE, messages[0]);
+ return false;
+ }
+ if (configuration.maxCombinedFileSize > -1 && totalSize > configuration.maxCombinedFileSize) {
+ log.error(MAX_FILESIZE_EXCEEDED + configuration.maxCombinedFileSize);
+ storageMessages(MAX_FILESIZE_EXCEEDED + configuration.maxCombinedFileSize, messages[0]);
+ return false;
+ }
+ if (configuration.maxFileCount > -1 && validFiles.size() > configuration.maxFileCount) {
+ log.error(TOO_MANY_FILES + configuration.maxFileCount);
+ storageMessages(TOO_MANY_FILES + configuration.maxFileCount, messages[0]);
+ return false;
+ }
+ try {
+ File targetFile = new File(path);
+ File targetDir = targetFile.getParentFile();
+ if (path.contains("/../") || path.contains("../") || path.contains("/..")) {
+ log.error(DIRECTORY_ATTACK + path);
+ storageMessages(DIRECTORY_ATTACK + path, messages[0]);
+ return false;
+ }
+ if (configuration.targetDirectory != null && !configuration.targetDirectory.isEmpty()) {
+ File configTargetDir = new File(configuration.targetDirectory);
+ String normalizedTargetPath = targetDir.getCanonicalPath();
+ String normalizedConfigPath = configTargetDir.getCanonicalPath();
+ if (!normalizedTargetPath.startsWith(normalizedConfigPath)) {
+ log.error(DIRECTORY_ATTACK + path);
+ storageMessages(DIRECTORY_ATTACK + path, messages[0]);
+ return false;
+ }
+ }
+ } catch (IOException e) {
+ log.error("Error validating target path: {}", path, e);
+ storageMessages("Error validating target path: " + path, messages[0]);
+ return false;
+ }
+ File[] toCompress = validFiles.toArray(new File[0]);
+ String format = CommonUtil.getFileType(path).toLowerCase();
+ try {
+ switch (format.toLowerCase()) {
+ case "zip":
+ compressToZip(toCompress, path);
+ break;
+ case "7z":
+ compressToSevenZ(toCompress, path);
+ break;
+ case "tar":
+ compressToTar(toCompress, path);
+ break;
+ case "gz":
+ compressToGzip(toCompress, path);
+ break;
+ case "jar":
+ compressToJar(toCompress, path);
+ break;
+ default:
+ log.error(format + UNSUPPORTED_FORMAT);
+ storageMessages(format + UNSUPPORTED_FORMAT, messages[0]);
+ return false;
+ }
+ return true;
+ } catch (Exception e) {
+ log.error(GENERIC_ERROR, e);
+ storageMessages(e.getMessage(), messages[0]);
+ return false;
+ }
+ }
+
+ /**
+ * Compresses files interactively, add files to a collection until the GXCompressor.compress method is executed
+ *
+ * @param path Target path for the compressed archive
+ * @param configuration Configuration parameters for decompression
+ * @param messages Collection to store output messages
+ * @return Boolean indicating success or failure of decompression operation
+ */
+ public static Compression newCompression(String path, CompressionConfiguration configuration, GXBaseCollection[] messages) {
+ return new Compression(path, configuration, messages);
+ }
+
+ /**
+ * Decompresses an archive file to the specified path based on configuration parameters.
+ *
+ * @param file Path to the archive file to decompress
+ * @param path Target path for the decompressed files
+ * @param configuration Configuration parameters for decompression
+ * @param messages Collection to store output messages
+ * @return Boolean indicating success or failure of decompression operation
+ */
+ public static Boolean decompress(String file, String path, CompressionConfiguration configuration, GXBaseCollection[] messages) {
+ File archiveFile = new File(file);
+ if (!archiveFile.exists()) {
+ log.error("{}{}", FILE_NOT_EXISTS, archiveFile.getAbsolutePath());
+ storageMessages(FILE_NOT_EXISTS + archiveFile.getAbsolutePath(), messages[0]);
+ return false;
+ }
+ if (archiveFile.length() == 0L) {
+ log.error("{}{}", EMPTY_FILE, file);
+ storageMessages(EMPTY_FILE + file, messages[0]);
+ return false;
+ }
+ int fileCount;
+ try {
+ fileCount = CompressionUtils.countArchiveEntries(archiveFile);
+ if (fileCount <= 0) {
+ log.error("{}{}", EMPTY_FILE, file);
+ storageMessages(EMPTY_FILE + file, messages[0]);
+ return false;
+ }
+ } catch (Exception e) {
+ log.error(PROCESSING_ERROR + file, e);
+ storageMessages(PROCESSING_ERROR + file, messages[0]);
+ return false;
+ }
+ try {
+ File targetDir = new File(path);
+ if (path.contains("/../") || path.contains("../") || path.contains("/..")) {
+ log.error(DIRECTORY_ATTACK + path);
+ storageMessages(DIRECTORY_ATTACK + path, messages[0]);
+ return false;
+ }
+ if (configuration.targetDirectory != null && !configuration.targetDirectory.isEmpty()) {
+ File configTargetDir = new File(configuration.targetDirectory);
+ String normalizedTargetPath = targetDir.getCanonicalPath();
+ String normalizedConfigPath = configTargetDir.getCanonicalPath();
+
+ if (!normalizedTargetPath.startsWith(normalizedConfigPath)) {
+ log.error(DIRECTORY_ATTACK + path);
+ storageMessages(DIRECTORY_ATTACK + path, messages[0]);
+ return false;
+ }
+ }
+ } catch (IOException e) {
+ log.error("Error validating target path: {}", path, e);
+ storageMessages("Error validating target path: " + path, messages[0]);
+ return false;
+ }
+ try {
+ if (!CompressionUtils.isArchiveSafe(archiveFile, path)) {
+ log.error(ZIP_SLIP_DETECTED + file);
+ storageMessages(ZIP_SLIP_DETECTED + file, messages[0]);
+ return false;
+ }
+ } catch (Exception e) {
+ log.error(PROCESSING_ERROR + file, e);
+ storageMessages(PROCESSING_ERROR + file, messages[0]);
+ return false;
+ }
+ try {
+ if (configuration.maxIndividualFileSize > -1) {
+ long maxFileSize = CompressionUtils.getMaxFileSize(archiveFile);
+ if (maxFileSize > configuration.maxIndividualFileSize) {
+ log.error(BIG_SINGLE_FILE + maxFileSize + " bytes");
+ storageMessages(BIG_SINGLE_FILE + maxFileSize + " bytes", messages[0]);
+ return false;
+ }
+ }
+ if (configuration.maxCombinedFileSize > -1) {
+ long totalSizeEstimate = CompressionUtils.estimateDecompressedSize(archiveFile);
+ if (totalSizeEstimate > configuration.maxCombinedFileSize) {
+ log.error(MAX_FILESIZE_EXCEEDED + configuration.maxCombinedFileSize);
+ storageMessages(MAX_FILESIZE_EXCEEDED + configuration.maxCombinedFileSize, messages[0]);
+ return false;
+ }
+ }
+ } catch (Exception e) {
+ log.error(ARCHIVE_SIZE_ESTIMATION_ERROR + "{}", file, e);
+ storageMessages("Error estimating archive size: " + file, messages[0]);
+ return false;
+ }
+ if (configuration.maxFileCount > -1 && fileCount > configuration.maxFileCount) {
+ log.error(TOO_MANY_FILES + configuration.maxFileCount);
+ storageMessages(TOO_MANY_FILES + configuration.maxFileCount, messages[0]);
+ return false;
+ }
+ String extension = getExtension(archiveFile.getName());
+ try {
+ switch (extension.toLowerCase()) {
+ case "zip":
+ decompressZip(archiveFile, path);
+ break;
+ case "7z":
+ decompress7z(archiveFile, path);
+ break;
+ case "tar":
+ decompressTar(archiveFile, path);
+ break;
+ case "gz":
+ decompressGzip(archiveFile, path);
+ break;
+ case "jar":
+ decompressJar(archiveFile, path);
+ break;
+ default:
+ log.error(extension + UNSUPPORTED_FORMAT);
+ storageMessages(extension + UNSUPPORTED_FORMAT, messages[0]);
+ return false;
+ }
+ return true;
+ } catch (Exception e) {
+ log.error(GENERIC_ERROR, e);
+ storageMessages(e.getMessage(), messages[0]);
+ return false;
+ }
+ }
+
+ private static void compressToZip(File[] files, String outputPath) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream(outputPath);
+ ZipOutputStream zipOut = new ZipOutputStream(fos)) {
+ for (File fileToZip : files) {
+ zipFile(fileToZip, fileToZip.getName(), zipOut);
+ }
+ }
+ }
+
+ private static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {
+ if (fileToZip.isHidden()) {
+ return;
+ }
+ if (fileToZip.isDirectory()) {
+ if (!fileName.endsWith("/")) {
+ fileName += "/";
+ }
+ zipOut.putNextEntry(new ZipEntry(fileName));
+ zipOut.closeEntry();
+ File[] children = fileToZip.listFiles();
+ if (children != null) {
+ for (File childFile : children) {
+ zipFile(childFile, fileName + childFile.getName(), zipOut);
+ }
+ }
+ } else {
+ try (FileInputStream fis = new FileInputStream(fileToZip)) {
+ zipOut.putNextEntry(new ZipEntry(fileName));
+ byte[] bytes = new byte[1024];
+ int length;
+ while ((length = fis.read(bytes)) >= 0) {
+ zipOut.write(bytes, 0, length);
+ }
+ }
+ }
+ }
+
+ private static void compressToSevenZ(File[] files, String outputPath) throws IOException {
+ if (files == null || outputPath == null) {
+ throw new IllegalArgumentException("Files and outputPath must not be null");
+ }
+ File outputFile = new File(outputPath);
+ if (outputFile.exists()) {
+ throw new IOException("Output file already exists");
+ }
+ try (SevenZOutputFile sevenZOutput = new SevenZOutputFile(outputFile)) {
+ for (File file : files) {
+ if (file == null || !file.exists()) {
+ continue;
+ }
+ addFileToSevenZ(sevenZOutput, file, file.getName());
+ }
+ }
+ }
+
+ private static void addFileToSevenZ(SevenZOutputFile sevenZOutput, File file, String entryName) throws IOException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ addFileToSevenZ(sevenZOutput, child, entryName + "/" + child.getName());
+ }
+ }
+ } else {
+ SevenZArchiveEntry entry = sevenZOutput.createArchiveEntry(file, entryName);
+ sevenZOutput.putArchiveEntry(entry);
+ try (FileInputStream fis = new FileInputStream(file)) {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = fis.read(buffer)) > 0) {
+ sevenZOutput.write(buffer, 0, len);
+ }
+ }
+ sevenZOutput.closeArchiveEntry();
+ }
+ }
+
+ private static void compressToTar(File[] files, String outputPath) throws IOException {
+ if (outputPath == null || outputPath.isEmpty()) {
+ throw new IllegalArgumentException("The output path must not be null or empty");
+ }
+ File outputFile = new File(outputPath);
+ if (outputFile.exists()) {
+ throw new IOException("Output file already exists");
+ }
+ try (FileOutputStream fos = new FileOutputStream(outputFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos);
+ TarArchiveOutputStream tarOut = new TarArchiveOutputStream(bos)) {
+ for (File file : files) {
+ if (file == null || !file.exists()) {
+ continue;
+ }
+ addFileToTar(tarOut, file, file.getName());
+ }
+ }
+ }
+
+ private static void addFileToTar(TarArchiveOutputStream tarOut, File file, String entryName) throws IOException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ addFileToTar(tarOut, child, entryName + "/" + child.getName());
+ }
+ }
+ } else {
+ TarArchiveEntry entry = new TarArchiveEntry(file, entryName);
+ entry.setSize(file.length());
+ tarOut.putArchiveEntry(entry);
+ try (FileInputStream fis = new FileInputStream(file)) {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = fis.read(buffer)) != -1) {
+ tarOut.write(buffer, 0, len);
+ }
+ }
+ tarOut.closeArchiveEntry();
+ }
+ }
+
+ private static void compressToGzip(File[] files, String outputPath) throws IOException {
+ if (files == null || files.length == 0) {
+ throw new IllegalArgumentException("No files to compress");
+ }
+ if (outputPath == null || outputPath.isEmpty()) {
+ throw new IllegalArgumentException("Output path is null or empty");
+ }
+ File outputFile = new File(outputPath);
+ if (outputFile.exists() && !outputFile.canWrite()) {
+ throw new IOException("Cannot write to output file");
+ }
+ File parentDir = outputFile.getParentFile();
+ if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
+ throw new IOException("Failed to create output directory");
+ }
+ boolean singleFile = files.length == 1 && files[0].isFile();
+ File tempFile = File.createTempFile("compress_", ".tmp", parentDir);
+ if (singleFile) {
+ try (
+ FileInputStream fis = new FileInputStream(files[0]);
+ FileOutputStream fos = new FileOutputStream(tempFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos);
+ GzipCompressorOutputStream gcos = new GzipCompressorOutputStream(bos)
+ ) {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = fis.read(buffer)) != -1) {
+ gcos.write(buffer, 0, len);
+ }
+ }
+ } else {
+ try (
+ FileOutputStream fos = new FileOutputStream(tempFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos);
+ GzipCompressorOutputStream gcos = new GzipCompressorOutputStream(bos);
+ TarArchiveOutputStream taos = new TarArchiveOutputStream(gcos)
+ ) {
+ taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
+ Stack fileStack = new Stack<>();
+ Stack pathStack = new Stack<>();
+ for (File f : files) {
+ if (f != null) {
+ fileStack.push(f);
+ pathStack.push("");
+ }
+ }
+ while (!fileStack.isEmpty()) {
+ File currentFile = fileStack.pop();
+ String path = pathStack.pop();
+ String entryName = path + currentFile.getName();
+ if (currentFile.isDirectory()) {
+ File[] children = currentFile.listFiles();
+ if (children != null && children.length > 0) {
+ for (File child : children) {
+ fileStack.push(child);
+ pathStack.push(entryName + "/");
+ }
+ } else {
+ TarArchiveEntry entry = new TarArchiveEntry(entryName + "/");
+ taos.putArchiveEntry(entry);
+ taos.closeArchiveEntry();
+ }
+ } else {
+ TarArchiveEntry entry = new TarArchiveEntry(currentFile, entryName);
+ taos.putArchiveEntry(entry);
+ try (FileInputStream fis = new FileInputStream(currentFile)) {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = fis.read(buffer)) != -1) {
+ taos.write(buffer, 0, len);
+ }
+ }
+ taos.closeArchiveEntry();
+ }
+ }
+ }
+ }
+ if (!tempFile.exists()) {
+ throw new IOException("Failed to create the archive");
+ }
+ String finalName = outputPath;
+ if (singleFile) {
+ if (!finalName.toLowerCase().endsWith(".gz")) {
+ finalName += ".gz";
+ }
+ } else {
+ if (finalName.toLowerCase().endsWith(".tar.gz")) {
+ // do nothing
+ } else if (finalName.toLowerCase().endsWith(".gz")) {
+ finalName = finalName.substring(0, finalName.length() - 3) + ".tar.gz";
+ } else {
+ finalName += ".tar.gz";
+ }
+ }
+ File finalFile = new File(finalName);
+ if (finalFile.exists() && !finalFile.delete()) {
+ throw new IOException("Failed to delete existing file with desired name");
+ }
+ if (!tempFile.renameTo(finalFile)) {
+ throw new IOException("Failed to rename archive to desired name");
+ }
+ }
+
+ private static void compressToJar(File[] files, String outputPath) throws IOException {
+ if (outputPath == null || outputPath.isEmpty()) {
+ throw new IllegalArgumentException("Output path is null or empty");
+ }
+ File outputFile = new File(outputPath);
+ if (outputFile.exists()) {
+ throw new IOException("Output file already exists");
+ }
+ try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(outputFile.toPath()))) {
+ byte[] buffer = new byte[1024];
+ for (File file : files) {
+ if (file == null || !file.exists()) {
+ continue;
+ }
+ String basePath = file.isDirectory() ? file.getCanonicalPath() : file.getParentFile().getCanonicalPath();
+ Stack stack = new Stack<>();
+ stack.push(file);
+ while (!stack.isEmpty()) {
+ File currentFile = stack.pop();
+ String entryName = currentFile.getCanonicalPath().substring(basePath.length() + 1).replace("\\", "/");
+ if (currentFile.isDirectory()) {
+ File[] subFiles = currentFile.listFiles();
+ if (subFiles != null) {
+ for (File subFile : subFiles) {
+ stack.push(subFile);
+ }
+ }
+ if (!entryName.isEmpty()) {
+ if (!entryName.endsWith("/")) {
+ entryName += "/";
+ }
+ jos.putNextEntry(new JarEntry(entryName));
+ jos.closeEntry();
+ }
+ } else {
+ FileInputStream fis = null;
+ try {
+ jos.putNextEntry(new JarEntry(entryName));
+ fis = new FileInputStream(currentFile);
+ int len;
+ while ((len = fis.read(buffer)) > 0) {
+ jos.write(buffer, 0, len);
+ }
+ jos.closeEntry();
+ } finally {
+ if (fis != null) {
+ fis.close();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void decompressZip(File archive, String directory) throws IOException {
+ byte[] buffer = new byte[1024];
+ ZipInputStream zis = new ZipInputStream(Files.newInputStream(archive.toPath()));
+ ZipEntry zipEntry = zis.getNextEntry();
+ while (zipEntry != null) {
+ File newFile = new File(directory, zipEntry.getName());
+ if (zipEntry.isDirectory()) {
+ if (!newFile.isDirectory() && !newFile.mkdirs()) {
+ throw new IOException("Failed to create directory " + newFile);
+ }
+ } else {
+ File parent = newFile.getParentFile();
+ if (!parent.isDirectory() && !parent.mkdirs()) {
+ throw new IOException("Failed to create directory " + parent);
+ }
+ FileOutputStream fos = new FileOutputStream(newFile);
+ int len;
+ while ((len = zis.read(buffer)) > 0) {
+ fos.write(buffer, 0, len);
+ }
+ fos.close();
+ }
+ zipEntry = zis.getNextEntry();
+ }
+ zis.closeEntry();
+ zis.close();
+ }
+
+ private static void decompress7z(File archive, String directory) throws IOException {
+ SevenZFile sevenZFile = new SevenZFile(archive);
+ SevenZArchiveEntry entry;
+ byte[] buffer = new byte[8192];
+ while ((entry = sevenZFile.getNextEntry()) != null) {
+ File newFile = new File(directory, entry.getName());
+ if (entry.isDirectory()) {
+ if (!newFile.isDirectory() && !newFile.mkdirs()) {
+ throw new IOException("Failed to create directory " + newFile);
+ }
+ } else {
+ File parent = newFile.getParentFile();
+ if (!parent.isDirectory() && !parent.mkdirs()) {
+ throw new IOException("Failed to create directory " + parent);
+ }
+ OutputStream out = Files.newOutputStream(newFile.toPath());
+ int bytesRead;
+ while ((bytesRead = sevenZFile.read(buffer)) != -1) {
+ out.write(buffer, 0, bytesRead);
+ }
+ out.close();
+ }
+ }
+ sevenZFile.close();
+ }
+
+ private static void decompressTar(File archive, String directory) throws IOException {
+ TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(archive.toPath()));
+ TarArchiveEntry entry;
+ byte[] buffer = new byte[8192];
+ while ((entry = tis.getNextEntry()) != null) {
+ File newFile = new File(directory, entry.getName());
+ if (entry.isDirectory()) {
+ if (!newFile.isDirectory() && !newFile.mkdirs()) {
+ throw new IOException("Failed to create directory " + newFile);
+ }
+ } else {
+ File parent = newFile.getParentFile();
+ if (!parent.isDirectory() && !parent.mkdirs()) {
+ throw new IOException("Failed to create directory " + parent);
+ }
+ OutputStream out = Files.newOutputStream(newFile.toPath());
+ int len;
+ while ((len = tis.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ }
+ out.close();
+ }
+ }
+ tis.close();
+ }
+
+ private static void decompressGzip(File archive, String directory) throws IOException {
+ if (!archive.exists() || !archive.isFile()) {
+ throw new IllegalArgumentException("The archive file does not exist or is not a file.");
+ }
+
+ File targetDir = new File(directory);
+ if (!targetDir.exists()) {
+ if (!targetDir.mkdirs()) {
+ String error = "Failed to create target directory: " + directory;
+ log.error(error);
+ throw new IOException(error);
+ }
+ } else if (!targetDir.isDirectory()) {
+ throw new IllegalArgumentException("The specified path exists but is not a directory.");
+ }
+
+ File tempFile = File.createTempFile("decompressed_", ".tmp");
+ try (
+ FileInputStream fis = new FileInputStream(archive);
+ GZIPInputStream gzipInputStream = new GZIPInputStream(fis);
+ FileOutputStream fos = new FileOutputStream(tempFile)
+ ) {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = gzipInputStream.read(buffer)) != -1) {
+ fos.write(buffer, 0, len);
+ }
+ }
+
+ boolean isTar = false;
+ try (FileInputStream tempFis = new FileInputStream(tempFile);
+ TarArchiveInputStream testTar = new TarArchiveInputStream(tempFis)) {
+ TarArchiveEntry testEntry = testTar.getNextEntry();
+ if (testEntry != null) {
+ isTar = true;
+ }
+ } catch (IOException ignored) {}
+
+ if (isTar) {
+ try (FileInputStream tarFis = new FileInputStream(tempFile);
+ TarArchiveInputStream tarInput = new TarArchiveInputStream(tarFis)) {
+
+ TarArchiveEntry entry;
+ while ((entry = tarInput.getNextEntry()) != null) {
+ File outFile = new File(targetDir, entry.getName());
+ if (entry.isDirectory()) {
+ if (!outFile.exists() && !outFile.mkdirs()) {
+ String error = "Failed to create directory: " + outFile;
+ log.error(error);
+ throw new IOException(error);
+ }
+ } else {
+ File parent = outFile.getParentFile();
+ if (!parent.exists() && !parent.mkdirs()) {
+ String error = "Failed to create parent directory: " + parent;
+ log.error(error);
+ throw new IOException(error);
+ }
+ try (FileOutputStream os = new FileOutputStream(outFile)) {
+ byte[] buffer = new byte[8192];
+ int count;
+ while ((count = tarInput.read(buffer)) != -1) {
+ os.write(buffer, 0, count);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ String name = archive.getName();
+ if (name.toLowerCase().endsWith(".gz")) {
+ name = name.substring(0, name.length() - 3);
+ }
+ File singleOutFile = new File(targetDir, name);
+ if (!tempFile.renameTo(singleOutFile)) {
+ try (
+ FileInputStream in = new FileInputStream(tempFile);
+ FileOutputStream out = new FileOutputStream(singleOutFile)
+ ) {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ }
+ }
+ }
+ }
+
+ if (!tempFile.delete()) {
+ tempFile.deleteOnExit();
+ }
+ }
+
+ private static void decompressJar(File archive, String directory) throws IOException {
+ if (!archive.exists() || !archive.isFile()) {
+ throw new IOException("Invalid archive file.");
+ }
+ File targetDir = new File(directory);
+ if (!targetDir.exists()) {
+ if (!targetDir.mkdirs()) {
+ throw new IOException("Failed to create target directory.");
+ }
+ }
+ try (JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(archive.toPath()))) {
+ JarEntry entry;
+ while ((entry = jarInputStream.getNextJarEntry()) != null) {
+ File outputFile = new File(targetDir, entry.getName());
+ if (entry.isDirectory()) {
+ if (!outputFile.exists() && !outputFile.mkdirs()) {
+ throw new IOException("Failed to create directory: " + outputFile.getAbsolutePath());
+ }
+ } else {
+ File parent = outputFile.getParentFile();
+ if (!parent.exists() && !parent.mkdirs()) {
+ throw new IOException("Failed to create parent directory: " + parent.getAbsolutePath());
+ }
+ try (FileOutputStream fos = new FileOutputStream(outputFile)) {
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = jarInputStream.read(buffer)) != -1) {
+ fos.write(buffer, 0, bytesRead);
+ }
+ }
+ }
+ jarInputStream.closeEntry();
+ }
+ }
+ }
+}
diff --git a/gxcompress/src/test/java/com/genexus/compression/TestCompression.java b/gxcompress/src/test/java/com/genexus/compression/TestCompression.java
new file mode 100644
index 000000000..8d8e0ac90
--- /dev/null
+++ b/gxcompress/src/test/java/com/genexus/compression/TestCompression.java
@@ -0,0 +1,101 @@
+package com.genexus.compression;
+
+import com.genexus.GXBaseCollection;
+import com.genexus.SdtMessages_Message;
+import com.genexus.specific.java.Connect;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.util.ArrayList;
+
+import static org.junit.Assert.*;
+
+public class TestCompression {
+
+ private ArrayList files;
+ private File testDirectory;
+ private static GXBaseCollection[] msgs;
+
+ @BeforeClass
+ public static void setUpTestSuite() {
+ Connect.init();
+ msgs = new GXBaseCollection[]{new GXBaseCollection<>()};
+ msgs[0] = new GXBaseCollection<>(SdtMessages_Message.class, "Messages.Message", "Genexus");
+ }
+
+ @Before
+ public void setUpTest() throws IOException {
+ testDirectory = Files.createTempDirectory("testCompressor").toFile();
+ files = new ArrayList<>();
+ String content = "This is a sample text to test the compression functionality.";
+ for (int i = 0; i < 3; i++) {
+ File file = new File(testDirectory, "testFile" + i + ".txt");
+ try (PrintWriter out = new PrintWriter(file)) {
+ out.println(content);
+ }
+ files.add(file.getAbsolutePath());
+ }
+ }
+
+ @After
+ public void tearDown() {
+ for (String filePath : files) {
+ new File(filePath).delete();
+ }
+ testDirectory.delete();
+ }
+
+ @Test
+ public void testCompressToZip() {
+ String outputPath = new File(testDirectory, "output.zip").getAbsolutePath();
+ Boolean result = GXCompressor.compress(files, outputPath, new CompressionConfiguration(), msgs);
+ assertTrue(result);
+ assertTrue(new File(outputPath).exists());
+ }
+
+ @Test
+ public void testCompressToSevenZ() {
+ String outputPath = new File(testDirectory, "output.7z").getAbsolutePath();
+ Boolean result = GXCompressor.compress(files, outputPath, new CompressionConfiguration(), msgs);
+ assertTrue(result);
+ assertTrue(new File(outputPath).exists());
+ }
+
+ @Test
+ public void testCompressToTar() {
+ String outputPath = new File(testDirectory, "output.tar").getAbsolutePath();
+ Boolean result = GXCompressor.compress(files, outputPath, new CompressionConfiguration(), msgs);
+ assertTrue(result);
+ assertTrue(new File(outputPath).exists());
+ }
+
+ @Test
+ public void testCompressToGzip() {
+ String outputPath = new File(testDirectory, "output.gz").getAbsolutePath();
+ ArrayList singleFileCollection = new ArrayList<>();
+ singleFileCollection.add(files.get(0));
+ Boolean result = GXCompressor.compress(singleFileCollection, outputPath, new CompressionConfiguration(), msgs);
+ assertTrue(result);
+ assertTrue(new File(outputPath).exists());
+ }
+
+ @Test
+ public void testCompressToJar() {
+ String outputPath = new File(testDirectory, "output.jar").getAbsolutePath();
+ Boolean result = GXCompressor.compress(files, outputPath, new CompressionConfiguration(), msgs);
+ assertTrue(result);
+ assertTrue(new File(outputPath).exists());
+ }
+
+ @Test
+ public void testUnsupportedFormat() {
+ String outputPath = new File(testDirectory, "output.unknown").getAbsolutePath();
+ Boolean result = GXCompressor.compress(files, outputPath, new CompressionConfiguration(), msgs);
+ assertFalse(result);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 61a83c19a..58ab625db 100644
--- a/pom.xml
+++ b/pom.xml
@@ -110,14 +110,15 @@
gxcloudstorage-azureblob
gxcloudstorage-ibmcos
gxcloudstorage-tests
- gxobservability
- gxcloudstorage-awss3-v2
- securityapicommons
- gxjwt
- gxcryptography
- gxxmlsignature
- gxsftp
- gxftps
+ gxobservability
+ gxcloudstorage-awss3-v2
+ gxcompress
+ securityapicommons
+ gxjwt
+ gxcryptography
+ gxxmlsignature
+ gxsftp
+ gxftps