Skip to content
Commits on Source (7)
language: java
jdk:
- oraclejdk7
- openjdk7
- oraclejdk8
- oraclejdk9
# No need for preliminary install step.
install: true
......
......@@ -11,6 +11,8 @@ The current master is now at https://github.com/codehaus-plexus/plexus-archiver
You can find details about the different releases in the [Release Notes](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md).
* [Release 3.4](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md#release-34).
* [Release 3.3](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md#release-33).
* [Release 3.2](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md#release-32).
* [Release 3.6.0](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md#plexus-archiver-360).
* [Release 3.5](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md#plexus-archiver-35).
* [Release 3.4](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md#plexus-archiver-34).
* [Release 3.3](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md#plexus-archiver-33).
* [Release 3.2](https://github.com/codehaus-plexus/plexus-archiver/blob/master/ReleaseNotes.md#plexus-archiver-32).
This diff is collapsed.
plexus-archiver (3.6.0-1) unstable; urgency=medium
* Team upload.
* New upstream release
- Fixes CVE-2018-1002200: Arbitrary file write vulnerability using
a specially crafted zip file (Closes: #900953)
* Removed Damien Raude-Morvan from the uploaders (Closes: #889426)
* Standards-Version updated to 4.1.4
* Switch to debhelper level 11
* Use salsa.debian.org Vcs-* URLs
-- Emmanuel Bourg <ebourg@apache.org> Thu, 07 Jun 2018 11:50:41 +0200
plexus-archiver (3.5-2) unstable; urgency=medium
* Team upload.
......
......@@ -4,10 +4,9 @@ Priority: optional
Maintainer: Debian Java Maintainers <pkg-java-maintainers@lists.alioth.debian.org>
Uploaders:
Torsten Werner <twerner@debian.org>,
Ludovic Claude <ludovic.claude@laposte.net>,
Damien Raude-Morvan <drazzib@debian.org>
Ludovic Claude <ludovic.claude@laposte.net>
Build-Depends:
debhelper (>= 10),
debhelper (>= 11~),
default-jdk,
junit4,
libcommons-compress-java (>= 1.9),
......@@ -18,9 +17,9 @@ Build-Depends:
libsnappy-java,
libxz-java,
maven-debian-helper (>= 2.2)
Standards-Version: 4.1.0
Vcs-Git: https://anonscm.debian.org/git/pkg-java/plexus-archiver.git
Vcs-Browser: https://anonscm.debian.org/cgit/pkg-java/plexus-archiver.git
Standards-Version: 4.1.4
Vcs-Git: https://salsa.debian.org/java-team/plexus-archiver.git
Vcs-Browser: https://salsa.debian.org/java-team/plexus-archiver
Homepage: https://codehaus-plexus.github.io/plexus-archiver/
Package: libplexus-archiver-java
......
......@@ -2,6 +2,3 @@
%:
dh $@
get-orig-source:
uscan --download-current-version --force-download --rename
......@@ -10,14 +10,14 @@
</parent>
<artifactId>plexus-archiver</artifactId>
<version>3.5</version>
<version>3.6.0</version>
<name>Plexus Archiver Component</name>
<scm>
<connection>scm:git:git@github.com:codehaus-plexus/plexus-archiver.git</connection>
<developerConnection>scm:git:git@github.com:codehaus-plexus/plexus-archiver.git</developerConnection>
<url>http://github.com/codehaus-plexus/plexus-archiver</url>
<tag>plexus-archiver-3.5</tag>
<tag>plexus-archiver-3.6.0</tag>
</scm>
<issueManagement>
<system>jira</system>
......@@ -57,17 +57,17 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.0.24</version>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-io</artifactId>
<version>3.0.0</version>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.14</version>
<version>1.16.1</version>
</dependency>
<dependency>
<groupId>org.iq80.snappy</groupId>
......@@ -89,7 +89,7 @@
<dependency>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
<version>1.6</version>
<version>1.8</version>
<scope>runtime</scope>
</dependency>
</dependencies>
......
......@@ -1007,6 +1007,8 @@ public final void createArchive()
{
cleanUp();
}
postCreateArchive();
}
protected boolean hasVirtualFiles()
......@@ -1036,6 +1038,21 @@ protected void validate()
{
}
/**
* This method is called after the archive creation
* completes successfully (no exceptions are thrown).
*
* Subclasses may override this method in order to
* augment or validate the archive after it is
* created.
*
* @since 3.6
*/
protected void postCreateArchive()
throws ArchiverException, IOException
{
}
protected abstract String getArchiveType();
private void addCloseable( Object maybeCloseable )
......
......@@ -308,6 +308,15 @@ protected void extractFile( final File srcF, final File dir, final InputStream c
// Hmm. Symlinks re-evaluate back to the original file here. Unsure if this is a good thing...
final File f = FileUtils.resolveFile( dir, entryName );
// Make sure that the resolved path of the extracted file doesn't escape the destination directory
String canonicalDirPath = dir.getCanonicalPath();
String canonicalDestPath = f.getCanonicalPath();
if ( !canonicalDestPath.startsWith( canonicalDirPath ) )
{
throw new ArchiverException( "Entry is outside of the target directory (" + entryName + ")" );
}
try
{
if ( !isOverwrite() && f.exists() && ( f.lastModified() >= entryDate.getTime() ) )
......
......@@ -484,7 +484,10 @@ protected void zipFile( InputStreamSupplier is, ConcurrentJarCreator zOut, Strin
{
if ( !doubleFilePass || skipWriting )
{
filesetManifest( fromArchive, is.get() );
try ( InputStream manifestInputStream = is.get() )
{
filesetManifest( fromArchive, manifestInputStream );
}
}
}
else if ( INDEX_NAME.equalsIgnoreCase( vPath ) && index )
......
/**
*
* Copyright 2018 The Apache Software Foundation
*
* 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.jar;
import org.apache.commons.compress.parallel.InputStreamSupplier;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
import org.codehaus.plexus.archiver.util.ResourceUtils;
import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator;
import org.codehaus.plexus.components.io.resources.PlexusIoResource;
import org.codehaus.plexus.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* A {@link ModularJarArchiver} implementation that uses
* the {@code jar} tool provided by
* {@code java.util.spi.ToolProvider} to create
* modular JAR files.
*
* <p>
* The basic JAR archive is created by {@link JarArchiver}
* and the {@code jar} tool is used to upgrade it to modular JAR.
*
* <p>
* If the JAR file does not contain module descriptor
* or the JDK does not provide the {@code jar} tool
* (for example JDK prior to Java 9), then the
* archive created by {@link JarArchiver}
* is left unchanged.
*/
public class JarToolModularJarArchiver
extends ModularJarArchiver
{
private static final String MODULE_DESCRIPTOR_FILE_NAME
= "module-info.class";
private static final Pattern MRJAR_VERSION_AREA
= Pattern.compile( "META-INF/versions/\\d+/" );
private Object jarTool;
private boolean moduleDescriptorFound;
private Path tempDir;
public JarToolModularJarArchiver()
{
try
{
Class<?> toolProviderClass =
Class.forName( "java.util.spi.ToolProvider" );
Object jarToolOptional = toolProviderClass
.getMethod( "findFirst", String.class )
.invoke( null, "jar" );
jarTool = jarToolOptional.getClass().getMethod( "get" )
.invoke( jarToolOptional );
}
catch ( ReflectiveOperationException | SecurityException e )
{
// Ignore. It is expected that the jar tool
// may not be available.
}
}
@Override
protected void zipFile( InputStreamSupplier is, ConcurrentJarCreator zOut,
String vPath, long lastModified, File fromArchive,
int mode, String symlinkDestination,
boolean addInParallel )
throws IOException, ArchiverException
{
// We store the module descriptors in temporary location
// and then add it to the JAR file using the JDK jar tool.
// It may look strange at first, but to update a JAR file
// you need to add new files[1] and the only files
// we're sure that exists in modular JAR file
// are the module descriptors.
//
// [1] There are some exceptions but we need at least one file to
// ensure it will work in all cases.
if ( jarTool != null && isModuleDescriptor( vPath ) )
{
getLogger().debug( "Module descriptor found: " + vPath );
moduleDescriptorFound = true;
// Copy the module descriptor to temporary directory
// so later then can be added to the JAR archive
// by the jar tool.
if ( tempDir == null )
{
tempDir = Files
.createTempDirectory( "plexus-archiver-modular_jar-" );
tempDir.toFile().deleteOnExit();
}
File destFile = tempDir.resolve( vPath ).toFile();
destFile.getParentFile().mkdirs();
destFile.deleteOnExit();
ResourceUtils.copyFile( is.get(), destFile );
ArchiveEntryUtils.chmod( destFile, mode );
destFile.setLastModified( lastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE
? System.currentTimeMillis()
: lastModified );
}
else
{
super.zipFile( is, zOut, vPath, lastModified,
fromArchive, mode, symlinkDestination, addInParallel );
}
}
@Override
protected void postCreateArchive()
throws ArchiverException
{
if ( !moduleDescriptorFound )
{
// no need to update the JAR archive
return;
}
try
{
getLogger().debug( "Using the jar tool to " +
"update the archive to modular JAR." );
Integer result = (Integer) jarTool.getClass()
.getMethod( "run",
PrintStream.class, PrintStream.class, String[].class )
.invoke( jarTool,
System.out, System.err,
getJarToolArguments() );
if ( result != null && result != 0 )
{
throw new ArchiverException( "Could not create modular JAR file. " +
"The JDK jar tool exited with " + result );
}
}
catch ( ReflectiveOperationException | SecurityException e )
{
throw new ArchiverException( "Exception occurred " +
"while creating modular JAR file", e );
}
finally
{
clearTempDirectory();
}
}
/**
* Returns {@code true} if {@code path}
* is a module descriptor.
*/
private boolean isModuleDescriptor( String path )
{
if ( path.endsWith( MODULE_DESCRIPTOR_FILE_NAME ) )
{
String prefix = path.substring( 0,
path.lastIndexOf( MODULE_DESCRIPTOR_FILE_NAME ) );
// the path is a module descriptor if it located
// into the root of the archive or into the
// version are of a multi-release JAR file
return prefix.isEmpty() ||
MRJAR_VERSION_AREA.matcher( prefix ).matches();
}
else
{
return false;
}
}
/**
* Prepares the arguments for the jar tool.
* It takes into account the module version,
* main class, etc.
*/
private String[] getJarToolArguments()
{
List<String> args = new ArrayList<>();
args.add( "--update" );
args.add( "--file" );
args.add( getDestFile().getAbsolutePath() );
if ( getModuleMainClass() != null )
{
args.add( "--main-class" );
args.add( getModuleMainClass() );
}
if ( getModuleVersion() != null )
{
args.add( "--module-version" );
args.add( getModuleVersion() );
}
if ( !isCompress() )
{
args.add( "--no-compress" );
}
args.add( "-C" );
args.add( tempDir.toFile().getAbsolutePath() );
args.add( "." );
return args.toArray( new String[]{} );
}
/**
* Makes best effort the clean up
* the temporary directory used.
*/
private void clearTempDirectory()
{
try
{
if ( tempDir != null )
{
FileUtils.deleteDirectory( tempDir.toFile() );
}
}
catch ( IOException e )
{
// Ignore. It is just best effort.
}
}
}
/**
*
* Copyright 2018 The Apache Software Foundation
*
* 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.jar;
/**
* Base class for creating modular JAR archives.
*
* Subclasses are required to be able to handle both
* JAR archives with module descriptor (modular JAR)
* and without ("regular" JAR).
* That would allow clients of this class to use
* it without prior knowledge if the classes
* they are going to add are part of module
* (contain module descriptor class) or not.
*
* @since 3.6
*/
public abstract class ModularJarArchiver
extends JarArchiver
{
private String moduleMainClass;
private String moduleVersion;
public String getModuleMainClass()
{
return moduleMainClass;
}
/**
* Sets the module main class.
* Ignored if the JAR file does not contain
* module descriptor.
*
* <p>Note that implementations may choose
* to replace the value set in the manifest as well.
*
* @param moduleMainClass the module main class.
*/
public void setModuleMainClass( String moduleMainClass )
{
this.moduleMainClass = moduleMainClass;
}
public String getModuleVersion()
{
return moduleVersion;
}
/**
* Sets the module version.
* Ignored if the JAR file does not contain
* module descriptor.
*
* @param moduleVersion the module version.
*/
public void setModuleVersion( String moduleVersion )
{
this.moduleVersion = moduleVersion;
}
}
......@@ -23,6 +23,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Hashtable;
import java.util.Stack;
......@@ -502,8 +503,7 @@ protected void zipFile( InputStreamSupplier in, ConcurrentJarCreator zOut, Strin
InputStream payload;
if ( ze.isUnixSymlink() )
{
ZipEncoding enc = ZipEncodingHelper.getZipEncoding( getEncoding() );
final byte[] bytes = enc.encode( symlinkDestination ).array();
final byte[] bytes = encodeArchiveEntry( symlinkDestination, getEncoding() );
payload = new ByteArrayInputStream( bytes );
zOut.addArchiveEntry( ze, createInputStreamSupplier( payload ), true );
}
......@@ -644,14 +644,24 @@ protected void zipDir( PlexusIoResource dir, ConcurrentJarCreator zOut, String v
else
{
String symlinkDestination = ( (SymlinkDestinationSupplier) dir ).getSymlinkDestination();
ZipEncoding enc = ZipEncodingHelper.getZipEncoding( encodingToUse );
final byte[] bytes = enc.encode( symlinkDestination ).array();
final byte[] bytes = encodeArchiveEntry( symlinkDestination, encodingToUse );
ze.setMethod( ZipArchiveEntry.DEFLATED );
zOut.addArchiveEntry( ze, createInputStreamSupplier( new ByteArrayInputStream( bytes ) ), true );
}
}
}
private byte[] encodeArchiveEntry( String payload, String encoding )
throws IOException
{
ZipEncoding enc = ZipEncodingHelper.getZipEncoding( encoding );
ByteBuffer encodedPayloadByteBuffer = enc.encode( payload );
byte[] encodedPayloadBytes = new byte[encodedPayloadByteBuffer.limit()];
encodedPayloadByteBuffer.get( encodedPayloadBytes );
return encodedPayloadBytes;
}
protected InputStreamSupplier createInputStreamSupplier( final InputStream inputStream )
{
return new InputStreamSupplier()
......
......@@ -195,9 +195,16 @@ public void remove()
@Override
public void close()
throws IOException
{
try
{
urlClassLoader.close();
}
finally
{
zipFile.close();
}
}
}
......
......@@ -41,6 +41,12 @@
<implementation>org.codehaus.plexus.archiver.jar.JarArchiver</implementation>
<instantiation-strategy>per-lookup</instantiation-strategy>
</component>
<component>
<role>org.codehaus.plexus.archiver.Archiver</role>
<role-hint>mjar</role-hint>
<implementation>org.codehaus.plexus.archiver.jar.JarToolModularJarArchiver</implementation>
<instantiation-strategy>per-lookup</instantiation-strategy>
</component>
<component>
<role>org.codehaus.plexus.archiver.Archiver</role>
......
......@@ -24,6 +24,10 @@
package org.codehaus.plexus.archiver;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.FileTime;
import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.util.FileUtils;
......@@ -35,31 +39,54 @@ public abstract class BasePlexusArchiverTest extends PlexusTestCase
{
/**
* Ensure that when a new file is created at the specified location that the timestamp of
* that file will be greater than the one specified as a reference.
*
* Warning: Runs in a busy loop creating a file until the output file is newer than the reference timestamp.
* This should be better than sleeping for a race condition time out value.
* Ensure that the last modified timestamp of a file will be greater
* than the one specified as a reference.
*
* @param outputFile the file to be created
* @param timestampReference the created file will have a newer timestamp than this reference timestamp.
* @param outputFile the file
* @param timestampReference the file will have a newer timestamp
* than this reference timestamp.
*
* @throws Exception failures
* @throws IOException if the timestamp could not be modified
*/
protected void waitUntilNewTimestamp( File outputFile, long timestampReference ) throws Exception
protected void waitUntilNewTimestamp( File outputFile, long timestampReference )
throws IOException
{
File tmpFile = File.createTempFile( "ZipArchiverTest.waitUntilNewTimestamp", null );
// slurp the file into a temp file and then copy the temp back over the top until it is newer.
FileUtils.copyFile( outputFile, tmpFile );
long startTime = System.currentTimeMillis();
File tmpFile = File.createTempFile(
"BasePlexusArchiverTest.waitUntilNewTimestamp", null );
long newTimestamp;
FileUtils.copyFile( tmpFile, outputFile );
while ( timestampReference >= outputFile.lastModified() )
// We could easily just set the last modified time using
// Files.setLastModifiedTime and System.currentTimeMillis(),
// but the problem is that tests are using this method to verify that
// the force flag is working. To ensure that modified or
// newly created files will have timestamp newer than
// `timestampReference`, we need to modify a file ourself.
// Otherwise the build may fail because when the test overrides
// `outputFile` it will have timestamp that is equal
// to `timestampReference`.
do
{
FileUtils.copyFile( tmpFile, outputFile );
FileUtils.fileWrite( tmpFile, "waitUntilNewTimestamp" );
newTimestamp = tmpFile.lastModified();
Thread.yield();
}
while ( timestampReference >= newTimestamp
// A simple guard to ensure that we'll not do this forever.
// If the last modified timestamp is not changed to
// a newer value after 10 seconds, probably it never will.
&& System.currentTimeMillis() - startTime < 10_000 );
tmpFile.delete();
if ( timestampReference >= newTimestamp )
{
throw new IOException("Could not modify the last modified timestamp "
+ "to newer than the refence value." );
}
FileTime newTimestampTime = FileTime.fromMillis( newTimestamp );
Files.setLastModifiedTime( outputFile.toPath(), newTimestampTime );
}
/**
......
/**
*
* Copyright 2018 The Apache Software Foundation
*
* 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.jar;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.util.IOUtil;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public abstract class BaseJarArchiverTest
{
/*
* Verify that the JarArchiver implementation
* could create basic JAR file
*/
@Test
public void testCreateJar()
throws IOException, ArchiverException
{
File jarFile = new File( "target/output/testJar.jar" );
jarFile.delete();
JarArchiver archiver = getJarArchiver();
archiver.setDestFile( jarFile );
archiver.addDirectory( new File( "src/test/resources/java-classes" ) );
archiver.createArchive();
// verify that the JAR file is created and contains the expected files
try ( ZipFile resultingArchive = new ZipFile( jarFile ) )
{
// verify that the JAR file contains manifest file
assertNotNull( resultingArchive.getEntry( "META-INF/MANIFEST.MF" ) );
// verify the JAR contains the class and it is not corrupted
ZipEntry classFileEntry = resultingArchive.getEntry( "com/example/app/Main.class" );
InputStream resultingClassFile = resultingArchive.getInputStream( classFileEntry );
InputStream originalClassFile =
new FileInputStream( "src/test/resources/java-classes/com/example/app/Main.class" );
assertTrue( IOUtil.contentEquals( originalClassFile, resultingClassFile ) );
}
}
protected abstract JarArchiver getJarArchiver();
}
......@@ -5,19 +5,20 @@
import java.io.IOException;
import java.util.Random;
import org.codehaus.plexus.archiver.ArchiverException;
import junit.framework.TestCase;
import org.junit.Test;
public class JarArchiverTest
extends TestCase
extends BaseJarArchiverTest
{
@Test
public void testCreateManifestOnlyJar()
throws IOException, ManifestException, ArchiverException
{
File jarFile = File.createTempFile( "JarArchiverTest.", ".jar" );
jarFile.deleteOnExit();
JarArchiver archiver = new JarArchiver();
JarArchiver archiver = getJarArchiver();
archiver.setDestFile( jarFile );
Manifest manifest = new Manifest();
......@@ -30,18 +31,20 @@ public void testCreateManifestOnlyJar()
archiver.createArchive();
}
@Test
public void testNonCompressed()
throws IOException, ManifestException, ArchiverException
{
File jarFile = new File( "target/output/jarArchiveNonCompressed.jar" );
JarArchiver archiver = new JarArchiver();
JarArchiver archiver = getJarArchiver();
archiver.setDestFile( jarFile );
archiver.setCompress( false );
archiver.addDirectory( new File( "src/test/resources/mjar179" ) );
archiver.createArchive();
}
@Test
public void testVeryLargeJar()
throws IOException, ManifestException, ArchiverException
{
......@@ -65,10 +68,15 @@ public void testVeryLargeJar()
File jarFile = new File( "target/output/veryLargeJar.jar" );
JarArchiver archiver = new JarArchiver();
JarArchiver archiver = getJarArchiver();
archiver.setDestFile( jarFile );
archiver.addDirectory( tmpDir );
archiver.createArchive();
}
@Override
protected JarArchiver getJarArchiver()
{
return new JarArchiver();
}
}
/**
*
* Copyright 2018 The Apache Software Foundation
*
* 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.jar;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.codehaus.plexus.archiver.ArchiverException;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
public class JarToolModularJarArchiverTest
extends BaseJarArchiverTest
{
private ModularJarArchiver archiver;
/*
* Configures the ModularJarArchiver for the test cases.
*/
@Before
public void ModularJarArchiver()
throws Exception
{
File jarFile = new File( "target/output/modular.jar" );
jarFile.delete();
archiver = getJarArchiver();
archiver.setDestFile( jarFile );
archiver.addDirectory( new File( "src/test/resources/java-classes" ) );
}
/*
* Verify that the main class and the version are properly set for a modular JAR file.
*/
@Test
public void testModularJarWithMainClassAndVersion()
throws Exception
{
assumeTrue( modulesAreSupported() );
archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) );
archiver.setModuleVersion( "1.0.0" );
archiver.setModuleMainClass( "com.example.app.Main" );
archiver.createArchive();
// verify that the proper version and main class are set
assertModularJarFile( archiver.getDestFile(),
"1.0.0", "com.example.app.Main", "com.example.app", "com.example.resources" );
}
/*
* Verify that a modular JAR file is created even when no additional attributes are set.
*/
@Test
public void testModularJar()
throws Exception
{
assumeTrue( modulesAreSupported() );
archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) );
archiver.createArchive();
// verify that the proper version and main class are set
assertModularJarFile( archiver.getDestFile(),
null, null, "com.example.app", "com.example.resources" );
}
/*
* Verify that exception is thrown when the modular JAR is not valid.
*/
@Test( expected = ArchiverException.class )
public void testInvalidModularJar()
throws Exception
{
assumeTrue( modulesAreSupported() );
archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) );
// Not a valid version
archiver.setModuleVersion( "notAValidVersion" );
archiver.createArchive();
}
/*
* Verify that modular JAR files could be created even
* if the Java version does not support modules.
*/
@Test
public void testModularJarPriorJava9()
throws Exception
{
assumeFalse( modulesAreSupported() );
archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) );
archiver.setModuleVersion( "1.0.0" );
archiver.setModuleMainClass( "com.example.app.Main" );
archiver.createArchive();
// verify that the modular jar is created
try ( ZipFile resultingArchive = new ZipFile( archiver.getDestFile() ) )
{
assertNotNull( resultingArchive.getEntry( "module-info.class" ) );
}
}
/*
* Verify that the compression flag is respected.
*/
@Test
public void testNoCompression()
throws Exception
{
assumeTrue( modulesAreSupported() );
archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) );
archiver.setCompress( false );
archiver.createArchive();
// verify that the entries are not compressed
try ( ZipFile resultingArchive = new ZipFile( archiver.getDestFile() ) )
{
Enumeration<? extends ZipEntry> entries = resultingArchive.entries();
while ( entries.hasMoreElements() )
{
ZipEntry entry = entries.nextElement();
assertEquals( ZipEntry.STORED, entry.getMethod() );
}
}
}
/*
* Verify that the compression set in the "plain" JAR file
* is kept after it is updated to modular JAR file.
*/
@Test
public void testCompression()
throws Exception
{
assumeTrue( modulesAreSupported() );
archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) );
archiver.addFile( new File( "src/test/jars/test.jar" ), "META-INF/lib/test.jar" );
archiver.setRecompressAddedZips( false );
archiver.createArchive();
// verify that the compression is kept
try ( ZipFile resultingArchive = new ZipFile( archiver.getDestFile() ) )
{
Enumeration<? extends ZipEntry> entries = resultingArchive.entries();
while ( entries.hasMoreElements() )
{
ZipEntry entry = entries.nextElement();
int expectedMethod = entry.isDirectory() || entry.getName().endsWith( ".jar" )
? ZipEntry.STORED
: ZipEntry.DEFLATED;
assertEquals( expectedMethod, entry.getMethod() );
}
}
}
/*
* Verify that a module descriptor in the versioned area is handled correctly.
*/
@Test
public void testModularMultiReleaseJar()
throws Exception
{
assumeTrue( modulesAreSupported() );
archiver.addFile( new File( "src/test/resources/java-module-descriptor/module-info.class" ),
"META-INF/versions/9/module-info.class" );
Manifest manifest = new Manifest();
manifest.addConfiguredAttribute( new Manifest.Attribute( "Multi-Release", "true" ) );
archiver.addConfiguredManifest( manifest );
archiver.setModuleVersion( "1.0.0" );
archiver.setModuleMainClass( "com.example.app.Main" );
archiver.createArchive();
// verify that the resulting modular jar has the proper version and main class set
try ( ZipFile resultingArchive = new ZipFile( archiver.getDestFile() ) )
{
ZipEntry moduleDescriptorEntry =
resultingArchive.getEntry( "META-INF/versions/9/module-info.class" );
InputStream resultingModuleDescriptor = resultingArchive.getInputStream( moduleDescriptorEntry );
assertModuleDescriptor( resultingModuleDescriptor,
"1.0.0", "com.example.app.Main", "com.example.app", "com.example.resources" );
}
}
@Override
protected JarToolModularJarArchiver getJarArchiver()
{
return new JarToolModularJarArchiver();
}
private void assertModularJarFile( File jarFile ,
String expectedVersion, String expectedMainClass,
String... expectedPackages )
throws Exception
{
try ( ZipFile resultingArchive = new ZipFile( jarFile ) )
{
ZipEntry moduleDescriptorEntry = resultingArchive.getEntry( "module-info.class" );
InputStream resultingModuleDescriptor = resultingArchive.getInputStream( moduleDescriptorEntry );
assertModuleDescriptor( resultingModuleDescriptor,
expectedVersion, expectedMainClass, expectedPackages );
}
}
private void assertModuleDescriptor( InputStream moduleDescriptorInputStream,
String expectedVersion, String expectedMainClass,
String... expectedPackages )
throws Exception
{
// ModuleDescriptor methods are available from Java 9 so let's get by reflection
Class<?> moduleDescriptorClass = Class.forName( "java.lang.module.ModuleDescriptor" );
Class<?> optionalClass = Class.forName( "java.util.Optional" );
Method readMethod = moduleDescriptorClass.getMethod( "read", InputStream.class );
Method mainClassMethod = moduleDescriptorClass.getMethod( "mainClass" );
Method rawVersionMethod = moduleDescriptorClass.getMethod( "rawVersion" );
Method packagesMethod = moduleDescriptorClass.getMethod( "packages" );
Method isPresentMethod = optionalClass.getMethod( "isPresent" );
Method getMethod = optionalClass.getMethod( "get" );
// Read the module from the input stream
Object moduleDescriptor = readMethod.invoke( null, moduleDescriptorInputStream );
// Get the module main class
Object mainClassOptional = mainClassMethod.invoke( moduleDescriptor );
String actualMainClass = null;
if ( (boolean) isPresentMethod.invoke( mainClassOptional ) )
{
actualMainClass = (String) getMethod.invoke( mainClassOptional );
}
// Get the module version
Object versionOptional = rawVersionMethod.invoke( moduleDescriptor );
String actualVersion = null;
if ( (boolean) isPresentMethod.invoke( versionOptional ) )
{
actualVersion = (String) getMethod.invoke( versionOptional );
}
// Get the module packages
Set<String> actualPackagesSet = (Set<String>) packagesMethod.invoke( moduleDescriptor );
Set<String> expectedPackagesSet = new HashSet<>( Arrays.asList( expectedPackages ) );
assertEquals( expectedMainClass, actualMainClass );
assertEquals( expectedVersion, actualVersion );
assertEquals( expectedPackagesSet, actualPackagesSet );
}
/*
* Returns true if the current version of Java does support modules.
*/
private boolean modulesAreSupported()
{
try
{
Class.forName( "java.lang.module.ModuleDescriptor" );
}
catch ( ClassNotFoundException e )
{
return false;
}
return true;
}
}