unzip: archive containing links -> exception

0 votes
asked Nov 7, 2016 by oki (250 points)

tried to extract zip Archive which contains some links in mono on linux.

extract manually using unzip works in Linux Shell, I got following file:

lrwxrwxrwx 1 root root 8 Nov 7 18:40 ncurses.h -> curses.h

with a C# Code snippet:

using (var archive = new ZipArchive(InstallationFile))
{
    var action = ActionOnExistingFiles.OverwriteAll;
    archive.ExtractAll(DatabasePath, TransferMethod.Copy, action);
}

I got following exception on ExtractAll Method:

Rebex.IO.Compression.ZipException Link detected ('\/pgsql\/include\/ncurses\/ncurses.h'). Rebex.BatchProblemEventArgs HandleException(System.Exception, Rebex.IO.TransferProblemType, Rebex.TransferItem, Rebex.TransferItem, Rebex.BatchProblemReactions, Rebex.BatchProblemReactions, Rebex.BatchProblemReactions ByRef)   at Rebex.BatchTransfer.HandleException (System.Exception ex, Rebex.IO.TransferProblemType type, Rebex.TransferItem remoteItem, Rebex.TransferItem localItem, Rebex.BatchProblemReactions defaultAction, Rebex.BatchProblemReactions possibleReactions, Rebex.BatchProblemReactions& chosenAction) [0x001b0] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.BatchTransfer.ProcessLink (Rebex.TraversalPathInfo info, Rebex.BatchProblemReactions possibleActions, Rebex.BatchProblemReactions& chosenAction) [0x0009d] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.BatchTransfer.ProcessPath (Rebex.TraversalPathInfo info) [0x00196] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.BatchTransfer.RetrieveHierarchy () [0x002a1] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.BatchTransfer.Transfer (Rebex.IO.TransferAction action, Rebex.BatchFileSetCollection sourceFilter, System.String targetPath, Rebex.IO.TransferMethod transferMethod, Rebex.IO.MoveMode moveMode, Rebex.IO.LinkProcessingMode actionOnLinks, Rebex.IO.ActionOnExistingFiles actionOnExistingFiles, Rebex.TransferItem expectedRootItem) [0x00227] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.IO.Compression.ZipBatchTransfer.Transfer (Rebex.IO.Compression.ArchiveOperation operation, Rebex.BatchFileSetCollection sourceFilter, System.String targetPath, Rebex.IO.TransferMethod transferMethod, Rebex.IO.ActionOnExistingFiles actionOnExistingFiles) [0x0002c] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.IO.Compression.ZipArchive.ExtractSync (Rebex.BatchFileSetCollection setCollection, System.String targetDirectoryPath, Rebex.IO.TransferMethod transferMethod, Rebex.IO.ActionOnExistingFiles actionOnExistingFiles) [0x0001a] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.IO.Compression.ZipArchive.Extract (System.String archivePathOrMask, System.String targetDirectoryPath, Rebex.IO.TraversalMode mode, Rebex.IO.TransferMethod transferMethod, Rebex.IO.ActionOnExistingFiles defaultActionOnExistingFiles, System.Boolean fromOldApi) [0x0001a] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.IO.Compression.ZipArchive.Extract (System.String archivePathOrMask, System.String targetDirectoryPath, Rebex.IO.TraversalMode mode, Rebex.IO.TransferMethod transferMethod, Rebex.IO.ActionOnExistingFiles defaultActionOnExistingFiles) [0x00001] in <89ad118348324dbfbe4b9279af8ac151>:0 
  at Rebex.IO.Compression.ZipArchive.ExtractAll (System.String targetDirectoryPath, Rebex.IO.TransferMethod transferMethod, Rebex.IO.ActionOnExistingFiles defaultActionOnExistingFiles) [0x0000d] in <89ad118348324dbfbe4b9279af8ac151>:0 

How to fix?

Applies to: Rebex ZIP

1 Answer

0 votes
answered Nov 7, 2016 by Lukas Matyska (59,010 points)
edited Nov 24, 2016 by Lukas Matyska

Extraction of links is not supported yet.
If you want to skip all links set MultiFileLinkMode to SkipLinks:

archive.Options.MultiFileLinkMode = LinkProcessingMode.SkipLinks;

However, you can determine the target of a link using the ZipItem.LinkTarget:

archive.GetItem("path").LinkTarget

// or

archive["path"].LinkTarget

UPDATE:

With a little effort you can extract links manually:

using (var archive = new ZipArchive(InstallationFile))
{
    // create cache of items for later use
    var items = new Dictionary<string, ZipItem>();
    foreach (var item in archive)
    {
        items.Add(item.Path, item);
    }

    // register event to handle LinkDetected problems
    archive.ProblemDetected += (s, e) =>
    {
        if (e.ProblemType == ArchiveProblemType.LinkDetected &&
            e.Operation == ArchiveOperation.Extract)
        {
            string externalPath = e.ExternalItemPath;
            string linkTarget = items[e.ArchiveItemPath].LinkTarget;

            // do what you need, e.g.:
            // var f = new UnixFileInfo(externalPath);
            // f.CreateSymbolicLink(linkTarget);                            

            // skip processing by library
            e.Action = ArchiveProblemActions.Skip;
        }
    };

    // extract all items
    var action = ActionOnExistingFiles.OverwriteAll;
    archive.ExtractAll(DatabasePath, TransferMethod.Copy, action);
}
commented Nov 24, 2016 by oki (250 points)
Thank you for your reply. unfortunately this won't work due recursive processing of directories.

Solved this by Invoke an external method in Zip/Shared/BatchTransfer.cs
and processed this myself by:
        private void ProcessLink(bool isDownload, string itemPath, string linkTarget,

            out BatchProblemReactions chosenAction)

        {

            if (isDownload)

            {

                Log.Info($"create Link {itemPath} to {linkTarget}");

                var f = new UnixFileInfo(itemPath);

                f.CreateSymbolicLink(linkTarget);

            }

            chosenAction = BatchProblemReactions.Skip;

        }
commented Nov 24, 2016 by Lukas Matyska (59,010 points)
Great, that you was able to solve the problem yourself. Actually, you brought me to a solution available for those users without source code edition. It is possible to achieve same behavior using `ProblemDetected` event. Please, see my updated answer above.
commented Nov 24, 2016 by oki (250 points)
Lukas, this works only, if LinkTarget gets public in BatchTransfer.cs.
This has to be exposed like:
        public string LinkTargetName
        {
            get
            {
                if (this._inner is ZipItem) return ((ZipItem)_inner).LinkTarget;
                return String.Empty;
            }
        }
commented Nov 24, 2016 by Lukas Matyska (59,010 points)
I don't understand. `LinkTarget` is exposed on `ZipItem`. I tried my code and it is working without exposing anything special.

The `ZipProblemDetectedEventArgs` has only `ArchiveItemPath` and `ExternalItemPath`, so I cached collection of `ZipItem`s to be able to get the `LinkTarget` for specified path. And this is enough to make it working.

However, I realized that it would be nice to have `ZipItem` on event directly. Or enable possibility to get the `ZipItem` during events for read-only access.
...