srikanth-lingala / zip4j

A Java library for zip files and streams
Apache License 2.0
2.01k stars 307 forks source link

Symlinks to directories fail to zip properly #486

Closed ajpfahnl closed 1 year ago

ajpfahnl commented 1 year ago

Toy example:

I created one directory with a symlink to it and a regular file with a symlink to it like so:

$ mkdir tmp
$ cd tmp
$ mkdir a; ln -s a b; touch c; ln -s c d
$ cd ..
$ tree tmp
tmp
├── a
├── b -> a
├── c
└── d -> c

Ideally what would happen is the following:

$ zip -yr tmp.zip tmp
$ unzip tmp.zip -d newtmp
Archive:  tmp.zip
   creating: newtmp/tmp/
   creating: newtmp/tmp/a/
 extracting: newtmp/tmp/c            
    linking: newtmp/tmp/d            -> c 
    linking: newtmp/tmp/b            -> a 
finishing deferred symbolic links:
  newtmp/tmp/d           -> c
  newtmp/tmp/b           -> a
$ tree newtmp 
newtmp
└── tmp
    ├── a
    ├── b -> a
    ├── c
    └── d -> c

I then attempted to use Zip4j to zip these files.

First attempt is a naive approach. Just setSymbolicLinkAction to INCLUDE_LINK_ONLY:

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class ZipTest {
    public static void main(String[] args) throws IOException {
        try (ZipFile zipFile = new ZipFile("test.zip");
             Stream<Path> paths = Files.walk(Paths.get("tmp"))) {
            paths.forEach(path -> {
                ZipParameters zp = new ZipParameters();
                if ((!Files.isSymbolicLink(path)) && Files.isDirectory(path)) {
                    zp.setFileNameInZip(path + "/");
                } else {
                    zp.setFileNameInZip(path.toString());
                }
                zp.setSymbolicLinkAction(ZipParameters.SymbolicLinkAction.INCLUDE_LINK_ONLY);
                try {
                    zipFile.addFile(path.toFile(), zp);
                } catch (ZipException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }
}

Let's see what it contains:

$ unzip test.zip -d test && tree test
Archive:  test.zip
   creating: test/tmp/
   creating: test/tmp/a/
 extracting: test/tmp/c              
    linking: test/tmp/d              -> c 
 extracting: test/tmp/b              
finishing deferred symbolic links:
  test/tmp/d             -> c
test
└── tmp
    ├── a
    ├── b
    ├── c
    └── d -> c

2 directories, 3 files

That didn't work. The symlink b to directory a, did not stay a symlink.

Then I tried only setting INCLUDE_LINK_ONLY for paths tested to be symlinks, and manually set CompressionLevel.NO_COMPRESSION and CompressionMethod.STORE.

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.CompressionMethod;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class ZipTest {
    public static void main(String[] args) throws IOException {
        try (ZipFile zipFile = new ZipFile("test.zip");
             Stream<Path> paths = Files.walk(Paths.get("tmp"))) {
            paths.forEach(path -> {
                ZipParameters zp = new ZipParameters();
                if ((!Files.isSymbolicLink(path)) && Files.isDirectory(path)) {
                    zp.setFileNameInZip(path + "/");
                } else {
                    zp.setFileNameInZip(path.toString());
                }
                if (Files.isSymbolicLink(path)) {
                    zp.setSymbolicLinkAction(ZipParameters.SymbolicLinkAction.INCLUDE_LINK_ONLY);
                    zp.setCompressionLevel(CompressionLevel.NO_COMPRESSION);
                    zp.setCompressionMethod(CompressionMethod.STORE);
                }
                try {
                    zipFile.addFile(path.toFile(), zp);
                } catch (ZipException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }
}

Unzipping output:

$ unzip test.zip -d test   
Archive:  test.zip
   creating: test/tmp/
   creating: test/tmp/a/
 extracting: test/tmp/c              
    linking: test/tmp/d              -> c 
 extracting: test/tmp/b              
finishing deferred symbolic links:
  test/tmp/d             -> c

It still won't link b to a!

srikanth-lingala commented 1 year ago

Thanks for the detailed analysis of the issue. It always helps to have a detailed description and a code sample to reproduce the issue. I am looking into this issue.

srikanth-lingala commented 1 year ago

Fixed in v2.11.4 released today

ajpfahnl commented 1 year ago

Thank you for working on this!