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}"/>