Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package plexus-archiver for openSUSE:Factory 
checked in at 2026-10-20 12:38:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/plexus-archiver (Old)
 and      /work/SRC/openSUSE:Factory/.plexus-archiver.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "plexus-archiver"

Tue Oct 20 12:38:36 2026 rev:16 rq:1386171 version:4.15.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/plexus-archiver/plexus-archiver.changes  
2026-09-29 17:50:45.313651025 +0200
+++ 
/work/SRC/openSUSE:Factory/.plexus-archiver.new.3698736/plexus-archiver.changes 
    2026-10-20 12:40:13.085249972 +0200
@@ -1,0 +2,18 @@
+Mon Oct 19 05:17:04 UTC 2026 - Fridrich Strba <[email protected]>
+
+- Upgrade to upstream version 4.15.0
+  * Bug Fixes
+    + Copy the target when DirectoryArchiver cannot create a
+      symbolic link
+    + Copy the target when the file system refuses a symbolic link
+  * Documentation updates
+    + Port the site index fixes from master
+  * Build
+    + Port the CI workflow updates from master
+  * Dependency updates
+    + Bump release-drafter/release-drafter from 7.7.0 to 7.10.0
+    + Bump com.google.guava:guava from 33.7.1-jre to 33.7.2-jre
+    + Bump org.codehaus.plexus:plexus-io from 3.7.0 to 3.8.0
+    + Bump com.github.luben:zstd-jni from 1.5.7-15 to 1.5.7-16
+
+-------------------------------------------------------------------

Old:
----
  plexus-archiver-4.14.0.tar.gz

New:
----
  plexus-archiver-4.15.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ plexus-archiver.spec ++++++
--- /var/tmp/diff_new_pack.xm63Ws/_old  2026-10-20 12:40:14.130293709 +0200
+++ /var/tmp/diff_new_pack.xm63Ws/_new  2026-10-20 12:40:14.132293793 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           plexus-archiver
-Version:        4.14.0
+Version:        4.15.0
 Release:        0
 Summary:        Plexus Archiver Component
 License:        Apache-2.0

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.xm63Ws/_old  2026-10-20 12:40:14.162295048 +0200
+++ /var/tmp/diff_new_pack.xm63Ws/_new  2026-10-20 12:40:14.166295216 +0200
@@ -1,6 +1,6 @@
-mtime: 1790595406
-commit: 0ecb4dec4a28449892c938960637f3e0cdcfbabcda3db3c6c06a8fad3f4f537c
+mtime: 1792387637
+commit: b7215640fe2417e689c5df01899f92af61f901ad999f3a048268801946ebf761
 url: https://src.opensuse.org/java-packages/plexus-archiver
-revision: 0ecb4dec4a28449892c938960637f3e0cdcfbabcda3db3c6c06a8fad3f4f537c
+revision: b7215640fe2417e689c5df01899f92af61f901ad999f3a048268801946ebf761
 projectscmsync: https://src.opensuse.org/java-packages/_ObsPrj
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-10-19 07:27:17.000000000 +0200
@@ -0,0 +1 @@
+.osc

++++++ plexus-archiver-4.14.0.tar.gz -> plexus-archiver-4.15.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/.github/workflows/maven.yml 
new/plexus-archiver-plexus-archiver-4.15.0/.github/workflows/maven.yml
--- old/plexus-archiver-plexus-archiver-4.14.0/.github/workflows/maven.yml      
2026-08-23 14:43:30.000000000 +0200
+++ new/plexus-archiver-plexus-archiver-4.15.0/.github/workflows/maven.yml      
2026-10-19 02:47:32.000000000 +0200
@@ -17,7 +17,7 @@
 
 name: GitHub CI
 
-on: [push, pull_request]
+on: [push, pull_request, workflow_dispatch]
 
 jobs:
   build:
@@ -29,7 +29,7 @@
       # https://github.com/codehaus-plexus/plexus-archiver/issues/164
       matrix-include: >
         [ 
-          {"jdk": "17.0.2", "os": "windows-latest", distribution: "zulu" }
+          {"jdk": "17.0.2", "os": "windows-latest", "distribution": "zulu" }
         ]
 
 #   deploy:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/.github/workflows/release-drafter.yml
 
new/plexus-archiver-plexus-archiver-4.15.0/.github/workflows/release-drafter.yml
--- 
old/plexus-archiver-plexus-archiver-4.14.0/.github/workflows/release-drafter.yml
    2026-08-23 14:43:30.000000000 +0200
+++ 
new/plexus-archiver-plexus-archiver-4.15.0/.github/workflows/release-drafter.yml
    2026-10-19 02:47:32.000000000 +0200
@@ -7,7 +7,7 @@
   update_release_draft:
     runs-on: ubuntu-latest
     steps:
-      - uses: release-drafter/[email protected]
+      - uses: release-drafter/[email protected]
         with:
           # Resolved against .github/ on the branch this workflow runs on, not 
on the default branch.
           config-name: 'release-drafter-4.x.yml'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/plexus-archiver-plexus-archiver-4.14.0/pom.xml 
new/plexus-archiver-plexus-archiver-4.15.0/pom.xml
--- old/plexus-archiver-plexus-archiver-4.14.0/pom.xml  2026-08-23 
14:43:30.000000000 +0200
+++ new/plexus-archiver-plexus-archiver-4.15.0/pom.xml  2026-10-19 
02:47:32.000000000 +0200
@@ -9,7 +9,7 @@
   </parent>
 
   <artifactId>plexus-archiver</artifactId>
-  <version>4.14.0</version>
+  <version>4.15.0</version>
   <name>Plexus Archiver Component</name>
   <description>One API for creating and extracting archives - zip, jar, tar 
and their compressed
     variants - regardless of format.</description>
@@ -32,7 +32,7 @@
   <scm>
     
<connection>scm:git:https://github.com/codehaus-plexus/plexus-archiver.git</connection>
     
<developerConnection>scm:git:https://github.com/codehaus-plexus/plexus-archiver.git</developerConnection>
-    <tag>plexus-archiver-4.14.0</tag>
+    <tag>plexus-archiver-4.15.0</tag>
     
