zip rewritten to memory
This commit is contained in:
parent
d51b53bb4b
commit
07434a9fd6
@ -12,9 +12,14 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
@ -25,6 +30,12 @@ import org.apache.commons.compress.archivers.sevenz.SevenZFile;
|
||||
import cz.kamma.kfmanager.model.FileItem;
|
||||
import cz.kamma.kfmanager.model.FtpProfile;
|
||||
import net.lingala.zip4j.ZipFile;
|
||||
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
|
||||
import net.lingala.zip4j.model.ZipParameters;
|
||||
import net.lingala.zip4j.model.enums.AesKeyStrength;
|
||||
import net.lingala.zip4j.model.enums.CompressionLevel;
|
||||
import net.lingala.zip4j.model.enums.CompressionMethod;
|
||||
import net.lingala.zip4j.model.enums.EncryptionMethod;
|
||||
|
||||
/**
|
||||
* Service for file operations - copy, move, delete, etc.
|
||||
@ -1055,6 +1066,67 @@ public class FileOperations {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract ZIP/JAR/WAR by first loading archive bytes to memory and then
|
||||
* reading entries from ZipInputStream.
|
||||
*/
|
||||
public static void extractZipArchiveFromMemory(File archive, File targetDir, ProgressCallback callback) throws IOException {
|
||||
if (!targetDir.exists() && !targetDir.mkdirs()) {
|
||||
throw new IOException("Failed to create target directory: " + targetDir);
|
||||
}
|
||||
|
||||
String name = archive.getName().toLowerCase();
|
||||
if (!(name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war"))) {
|
||||
throw new IOException("Unsupported archive format for in-memory zip extraction: " + archive.getName());
|
||||
}
|
||||
|
||||
// Encrypted archives need zip4j handling with password prompt.
|
||||
try (ZipFile zipFile = new ZipFile(archive)) {
|
||||
if (zipFile.isEncrypted()) {
|
||||
extractZip(archive, targetDir, callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] zipBytes = Files.readAllBytes(archive.toPath());
|
||||
long total = countZipEntries(zipBytes);
|
||||
long current = 0;
|
||||
byte[] buffer = new byte[65536];
|
||||
|
||||
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
|
||||
ZipEntry entry;
|
||||
while ((entry = zis.getNextEntry()) != null) {
|
||||
if (callback != null && callback.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
File targetFile = secureZipTarget(targetDir, entry.getName());
|
||||
if (entry.isDirectory()) {
|
||||
targetFile.mkdirs();
|
||||
} else {
|
||||
File parent = targetFile.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile), 65536)) {
|
||||
int len;
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
if (callback != null && callback.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current++;
|
||||
if (callback != null) {
|
||||
callback.onProgress(current, total, entry.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractZip(File archive, File targetDir, ProgressCallback callback) throws IOException {
|
||||
String password = null;
|
||||
try (ZipFile zipFile = new ZipFile(archive)) {
|
||||
@ -1077,6 +1149,26 @@ public class FileOperations {
|
||||
}
|
||||
}
|
||||
|
||||
private static long countZipEntries(byte[] zipBytes) throws IOException {
|
||||
long total = 0;
|
||||
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
|
||||
while (zis.getNextEntry() != null) {
|
||||
total++;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private static File secureZipTarget(File targetDir, String entryName) throws IOException {
|
||||
File targetFile = new File(targetDir, entryName);
|
||||
String targetDirPath = targetDir.getCanonicalPath() + File.separator;
|
||||
String targetFilePath = targetFile.getCanonicalPath();
|
||||
if (!targetFilePath.startsWith(targetDirPath) && !targetFilePath.equals(targetDir.getCanonicalPath())) {
|
||||
throw new IOException("Blocked unsafe ZIP entry path: " + entryName);
|
||||
}
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
private static void extract7z(File archive, File targetDir, ProgressCallback callback) throws IOException {
|
||||
String password = null;
|
||||
SevenZFile szf = null;
|
||||
@ -1208,34 +1300,73 @@ public class FileOperations {
|
||||
throw new IOException("Source directory for rewrite does not exist");
|
||||
}
|
||||
|
||||
if (targetArchive.exists()) {
|
||||
targetArchive.delete();
|
||||
}
|
||||
List<File> allEntries = listArchiveEntries(sourceDir);
|
||||
long total = allEntries.size();
|
||||
long current = 0;
|
||||
byte[] buffer = new byte[65536];
|
||||
|
||||
try (ZipFile zipFile = new ZipFile(targetArchive)) {
|
||||
if (password != null && !password.isEmpty()) {
|
||||
zipFile.setPassword(password.toCharArray());
|
||||
}
|
||||
ByteArrayOutputStream archiveBuffer = new ByteArrayOutputStream();
|
||||
char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null;
|
||||
try (ZipOutputStream zipStream = (passwordChars != null)
|
||||
? new ZipOutputStream(archiveBuffer, passwordChars)
|
||||
: new ZipOutputStream(archiveBuffer)) {
|
||||
|
||||
net.lingala.zip4j.model.ZipParameters params = new net.lingala.zip4j.model.ZipParameters();
|
||||
if (password != null && !password.isEmpty()) {
|
||||
params.setEncryptFiles(true);
|
||||
params.setEncryptionMethod(net.lingala.zip4j.model.enums.EncryptionMethod.AES);
|
||||
params.setAesKeyStrength(net.lingala.zip4j.model.enums.AesKeyStrength.KEY_STRENGTH_256);
|
||||
}
|
||||
for (File entry : allEntries) {
|
||||
if (callback != null && callback.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
File[] children = sourceDir.listFiles();
|
||||
if (children != null) {
|
||||
for (File child : children) {
|
||||
if (callback != null && callback.isCancelled()) break;
|
||||
if (child.isDirectory()) {
|
||||
zipFile.addFolder(child, params);
|
||||
} else {
|
||||
zipFile.addFile(child, params);
|
||||
String relativePath = sourceDir.toPath().relativize(entry.toPath()).toString().replace(File.separatorChar, '/');
|
||||
if (relativePath.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ZipParameters params = new ZipParameters();
|
||||
params.setCompressionMethod(CompressionMethod.DEFLATE);
|
||||
params.setCompressionLevel(CompressionLevel.NORMAL);
|
||||
params.setFileNameInZip(entry.isDirectory() ? relativePath + "/" : relativePath);
|
||||
|
||||
if (passwordChars != null && !entry.isDirectory()) {
|
||||
params.setEncryptFiles(true);
|
||||
params.setEncryptionMethod(EncryptionMethod.AES);
|
||||
params.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
|
||||
}
|
||||
|
||||
zipStream.putNextEntry(params);
|
||||
if (!entry.isDirectory()) {
|
||||
try (InputStream in = new BufferedInputStream(new FileInputStream(entry), 65536)) {
|
||||
int len;
|
||||
while ((len = in.read(buffer)) > 0) {
|
||||
if (callback != null && callback.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
zipStream.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
zipStream.closeEntry();
|
||||
|
||||
current++;
|
||||
if (callback != null) {
|
||||
callback.onProgress(current, total, relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(targetArchive), 65536)) {
|
||||
archiveBuffer.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<File> listArchiveEntries(File sourceDir) throws IOException {
|
||||
List<File> entries = new ArrayList<>();
|
||||
try (Stream<Path> stream = Files.walk(sourceDir.toPath())) {
|
||||
stream
|
||||
.filter(path -> !path.equals(sourceDir.toPath()))
|
||||
.sorted(Comparator.comparingInt(path -> path.getNameCount()))
|
||||
.forEach(path -> entries.add(path.toFile()));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
public enum OverwriteResponse {
|
||||
|
||||
@ -38,10 +38,12 @@ import java.util.Map;
|
||||
import java.util.Locale;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.PosixFileAttributes;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.nio.file.attribute.DosFileAttributes;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Single tab in a panel - displays the contents of one directory
|
||||
@ -4681,10 +4683,11 @@ public class FilePanelTab extends JPanel {
|
||||
|
||||
Thread extractionThread = new Thread(() -> {
|
||||
try {
|
||||
Path tempDir = java.nio.file.Files.createTempDirectory("kfmanager-archive-");
|
||||
Path tempDir = createArchiveWorkspaceDir();
|
||||
final String[] usedPassword = new String[1];
|
||||
|
||||
FileOperations.extractArchive(archive, tempDir.toFile(), new FileOperations.ProgressCallback() {
|
||||
String archiveLower = archive.getName().toLowerCase();
|
||||
FileOperations.ProgressCallback callback = new FileOperations.ProgressCallback() {
|
||||
@Override
|
||||
public void onProgress(long current, long total, String currentFile) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
@ -4763,7 +4766,13 @@ public class FilePanelTab extends JPanel {
|
||||
public FileOperations.SymlinkResponse confirmSymlink(File file) {
|
||||
return FileOperations.SymlinkResponse.FOLLOW;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (archiveLower.endsWith(".zip") || archiveLower.endsWith(".jar") || archiveLower.endsWith(".war")) {
|
||||
FileOperations.extractZipArchiveFromMemory(archive, tempDir.toFile(), callback);
|
||||
} else {
|
||||
FileOperations.extractArchive(archive, tempDir.toFile(), callback);
|
||||
}
|
||||
|
||||
currentArchivePassword = usedPassword[0];
|
||||
|
||||
@ -4785,4 +4794,13 @@ public class FilePanelTab extends JPanel {
|
||||
extractionThread.setDaemon(false);
|
||||
extractionThread.start();
|
||||
}
|
||||
|
||||
private Path createArchiveWorkspaceDir() throws IOException {
|
||||
String home = System.getProperty("user.home");
|
||||
Path base = Paths.get(home, ".kfmanager", "archive-work");
|
||||
Files.createDirectories(base);
|
||||
Path dir = base.resolve("session-" + UUID.randomUUID());
|
||||
Files.createDirectories(dir);
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user