From a4dedd1324661ee05473f65cdf19e77e638f94cb Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Thu, 14 May 2026 18:55:46 +0200 Subject: [PATCH] better archive support --- .../kfmanager/service/FileOperations.java | 171 +++++++++++++++++- .../cz/kamma/kfmanager/ui/FilePanelTab.java | 16 +- 2 files changed, 173 insertions(+), 14 deletions(-) diff --git a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java index f12a3ec..71ec33a 100644 --- a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java +++ b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java @@ -735,7 +735,7 @@ public class FileOperations { public static byte[] readFileFromArchive(File archive, String entryPath) throws IOException { String name = archive.getName().toLowerCase(); try { - if (name.endsWith(".zip") || name.endsWith(".jar")) { + if (name.endsWith(".car") || name.endsWith(".zip") || name.endsWith(".jar")) { try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archive))) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { @@ -817,14 +817,138 @@ public class FileOperations { public static boolean isArchiveFile(String filename) { if (filename == null) return false; String n = filename.toLowerCase(); - return n.endsWith(".war") || n.endsWith(".zip") || n.endsWith(".jar") || n.endsWith(".tar") || n.endsWith(".tar.gz") || n.endsWith(".tgz") || n.endsWith(".7z") || n.endsWith(".rar"); + return n.endsWith(".car") || n.endsWith(".war") || n.endsWith(".zip") || n.endsWith(".jar") || n.endsWith(".tar") || n.endsWith(".tar.gz") || n.endsWith(".tgz") || n.endsWith(".7z") || n.endsWith(".rar"); + } + + /** + * Checks if a file can be opened as an archive by actually attempting to read it. + * This method does NOT rely on file extension - it always tries to actually + * read and parse the file as an archive to verify its contents. + */ + public static boolean canOpenAsArchive(File file) { + if (file == null || !file.exists() || !file.isFile()) { + return false; + } + + // Always try to detect format by actually reading the file content + // This works for any file regardless of extension + return tryDetectArchiveFormat(file); + } + + /** + * Attempts to detect the archive format by trying to read the file. + * Returns true if the file can be opened as any supported archive format. + */ + private static boolean tryDetectArchiveFormat(File file) { + try { + String name = file.getName().toLowerCase(); + + // Try ZIP (includes .car, .zip, .jar, .war, and any file with zip magic) + if (name.endsWith(".car") || name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war")) { + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(file))) { + ZipEntry entry = zis.getNextEntry(); + if (entry != null) { + return true; + } + } catch (Exception ignore) { + // Not a valid zip + } + } else { + // Try ZIP for unknown extensions (zip files without extension) + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(file))) { + ZipEntry entry = zis.getNextEntry(); + if (entry != null) { + return true; + } + } catch (Exception ignore) { + // Not a valid zip + } + } + + // Try 7z + if (name.endsWith(".7z")) { + try (SevenZFile szf = new SevenZFile(file)) { + SevenZArchiveEntry entry = szf.getNextEntry(); + if (entry != null) { + return true; + } + } catch (Exception ignore) { + // Not a valid 7z + } + } else { + // Try 7z for unknown extensions + try (SevenZFile szf = new SevenZFile(file)) { + SevenZArchiveEntry entry = szf.getNextEntry(); + if (entry != null) { + return true; + } + } catch (Exception ignore) { + // Not a valid 7z + } + } + + // Try RAR + if (name.endsWith(".rar")) { + try (com.github.junrar.Archive rar = new com.github.junrar.Archive(file)) { + if (rar.getFileHeaders().size() > 0) { + return true; + } + } catch (Exception ignore) { + // Not a valid rar + } + } else { + // Try RAR for unknown extensions + try (com.github.junrar.Archive rar = new com.github.junrar.Archive(file)) { + if (rar.getFileHeaders().size() > 0) { + return true; + } + } catch (Exception ignore) { + // Not a valid rar + } + } + + // Try TAR/TAR.GZ + if (name.endsWith(".tar") || name.endsWith(".tar.gz") || name.endsWith(".tgz")) { + try { + InputStream is = new FileInputStream(file); + if (name.endsWith(".gz") || name.endsWith(".tgz")) { + is = new org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream(is); + } + try (org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais = new org.apache.commons.compress.archivers.tar.TarArchiveInputStream(is)) { + org.apache.commons.compress.archivers.tar.TarArchiveEntry entry = tais.getNextTarEntry(); + if (entry != null) { + return true; + } + } + } catch (Exception ignore) { + // Not a valid tar + } + } else { + // Try TAR for unknown extensions + try { + InputStream is = new FileInputStream(file); + try (org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais = new org.apache.commons.compress.archivers.tar.TarArchiveInputStream(is)) { + org.apache.commons.compress.archivers.tar.TarArchiveEntry entry = tais.getNextTarEntry(); + if (entry != null) { + return true; + } + } + } catch (Exception ignore) { + // Not a valid tar + } + } + + return false; + } catch (Exception e) { + return false; + } } private static void searchInArchiveCombined(File archive, String patternLower, Pattern filenameRegex, Pattern contentPattern, boolean caseSensitive, SearchCallback callback) { if (callback != null && callback.isCancelled()) return; String name = archive.getName().toLowerCase(); try { - if (name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war")) { + if (name.endsWith(".car") || name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war")) { try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archive))) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { @@ -1037,7 +1161,7 @@ public class FileOperations { String name = archive.getName().toLowerCase(); try { - if (name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war")) { + if (name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war") || name.endsWith(".car")) { extractZip(archive, targetDir, callback); } else if (name.endsWith(".7z")) { extract7z(archive, targetDir, callback); @@ -1046,7 +1170,40 @@ public class FileOperations { } else if (name.endsWith(".tar") || name.endsWith(".tar.gz") || name.endsWith(".tgz")) { extractTar(archive, targetDir, callback); } else { - throw new IOException("Unsupported archive format: " + name); + // For unknown extensions, try to detect format by attempting to read + // First try ZIP (most common) + try { + extractZip(archive, targetDir, callback); + return; // Success + } catch (IOException zipEx) { + // Not a zip, try other formats + } + + // Try 7z + try { + extract7z(archive, targetDir, callback); + return; // Success + } catch (IOException sevenZEx) { + // Not a 7z + } + + // Try RAR + try { + extractRar(archive, targetDir, callback); + return; // Success + } catch (IOException rarEx) { + // Not a rar + } + + // Try TAR/TAR.GZ + try { + extractTar(archive, targetDir, callback); + return; // Success + } catch (IOException tarEx) { + // Not a tar + } + + throw new IOException("Unsupported or invalid archive format: " + name); } } catch (Exception e) { if (callback != null) { @@ -1066,7 +1223,7 @@ public class FileOperations { } String name = archive.getName().toLowerCase(); - if (!(name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war"))) { + if (!(name.endsWith(".car") || name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war"))) { throw new IOException("Unsupported archive format for in-memory zip extraction: " + archive.getName()); } @@ -1282,7 +1439,7 @@ public class FileOperations { public static boolean supportsArchiveRewrite(File archive) { if (archive == null) return false; String name = archive.getName().toLowerCase(); - return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war"); + return name.endsWith(".car") || name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war"); } public static void rewriteArchiveFromDirectory(File sourceDir, File targetArchive, String password, ProgressCallback callback) throws IOException { diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index b9db6b5..37fd62a 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -1162,8 +1162,10 @@ public class FilePanelTab extends JPanel { int row = fileTable.getSelectedRow(); if (row >= 0) { FileItem item = (viewMode == ViewMode.BRIEF) ? tableModel.getItemFromBriefLayout(row, briefCurrentColumn) : tableModel.getItem(row); - if (item != null && (item.isDirectory() || FileOperations.isArchiveFile(item.getFile()))) { - openSelectedItem(); + if (item != null && !item.isFtp()) { + if (item.isDirectory() || (item.getFile() != null && FileOperations.canOpenAsArchive(item.getFile()))) { + openSelectedItem(); + } } } e.consume(); @@ -2063,7 +2065,7 @@ public class FilePanelTab extends JPanel { if (item.getName().equals("..")) { navigateUp(); - } else if (FileOperations.isArchiveFile(item.getFile())) { + } else if (FileOperations.canOpenAsArchive(item.getFile())) { rememberArchiveReturnState(item.getFile()); final File archiveFile = item.getFile(); @@ -2097,7 +2099,7 @@ public class FilePanelTab extends JPanel { ); } else if (item.isDirectory()) { loadDirectory(item.getFile()); - } else if (item.getFile().isFile()) { + } else if (item.getFile() != null && item.getFile().isFile()) { openFileNative(item.getFile()); } } @@ -2610,7 +2612,7 @@ public class FilePanelTab extends JPanel { if (item.getName().equals("..")) { navigateUp(); - } else if (FileOperations.isArchiveFile(item.getFile())) { + } else if (FileOperations.canOpenAsArchive(item.getFile())) { rememberArchiveReturnState(item.getFile()); final File archiveFile = item.getFile(); @@ -2634,7 +2636,7 @@ public class FilePanelTab extends JPanel { ); } else if (item.isDirectory()) { loadDirectory(item.getFile()); - } else if (item.getFile().isFile()) { + } else if (item.getFile() != null && item.getFile().isFile()) { openFileNative(item.getFile()); } } @@ -4882,7 +4884,7 @@ public class FilePanelTab extends JPanel { } }; - if (archiveLower.endsWith(".zip") || archiveLower.endsWith(".jar") || archiveLower.endsWith(".war")) { + if (archiveLower.endsWith(".car") || archiveLower.endsWith(".zip") || archiveLower.endsWith(".jar") || archiveLower.endsWith(".war")) { FileOperations.extractZipArchiveFromMemory(archive, tempDir.toFile(), callback); } else { FileOperations.extractArchive(archive, tempDir.toFile(), callback);