<url>https://github.com/codehaus-plexus/plexus-archiver/tree/${project.scm.tag}/</url>
   </scm>
   <issueManagement>
@@ -49,7 +49,7 @@
   <properties>
     <slf4jVersion>1.7.36</slf4jVersion>
     <version.sisu>1.1.0</version.sisu>
-    
<project.build.outputTimestamp>2026-08-23T12:40:45Z</project.build.outputTimestamp>
+    
<project.build.outputTimestamp>2026-10-19T00:47:10Z</project.build.outputTimestamp>
   </properties>
 
   <dependencies>
@@ -68,7 +68,7 @@
     <dependency>
       <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-io</artifactId>
-      <version>3.7.0</version>
+      <version>3.8.0</version>
     </dependency>
     <dependency>
       <groupId>org.apache.commons</groupId>
@@ -94,7 +94,7 @@
     <dependency>
       <groupId>com.github.luben</groupId>
       <artifactId>zstd-jni</artifactId>
-      <version>1.5.7-15</version>
+      <version>1.5.7-16</version>
       <scope>runtime</scope>
     </dependency>
     <dependency>
@@ -140,7 +140,7 @@
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
-      <version>33.7.1-jre</version>
+      <version>33.7.2-jre</version>
       <scope>test</scope>
     </dependency>
     <dependency>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java
 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java
--- 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java
       2026-08-23 14:43:30.000000000 +0200
+++ 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java
       2026-10-19 02:47:32.000000000 +0200
@@ -29,6 +29,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
+import org.codehaus.plexus.archiver.util.DeferredSymlinks;
 import org.codehaus.plexus.components.io.attributes.SymlinkUtils;
 import org.codehaus.plexus.components.io.filemappers.FileMapper;
 import org.codehaus.plexus.components.io.fileselectors.FileSelector;
@@ -78,6 +79,16 @@
      */
     private boolean ignorePermissions = false;
 
+    /**
+     * @since 4.15.0
+     */
+    private SymlinkFailureMode symlinkFailureMode = 
SymlinkFailureMode.COPY_TARGET;
+
+    /**
+     * Symbolic links the file system refused during the current extraction, 
resolved once every entry is on disk.
+     */
+    private DeferredSymlinks deferredSymlinks;
+
     public AbstractUnArchiver() {
         // no op
     }
@@ -139,14 +150,30 @@
     @Override
     public final void extract() throws ArchiverException {
         validate();
-        execute();
+        deferredSymlinks = new DeferredSymlinks(getLogger());
+        try {
+            execute();
+            deferredSymlinks.resolve();
+        } catch (IOException e) {
+            throw new ArchiverException("Error while copying symbolic link 
targets", e);
+        } finally {
+            deferredSymlinks = null;
+        }
         runArchiveFinalizers();
     }
 
     @Override
     public final void extract(final String path, final File outputDirectory) 
throws ArchiverException {
         validate(path, outputDirectory);
-        execute(path, outputDirectory);
+        deferredSymlinks = new DeferredSymlinks(getLogger());
+        try {
+            execute(path, outputDirectory);
+            deferredSymlinks.resolve();
+        } catch (IOException e) {
+            throw new ArchiverException("Error while copying symbolic link 
targets", e);
+        } finally {
+            deferredSymlinks = null;
+        }
         runArchiveFinalizers();
     }
 
@@ -273,6 +300,25 @@
         this.ignorePermissions = ignorePermissions;
     }
 
+    /**
+     * @since 4.15.0
+     */
+    @Override
+    public SymlinkFailureMode getSymlinkFailureMode() {
+        return symlinkFailureMode;
+    }
+
+    /**
+     * @since 4.15.0
+     */
+    @Override
+    public void setSymlinkFailureMode(final SymlinkFailureMode 
symlinkFailureMode) {
+        if (symlinkFailureMode == null) {
+            throw new IllegalArgumentException("symlinkFailureMode must not be 
null");
+        }
+        this.symlinkFailureMode = symlinkFailureMode;
+    }
+
     protected void extractFile(
             final File srcF,
             final File dir,
@@ -320,7 +366,9 @@
             }
 
             if (!StringUtils.isEmpty(symlinkDestination)) {
-                SymlinkUtils.createSymbolicLink(targetFileName, new 
File(symlinkDestination));
+                if (!createSymlinkEntry(canonicalDirPath, targetFileName, 
symlinkDestination, entryName)) {
+                    return;
+                }
             } else if (isDirectory) {
                 targetFileName.mkdirs();
             } else {
@@ -337,6 +385,48 @@
         }
     }
 
+    /**
+     * Creates a symbolic link. Visible for testing, so a test can simulate a 
file system that refuses links.
+     *
+     * @param link the link to create
+     * @param target the link target as stored in the archive
+     * @throws IOException if the link cannot be created
+     * @since 4.15.0
+     */
+    protected void createSymbolicLink(File link, File target) throws 
IOException {
+        SymlinkUtils.createSymbolicLink(link, target);
+    }
+
+    /**
+     * @return {@code true} if the link exists on disk, {@code false} if it 
was skipped or deferred
+     */
+    private boolean createSymlinkEntry(Path root, File link, String target, 
String entryName) throws IOException {
+        try {
+            createSymbolicLink(link, new File(target));
+            return true;
+        } catch (IOException | UnsupportedOperationException e) {
+            if (symlinkFailureMode == SymlinkFailureMode.FAIL) {
+                if (e instanceof IOException) {
+                    throw (IOException) e;
+                }
+                throw new IOException("Cannot create symbolic link " + link, 
e);
+            }
+            boolean copy = symlinkFailureMode == 
SymlinkFailureMode.COPY_TARGET && deferredSymlinks != null;
+            getLogger()
+                    .warn(
+                            "Cannot create symbolic link {} -> {} ({}). On 
Windows this needs the 'Create symbolic"
+                                    + " links' right or Developer Mode; {}.",
+                            entryName,
+                            target,
+                            e.getMessage(),
+                            copy ? "the link target will be copied instead" : 
"the entry is skipped");
+            if (copy) {
+                deferredSymlinks.add(root, link, target, entryName);
+            }
+            return false;
+        }
+    }
+
     /**
      * Counter for casing message emitted, visible for testing.
      */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/SymlinkFailureMode.java
 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/SymlinkFailureMode.java
--- 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/SymlinkFailureMode.java
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/SymlinkFailureMode.java
       2026-10-19 02:47:32.000000000 +0200
@@ -0,0 +1,40 @@
+/*
+ * Copyright The Plexus developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.codehaus.plexus.archiver;
+
+/**
+ * What an {@link UnArchiver} does with a symbolic link entry when the file 
system refuses to create the link,
+ * for example on Windows without the "Create symbolic links" right or 
Developer Mode.
+ *
+ * @since 4.15.0
+ */
+public enum SymlinkFailureMode {
+    /**
+     * Fail the extraction.
+     */
+    FAIL,
+
+    /**
+     * Log a warning and, once every entry is extracted, copy the link target 
in place of the link.
+     * Targets outside the destination directory, missing targets and link 
cycles are skipped with a warning.
+     */
+    COPY_TARGET,
+
+    /**
+     * Log a warning and leave the entry out.
+     */
+    SKIP
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/UnArchiver.java
 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/UnArchiver.java
--- 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/UnArchiver.java
       2026-08-23 14:43:30.000000000 +0200
+++ 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/UnArchiver.java
       2026-10-19 02:47:32.000000000 +0200
@@ -126,4 +126,25 @@
      * @since 1.1
      */
     void setIgnorePermissions(final boolean ignorePermissions);
+
+    /**
+     * Returns what happens to a symbolic link entry that the file system 
refuses to create.
+     *
+     * @return the failure mode; {@link SymlinkFailureMode#FAIL} unless the 
implementation supports another
+     * @since 4.15.0
+     */
+    default SymlinkFailureMode getSymlinkFailureMode() {
+        return SymlinkFailureMode.FAIL;
+    }
+
+    /**
+     * Sets what happens to a symbolic link entry that the file system refuses 
to create.
+     *
+     * @param symlinkFailureMode the failure mode
+     * @throws UnsupportedOperationException if the implementation does not 
support this setting
+     * @since 4.15.0
+     */
+    default void setSymlinkFailureMode(SymlinkFailureMode symlinkFailureMode) {
+        throw new UnsupportedOperationException("setSymlinkFailureMode");
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/dir/DirectoryArchiver.java
 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/dir/DirectoryArchiver.java
--- 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/dir/DirectoryArchiver.java
    2026-08-23 14:43:30.000000000 +0200
+++ 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/dir/DirectoryArchiver.java
    2026-10-19 02:47:32.000000000 +0200
@@ -21,6 +21,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.attribute.FileTime;
 import java.util.ArrayList;
 import java.util.List;
@@ -29,8 +30,10 @@
 import org.codehaus.plexus.archiver.ArchiveEntry;
 import org.codehaus.plexus.archiver.ArchiverException;
 import org.codehaus.plexus.archiver.ResourceIterator;
+import org.codehaus.plexus.archiver.SymlinkFailureMode;
 import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
 import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
+import org.codehaus.plexus.archiver.util.DeferredSymlinks;
 import org.codehaus.plexus.archiver.util.ResourceUtils;
 import org.codehaus.plexus.components.io.attributes.SymlinkUtils;
 import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
@@ -45,6 +48,44 @@
     private final List<Runnable> directoryChmods = new ArrayList<>();
     private long filesCopied;
 
+    private SymlinkFailureMode symlinkFailureMode = 
SymlinkFailureMode.COPY_TARGET;
+
+    /**
+     * Returns what happens to a symbolic link that the file system refuses to 
create.
+     *
+     * @return the failure mode, {@link SymlinkFailureMode#COPY_TARGET} by 
default
+     * @since 4.15.0
+     */
+    public SymlinkFailureMode getSymlinkFailureMode() {
+        return symlinkFailureMode;
+    }
+
+    /**
+     * Sets what happens to a symbolic link that the file system refuses to 
create, for example on Windows without
+     * the "Create symbolic links" right or Developer Mode.
+     *
+     * @param symlinkFailureMode the failure mode
+     * @since 4.15.0
+     */
+    public void setSymlinkFailureMode(SymlinkFailureMode symlinkFailureMode) {
+        if (symlinkFailureMode == null) {
+            throw new IllegalArgumentException("symlinkFailureMode must not be 
null");
+        }
+        this.symlinkFailureMode = symlinkFailureMode;
+    }
+
+    /**
+     * Creates a symbolic link. Visible for testing, so a test can simulate a 
file system that refuses links.
+     *
+     * @param link the link to create
+     * @param target the link target
+     * @throws IOException if the link cannot be created
+     * @since 4.15.0
+     */
+    protected void createSymbolicLink(File link, File target) throws 
IOException {
+        SymlinkUtils.createSymbolicLink(link, target);
+    }
+
     /**
      * Default constructor.
      */
@@ -79,7 +120,9 @@
 
         getLogger().info("Copying files to {}", 
destDirectory.getAbsolutePath());
 
+        DeferredSymlinks deferredSymlinks = new DeferredSymlinks(getLogger());
         try {
+            final Path root = destDirectory.getCanonicalFile().toPath();
             while (iter.hasNext()) {
                 final ArchiveEntry f = iter.next();
                 // Check if we don't add directory file in itself
@@ -95,12 +138,35 @@
                     File target = new File(dest);
                     File symlink = new File(fileName);
                     makeParentDirectories(symlink);
-                    SymlinkUtils.createSymbolicLink(symlink, target);
+                    try {
+                        createSymbolicLink(symlink, target);
+                    } catch (IOException | UnsupportedOperationException e) {
+                        if (symlinkFailureMode == SymlinkFailureMode.FAIL) {
+                            if (e instanceof IOException) {
+                                throw (IOException) e;
+                            }
+                            throw new IOException("Cannot create symbolic link 
" + symlink, e);
+                        }
+                        boolean copy = symlinkFailureMode == 
SymlinkFailureMode.COPY_TARGET;
+                        getLogger()
+                                .warn(
+                                        "Cannot create symbolic link {} -> {} 
({}). On Windows this needs the"
+                                                + " 'Create symbolic links' 
right or Developer Mode; {}.",
+                                        f.getName(),
+                                        dest,
+                                        e.getMessage(),
+                                        copy ? "the link target will be copied 
instead" : "the entry is skipped");
+                        if (copy) {
+                            deferredSymlinks.add(root, symlink, dest, 
f.getName());
+                        }
+                    }
                 } else {
                     copyFile(f, fileName);
                 }
             }
 
+            deferredSymlinks.resolve();
+
             directoryChmods.forEach(Runnable::run);
             directoryChmods.clear();
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/util/DeferredSymlinks.java
 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/util/DeferredSymlinks.java
--- 
old/plexus-archiver-plexus-archiver-4.14.0/src/main/java/org/codehaus/plexus/archiver/util/DeferredSymlinks.java
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/plexus-archiver-plexus-archiver-4.15.0/src/main/java/org/codehaus/plexus/archiver/util/DeferredSymlinks.java
    2026-10-19 02:47:32.000000000 +0200
@@ -0,0 +1,155 @@
+/*
+ * Copyright The Plexus developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.codehaus.plexus.archiver.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.slf4j.Logger;
+
+import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
+
+/**
+ * Symbolic links the file system refused, to be replaced by a copy of their 
target once every entry is on disk.
+ *
+ * @see org.codehaus.plexus.archiver.SymlinkFailureMode#COPY_TARGET
+ * @since 4.15.0
+ */
+public final class DeferredSymlinks {
+
+    private final Logger logger;
+
+    private final List<Entry> entries = new ArrayList<>();
+
+    public DeferredSymlinks(Logger logger) {
+        this.logger = logger;
+    }
+
+    /**
+     * Records a link that could not be created.
+     *
+     * @param root the canonical directory the copy must stay inside
+     * @param link where the link would have been created; its parent 
directory must exist
+     * @param target the link target as stored in the archive or resource
+     * @param entryName the entry name, for messages
+     * @throws IOException if the parent directory cannot be canonicalized
+     */
+    public void add(Path root, File link, String target, String entryName) 
throws IOException {
+        // canonicalize the parent, not the link path, which might be an 
existing link
+        Path linkPath = 
link.getParentFile().getCanonicalFile().toPath().resolve(link.getName());
+        entries.add(new Entry(root, linkPath, target, entryName));
+    }
+
+    /**
+     * Copies the target of each recorded link in place of the link. Repeats 
until no more links resolve, so links
+     * that precede their target and chains of links work in any order. 
Targets outside the root, missing targets,
+     * cycles, occupied link paths and directories that contain the link are 
skipped with a warning.
+     *
+     * @throws IOException if copying fails
+     */
+    public void resolve() throws IOException {
+        List<Entry> pending = new ArrayList<>(entries);
+        entries.clear();
+        boolean progress = true;
+        while (progress && !pending.isEmpty()) {
+            progress = false;
+            for (Iterator<Entry> it = pending.iterator(); it.hasNext(); ) {
+                Entry link = it.next();
+                Path real;
+                try {
+                    real = link.link
+                            .resolveSibling(link.target.replace('/', 
File.separatorChar))
+                            .normalize()
+                            .toRealPath();
+                } catch (NoSuchFileException e) {
+                    // the target may be another refused link that a later 
round copies
+                    continue;
+                }
+                it.remove();
+                if (!real.startsWith(link.root)) {
+                    logger.warn(
+                            "Skipping symbolic link {}: target {} is outside 
the target directory",
+                            link.entryName,
+                            link.target);
+                } else if (Files.exists(link.link, LinkOption.NOFOLLOW_LINKS)) 
{
+                    logger.warn("Skipping symbolic link {}: {} already 
exists", link.entryName, link.link);
+                } else if (Files.isDirectory(real) && 
link.link.startsWith(real)) {
+                    logger.warn("Skipping symbolic link {}: target {} contains 
the link", link.entryName, link.target);
+                } else {
+                    copy(real, link.link);
+                    progress = true;
+                }
+            }
+        }
+        for (Entry link : pending) {
+            logger.warn(
+                    "Skipping symbolic link {}: target {} is not in the 
archive, or the links form a cycle",
+                    link.entryName,
+                    link.target);
+        }
+    }
+
+    private void copy(Path source, Path destination) throws IOException {
+        if (!Files.isDirectory(source)) {
+            Files.copy(source, destination, COPY_ATTRIBUTES);
+            return;
+        }
+        Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult preVisitDirectory(Path dir, 
BasicFileAttributes attrs) throws IOException {
+                
Files.createDirectories(destination.resolve(source.relativize(dir)));
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes 
attrs) throws IOException {
+                if (attrs.isSymbolicLink()) {
+                    logger.warn("Skipping symbolic link {} while copying {}", 
file, source);
+                } else {
+                    Files.copy(file, 
destination.resolve(source.relativize(file)), COPY_ATTRIBUTES);
+                }
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+
+    private static final class Entry {
+        final Path root;
+
+        final Path link;
+
+        final String target;
+
+        final String entryName;
+
+        Entry(Path root, Path link, String target, String entryName) {
+            this.root = root;
+            this.link = link;
+            this.target = target;
+            this.entryName = entryName;
+        }
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/src/site/markdown/index.md 
new/plexus-archiver-plexus-archiver-4.15.0/src/site/markdown/index.md
--- old/plexus-archiver-plexus-archiver-4.14.0/src/site/markdown/index.md       
2026-08-23 14:43:30.000000000 +0200
+++ new/plexus-archiver-plexus-archiver-4.15.0/src/site/markdown/index.md       
2026-10-19 02:47:32.000000000 +0200
@@ -1,12 +1,19 @@
+---
+title: Plexus Archiver
+author:
+  - Hervé Boutemy
+date: 2018-05-08
+---
+
 # Plexus Archiver
 
 Collection of Plexus components to create archives or extract archives to a 
directory with a unified 
[`Archiver`](./apidocs/index.html?org/codehaus/plexus/archiver/Archiver.html)/[`UnArchiver`](./apidocs/index.html?org/codehaus/plexus/archiver/UnArchiver.html)
 API whatever the archive format is.
 
-| Interface                                                                    
     | Components for supported formats (as Plexus role hint)                   
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                      
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                      
                                                                                
                                                                                
                                                                                
                                                                                
                      |
-|:----------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| 
[`Archiver`](./apidocs/index.html?org/codehaus/plexus/archiver/Archiver.html)   
  | 
[`bzip2`](./apidocs/index.html?org/codehaus/plexus/archiver/bzip2/BZip2Archiver.html),
 
[`dir`](./apidocs/index.html?org/codehaus/plexus/archiver/dir/DirectoryArchiver.html),
 
[`ear`](./apidocs/index.html?org/codehaus/plexus/archiver/ear/EarArchiver.html),
 
[`gzip`](./apidocs/index.html?org/codehaus/plexus/archiver/gzip/GZipArchiver.html),
 
[`jar`](./apidocs/index.html?org/codehaus/plexus/archiver/jar/JarArchiver.html),
 
[`mjar`](./apidocs/index.html?org/codehaus/plexus/archiver/jar/JarToolModularJarArchiver.html),
 
[`rar`](./apidocs/index.html?org/codehaus/plexus/archiver/rar/RarArchiver.html) 
\(notice produces a `jar`\), 
[`snappy`](./apidocs/index.html?org/codehaus/plexus/archiver/snappy/SnappyArchiver.html),
 
[`tar`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarArchiver.html),
 
[`tar.bz2`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarBZip2Archiver.html)
 \([`tbz2`](./apidocs/index
 .html?org/codehaus/plexus/archiver/tar/TBZ2Archiver.html)\), 
[`tar.gz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarGZipArchiver.html)
 
\([`tgz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TGZArchiver.html)\),
 
[`tar.snappy`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarSnappyArchiver.html),
 
[`tar.zst`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarZstdArchiver.html)
 
\([`tzst`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TZstdArchiver.html)\),
 
[`tar.xz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarXZArchiver.html)
 
\([`txz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TXZArchiver.html)\),
 
[`war`](./apidocs/index.html?org/codehaus/plexus/archiver/war/WarArchiver.html),
 [`xz`](./apidocs/index.html?org/codehaus/plexus/archiver/xz/XZArchiver.html), 
[`zip`](./apidocs/index.html?org/codehaus/plexus/archiver/zip/ZipArchiver.html),
 
[`zst`](./apidocs/index.html?org/codehaus/plexus/archiver/zstd/ZstdArchiver.html)
   
                                                                                
                                                                                
                                                                                
                                                                                
                      |
-|                                                                              
     |                                                                          
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                      
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                      
                                                                                
                                                                                
                                                                                
                                                                                
                      |
-| 
[`UnArchiver`](./apidocs/index.html?org/codehaus/plexus/archiver/UnArchiver.html)
 | 
[`bzip2`](./apidocs/index.html?org/codehaus/plexus/archiver/bzip2/BZip2UnArchiver.html),
 
[`gzip`](./apidocs/index.html?org/codehaus/plexus/archiver/gzip/GZipUnArchiver.html),
 
[`snappy`](./apidocs/index.html?org/codehaus/plexus/archiver/snappy/SnappyUnArchiver.html),
 
[`tar`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarUnArchiver.html),
 
[`tar.bz2`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarBZip2Archiver.html)
 
\([`tbz2`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TBZ2Archiver.html)\),
 
[`tar.gz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarGZipUnArchiver.html)
 
\([`tgz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TGZUnArchiver.html)\),
 
[`tar.snappy`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarSnappyUnArchiver.html)
 
[`tar.zst`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarZstdUnArchiver.html)
 \([`tzst`](./apidocs/ind
 ex.html?org/codehaus/plexus/archiver/tar/TZstdUnArchiver.html)\), 
[`tar.xz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarXZUnArchiver.html)
 
\([`txz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TXZUnArchiver.html)\),
 
[`xz`](./apidocs/index.html?org/codehaus/plexus/archiver/xz/XZUnArchiver.html), 
[`zip`](./apidocs/index.html?org/codehaus/plexus/archiver/zip/ZipUnArchiver.html)
 \(also available as 
[`car`](./apidocs/index.html?org/codehaus/plexus/archiver/car/CarUnArchiver.html),
 
[`ear`](./apidocs/index.html?org/codehaus/plexus/archiver/ear/EarUnArchiver.html),
 
[`esb`](./apidocs/index.html?org/codehaus/plexus/archiver/esb/EsbUnArchiver.html),
 
[`jar`](./apidocs/index.html?org/codehaus/plexus/archiver/jar/JarUnArchiver.html),
 
[`nar`](./apidocs/index.html?org/codehaus/plexus/archiver/nar/NarUnArchiver.html),
 
[`par`](./apidocs/index.html?org/codehaus/plexus/archiver/par/ParUnArchiver.html),
 [`rar`](./apidocs/index.html?org/codehaus/plexus/archiver/rar/RarUnArchiver.h
 tml), 
[`sar`](./apidocs/index.html?org/codehaus/plexus/archiver/sar/SarUnArchiver.html),
 
[`swc`](./apidocs/index.html?org/codehaus/plexus/archiver/swc/SwcUnArchiver.html),
 
[`war`](./apidocs/index.html?org/codehaus/plexus/archiver/war/WarUnArchiver.html)\),
 
[`zst`](./apidocs/index.html?org/codehaus/plexus/archiver/zstd/ZstdUnArchiver.html)
 |
+| Interface                                                                    
     | Components for supported formats (as Plexus role hint)                   
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                      
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                      
                                                                                
                                                                                
                                                                                
                                                                                
            |
+|:----------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 
[`Archiver`](./apidocs/index.html?org/codehaus/plexus/archiver/Archiver.html)   
  | 
[`bzip2`](./apidocs/index.html?org/codehaus/plexus/archiver/bzip2/BZip2Archiver.html),
 
[`dir`](./apidocs/index.html?org/codehaus/plexus/archiver/dir/DirectoryArchiver.html),
 
[`ear`](./apidocs/index.html?org/codehaus/plexus/archiver/ear/EarArchiver.html),
 
[`gzip`](./apidocs/index.html?org/codehaus/plexus/archiver/gzip/GZipArchiver.html),
 
[`jar`](./apidocs/index.html?org/codehaus/plexus/archiver/jar/JarArchiver.html),
 
[`mjar`](./apidocs/index.html?org/codehaus/plexus/archiver/jar/JarToolModularJarArchiver.html),
 
[`rar`](./apidocs/index.html?org/codehaus/plexus/archiver/rar/RarArchiver.html) 
(notice produces a `jar`), 
[`snappy`](./apidocs/index.html?org/codehaus/plexus/archiver/snappy/SnappyArchiver.html),
 
[`tar`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarArchiver.html),
 
[`tar.bz2`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarBZip2Archiver.html)
 ([`tbz2`](./apidocs/index.ht
 ml?org/codehaus/plexus/archiver/tar/TBZ2Archiver.html)), 
[`tar.gz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarGZipArchiver.html)
 
([`tgz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TGZArchiver.html)),
 
[`tar.snappy`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarSnappyArchiver.html),
 
[`tar.zst`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarZstdArchiver.html)
 
([`tzst`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TZstdArchiver.html)),
 
[`tar.xz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarXZArchiver.html)
 
([`txz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TXZArchiver.html)),
 
[`war`](./apidocs/index.html?org/codehaus/plexus/archiver/war/WarArchiver.html),
 [`xz`](./apidocs/index.html?org/codehaus/plexus/archiver/xz/XZArchiver.html), 
[`zip`](./apidocs/index.html?org/codehaus/plexus/archiver/zip/ZipArchiver.html),
 
[`zst`](./apidocs/index.html?org/codehaus/plexus/archiver/zstd/ZstdArchiver.html)
             
                                                                                
                                                                                
                                                                                
                                                                                
            |
+|                                                                              
     |                                                                          
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                      
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                      
                                                                                
                                                                                
                                                                                
                                                                                
            |
+| 
[`UnArchiver`](./apidocs/index.html?org/codehaus/plexus/archiver/UnArchiver.html)
 | 
[`bzip2`](./apidocs/index.html?org/codehaus/plexus/archiver/bzip2/BZip2UnArchiver.html),
 
[`gzip`](./apidocs/index.html?org/codehaus/plexus/archiver/gzip/GZipUnArchiver.html),
 
[`snappy`](./apidocs/index.html?org/codehaus/plexus/archiver/snappy/SnappyUnArchiver.html),
 
[`tar`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarUnArchiver.html),
 
[`tar.bz2`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarBZip2Archiver.html)
 
([`tbz2`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TBZ2Archiver.html)),
 
[`tar.gz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarGZipUnArchiver.html)
 
([`tgz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TGZUnArchiver.html)),
 
[`tar.snappy`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarSnappyUnArchiver.html)
 
[`tar.zst`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarZstdUnArchiver.html)
 ([`tzst`](./apidocs/index.ht
 ml?org/codehaus/plexus/archiver/tar/TZstdUnArchiver.html)), 
[`tar.xz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TarXZUnArchiver.html)
 
([`txz`](./apidocs/index.html?org/codehaus/plexus/archiver/tar/TXZUnArchiver.html)),
 
[`xz`](./apidocs/index.html?org/codehaus/plexus/archiver/xz/XZUnArchiver.html), 
[`zip`](./apidocs/index.html?org/codehaus/plexus/archiver/zip/ZipUnArchiver.html)
 (also available as 
[`car`](./apidocs/index.html?org/codehaus/plexus/archiver/car/CarUnArchiver.html),
 
[`ear`](./apidocs/index.html?org/codehaus/plexus/archiver/ear/EarUnArchiver.html),
 
[`esb`](./apidocs/index.html?org/codehaus/plexus/archiver/esb/EsbUnArchiver.html),
 
[`jar`](./apidocs/index.html?org/codehaus/plexus/archiver/jar/JarUnArchiver.html),
 
[`nar`](./apidocs/index.html?org/codehaus/plexus/archiver/nar/NarUnArchiver.html),
 
[`par`](./apidocs/index.html?org/codehaus/plexus/archiver/par/ParUnArchiver.html),
 
[`rar`](./apidocs/index.html?org/codehaus/plexus/archiver/rar/RarUnArchiver.html),
 [`s
 
ar`](./apidocs/index.html?org/codehaus/plexus/archiver/sar/SarUnArchiver.html), 
[`swc`](./apidocs/index.html?org/codehaus/plexus/archiver/swc/SwcUnArchiver.html),
 
[`war`](./apidocs/index.html?org/codehaus/plexus/archiver/war/WarUnArchiver.html)),
 
[`zst`](./apidocs/index.html?org/codehaus/plexus/archiver/zstd/ZstdUnArchiver.html)
 |
 
 ## What is Plexus Archiver?
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/src/test/java/org/codehaus/plexus/archiver/SymlinkFailureModeTest.java
 
new/plexus-archiver-plexus-archiver-4.15.0/src/test/java/org/codehaus/plexus/archiver/SymlinkFailureModeTest.java
--- 
old/plexus-archiver-plexus-archiver-4.14.0/src/test/java/org/codehaus/plexus/archiver/SymlinkFailureModeTest.java
   1970-01-01 01:00:00.000000000 +0100
+++ 
new/plexus-archiver-plexus-archiver-4.15.0/src/test/java/org/codehaus/plexus/archiver/SymlinkFailureModeTest.java
   2026-10-19 02:47:32.000000000 +0200
@@ -0,0 +1,257 @@
+/*
+ * Copyright The Plexus developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.codehaus.plexus.archiver;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.compress.archivers.zip.UnixStat;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.codehaus.plexus.archiver.tar.TarUnArchiver;
+import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Extraction when the file system refuses symbolic links, as Windows does 
without the symlink privilege.
+ */
+class SymlinkFailureModeTest {
+
+    @TempDir
+    Path temp;
+
+    private Path archive;
+
+    private Path dest;
+
+    /** Builds a tar from alternating entries: a name, then its content, or 
"->target" for a symbolic link. */
+    private File tar(String... entries) throws IOException {
+        archive = temp.resolve("test.tar");
+        try (TarArchiveOutputStream out = new 
TarArchiveOutputStream(Files.newOutputStream(archive))) {
+            for (int i = 0; i < entries.length; i += 2) {
+                String name = entries[i];
+                String value = entries[i + 1];
+                TarArchiveEntry entry;
+                byte[] content = new byte[0];
+                if (value.startsWith("->")) {
+                    entry = new TarArchiveEntry(name, 
TarArchiveEntry.LF_SYMLINK);
+                    entry.setLinkName(value.substring(2));
+                } else if (name.endsWith("/")) {
+                    entry = new TarArchiveEntry(name);
+                } else {
+                    entry = new TarArchiveEntry(name);
+                    content = value.getBytes(StandardCharsets.UTF_8);
+                    entry.setSize(content.length);
+                }
+                out.putArchiveEntry(entry);
+                out.write(content);
+                out.closeArchiveEntry();
+            }
+        }
+        return archive.toFile();
+    }
+
+    private File zip(String... entries) throws IOException {
+        archive = temp.resolve("test.zip");
+        try (ZipArchiveOutputStream out = new 
ZipArchiveOutputStream(Files.newOutputStream(archive))) {
+            for (int i = 0; i < entries.length; i += 2) {
+                String name = entries[i];
+                String value = entries[i + 1];
+                ZipArchiveEntry entry = new ZipArchiveEntry(name);
+                byte[] content;
+                if (value.startsWith("->")) {
+                    entry.setUnixMode(UnixStat.LINK_FLAG | 0777);
+                    content = 
value.substring(2).getBytes(StandardCharsets.UTF_8);
+                } else {
+                    content = value.getBytes(StandardCharsets.UTF_8);
+                }
+                out.putArchiveEntry(entry);
+                out.write(content);
+                out.closeArchiveEntry();
+            }
+        }
+        return archive.toFile();
+    }
+
+    /** Throws what Windows throws without the privilege. */
+    private static void refuse(File link) throws IOException {
+        throw new AccessDeniedException(link.getPath(), null, "A required 
privilege is not held by the client");
+    }
+
+    private TarUnArchiver refusingTar(File source) throws IOException {
+        TarUnArchiver unArchiver = new TarUnArchiver(source) {
+            @Override
+            protected void createSymbolicLink(File link, File target) throws 
IOException {
+                refuse(link);
+            }
+        };
+        dest = Files.createDirectories(temp.resolve("out"));
+        unArchiver.setDestDirectory(dest.toFile());
+        return unArchiver;
+    }
+
+    private String read(String name) throws IOException {
+        return new String(Files.readAllBytes(dest.resolve(name)), 
StandardCharsets.UTF_8);
+    }
+
+    private void assertCopied(String name, String content) throws IOException {
+        assertThat(dest.resolve(name)).isRegularFile();
+        assertThat(Files.isSymbolicLink(dest.resolve(name))).isFalse();
+        assertThat(read(name)).isEqualTo(content);
+    }
+
+    @Test
+    void copiesTargetWhenLinkFollowsIt() throws Exception {
+        refusingTar(tar("file.txt", "content", "link", 
"->file.txt")).extract();
+
+        assertCopied("link", "content");
+    }
+
+    @Test
+    void copiesTargetWhenLinkPrecedesIt() throws Exception {
+        refusingTar(tar("link", "->file.txt", "file.txt", 
"content")).extract();
+
+        assertCopied("link", "content");
+    }
+
+    @Test
+    void copiesTargetInSubdirectory() throws Exception {
+        refusingTar(tar("lib/server/libjsig.so", "->../libjsig.so", 
"lib/libjsig.so", "so"))
+                .extract();
+
+        assertCopied("lib/server/libjsig.so", "so");
+    }
+
+    @Test
+    void resolvesChainsInAnyOrder() throws Exception {
+        refusingTar(tar("a", "->b", "b", "->file.txt", "file.txt", 
"content")).extract();
+
+        assertCopied("a", "content");
+        assertCopied("b", "content");
+    }
+
+    @Test
+    void skipsCycles() throws Exception {
+        refusingTar(tar("a", "->b", "b", "->a")).extract();
+
+        assertThat(dest.resolve("a")).doesNotExist();
+        assertThat(dest.resolve("b")).doesNotExist();
+    }
+
+    @Test
+    void neverCopiesFromOutsideTheDestination() throws Exception {
+        Files.write(temp.resolve("outside.txt"), 
"secret".getBytes(StandardCharsets.UTF_8));
+
+        refusingTar(tar(
+                        "parent", "->../outside.txt",
+                        "nested", "->sub/../../outside.txt",
+                        "absolute", "->" + 
temp.resolve("outside.txt").toAbsolutePath()))
+                .extract();
+
+        assertThat(dest.resolve("parent")).doesNotExist();
+        assertThat(dest.resolve("nested")).doesNotExist();
+        assertThat(dest.resolve("absolute")).doesNotExist();
+    }
+
+    @Test
+    void copiesDirectoryTargets() throws Exception {
+        refusingTar(tar("dir/", "", "dir/inner.txt", "inner", "dirLink", 
"->dir"))
+                .extract();
+
+        assertCopied("dirLink/inner.txt", "inner");
+    }
+
+    @Test
+    void skipsLinksToAnEnclosingDirectory() throws Exception {
+        refusingTar(tar("dir/", "", "dir/self", "->.", "dir/up", 
"->..")).extract();
+
+        assertThat(dest.resolve("dir/self")).doesNotExist();
+        assertThat(dest.resolve("dir/up")).doesNotExist();
+    }
+
+    @Test
+    void skipsWhenModeIsSkip() throws Exception {
+        TarUnArchiver unArchiver = refusingTar(tar("file.txt", "content", 
"link", "->file.txt"));
+        unArchiver.setSymlinkFailureMode(SymlinkFailureMode.SKIP);
+        unArchiver.extract();
+
+        assertThat(dest.resolve("link")).doesNotExist();
+        assertCopied("file.txt", "content");
+    }
+
+    @Test
+    void failsWhenModeIsFail() throws Exception {
+        TarUnArchiver unArchiver = refusingTar(tar("file.txt", "content", 
"link", "->file.txt"));
+        unArchiver.setSymlinkFailureMode(SymlinkFailureMode.FAIL);
+
+        assertThatThrownBy(unArchiver::extract)
+                .isInstanceOf(ArchiverException.class)
+                .hasRootCauseInstanceOf(AccessDeniedException.class);
+    }
+
+    @Test
+    void extractsTwiceIntoTheSameDirectory() throws Exception {
+        TarUnArchiver unArchiver = refusingTar(tar("link", "->file.txt", 
"file.txt", "content"));
+        unArchiver.extract();
+        unArchiver.extract();
+
+        assertCopied("link", "content");
+    }
+
+    @Test
+    void copiesZipLinkTargets() throws Exception {
+        ZipUnArchiver unArchiver = new ZipUnArchiver(zip("link", "->file.txt", 
"file.txt", "content")) {
+            @Override
+            protected void createSymbolicLink(File link, File target) throws 
IOException {
+                refuse(link);
+            }
+        };
+        dest = Files.createDirectories(temp.resolve("out"));
+        unArchiver.setDestDirectory(dest.toFile());
+        unArchiver.extract();
+
+        assertCopied("link", "content");
+    }
+
+    @Test
+    void defaultsToCopyTarget() {
+        assertThat(new 
TarUnArchiver().getSymlinkFailureMode()).isEqualTo(SymlinkFailureMode.COPY_TARGET);
+    }
+
+    @Test
+    @DisabledOnOs(OS.WINDOWS)
+    void createsLinksWhenTheFileSystemAllowsThem() throws Exception {
+        TarUnArchiver unArchiver = new TarUnArchiver(tar("link", "->file.txt", 
"file.txt", "content"));
+        dest = Files.createDirectories(temp.resolve("out"));
+        unArchiver.setDestDirectory(dest.toFile());
+        unArchiver.extract();
+
+        assertThat(Files.isSymbolicLink(dest.resolve("link"))).isTrue();
+        
assertThat(Files.readSymbolicLink(dest.resolve("link")).toString()).isEqualTo("file.txt");
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/plexus-archiver-plexus-archiver-4.14.0/src/test/java/org/codehaus/plexus/archiver/dir/DirectoryArchiverSymlinkFailureTest.java
 
new/plexus-archiver-plexus-archiver-4.15.0/src/test/java/org/codehaus/plexus/archiver/dir/DirectoryArchiverSymlinkFailureTest.java
--- 
old/plexus-archiver-plexus-archiver-4.14.0/src/test/java/org/codehaus/plexus/archiver/dir/DirectoryArchiverSymlinkFailureTest.java
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/plexus-archiver-plexus-archiver-4.15.0/src/test/java/org/codehaus/plexus/archiver/dir/DirectoryArchiverSymlinkFailureTest.java
  2026-10-19 02:47:32.000000000 +0200
@@ -0,0 +1,102 @@
+/*
+ * Copyright The Plexus developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.codehaus.plexus.archiver.dir;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.codehaus.plexus.archiver.ArchiverException;
+import org.codehaus.plexus.archiver.SymlinkFailureMode;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Copying to a directory when the file system refuses symbolic links, as 
Windows does without the symlink privilege.
+ */
+class DirectoryArchiverSymlinkFailureTest {
+
+    @TempDir
+    Path temp;
+
+    private Path dest;
+
+    private File file;
+
+    private DirectoryArchiver archiver;
+
+    @BeforeEach
+    void setUp() throws IOException {
+        file = Files.write(temp.resolve("file.txt"), 
"content".getBytes(StandardCharsets.UTF_8))
+                .toFile();
+        dest = temp.resolve("out");
+        archiver = new DirectoryArchiver() {
+            @Override
+            protected void createSymbolicLink(File link, File target) throws 
IOException {
+                throw new AccessDeniedException(link.getPath(), null, "A 
required privilege is not held by the client");
+            }
+        };
+        archiver.setDestFile(dest.toFile());
+    }
+
+    @Test
+    void copiesTargetInsteadOfLink() throws Exception {
+        archiver.addSymlink("link", "file.txt");
+        archiver.addFile(file, "file.txt");
+        archiver.createArchive();
+
+        assertThat(Files.isSymbolicLink(dest.resolve("link"))).isFalse();
+        assertThat(dest.resolve("link")).hasContent("content");
+    }
+
+    @Test
+    void neverCopiesFromOutsideTheDestination() throws Exception {
+        archiver.addFile(file, "file.txt");
+        archiver.addSymlink("escape", "../file.txt");
+        archiver.createArchive();
+
+        assertThat(dest.resolve("escape")).doesNotExist();
+    }
+
+    @Test
+    void skipsWhenModeIsSkip() throws Exception {
+        archiver.setSymlinkFailureMode(SymlinkFailureMode.SKIP);
+        archiver.addFile(file, "file.txt");
+        archiver.addSymlink("link", "file.txt");
+        archiver.createArchive();
+
+        assertThat(dest.resolve("link")).doesNotExist();
+        assertThat(dest.resolve("file.txt")).hasContent("content");
+    }
+
+    @Test
+    void failsWhenModeIsFail() {
+        archiver.setSymlinkFailureMode(SymlinkFailureMode.FAIL);
+        archiver.addFile(file, "file.txt");
+        archiver.addSymlink("link", "file.txt");
+
+        assertThatThrownBy(archiver::createArchive)
+                .isInstanceOf(ArchiverException.class)
+                .hasRootCauseInstanceOf(AccessDeniedException.class);
+    }
+}

++++++ plexus-archiver-build.xml ++++++
--- /var/tmp/diff_new_pack.xm63Ws/_old  2026-10-20 12:40:14.594313129 +0200
+++ /var/tmp/diff_new_pack.xm63Ws/_new  2026-10-20 12:40:14.599313339 +0200
@@ -10,7 +10,7 @@
 
   <property name="project.groupId" value="org.codehaus.plexus"/>
   <property name="project.artifactId" value="plexus-archiver"/>
-  <property name="project.version" value="4.14.0"/>
+  <property name="project.version" value="4.15.0"/>
 
   <property name="compiler.release" value="8"/>
   <property name="compiler.source" value="1.${compiler.release}"/>

Reply via email to