From 5889f591a24e0c1cf231974c5cdbb1a8b399aca5 Mon Sep 17 00:00:00 2001 From: Mikolaj Izdebski Date: Wed, 9 May 2018 11:46:04 +0200 Subject: [PATCH] Switch to commons-compress for manifest manipulation Resolves: rhbz#1576358 --- ...ns-compress-for-manifest-injection-a.patch | 433 ++++++++++++++++++ xmvn.spec | 12 +- 2 files changed, 443 insertions(+), 2 deletions(-) create mode 100644 0001-Use-apache-commons-compress-for-manifest-injection-a.patch diff --git a/0001-Use-apache-commons-compress-for-manifest-injection-a.patch b/0001-Use-apache-commons-compress-for-manifest-injection-a.patch new file mode 100644 index 0000000..7332a41 --- /dev/null +++ b/0001-Use-apache-commons-compress-for-manifest-injection-a.patch @@ -0,0 +1,433 @@ +From fbc18951e5f6f98e0ae8db2b08a4477a39884e83 Mon Sep 17 00:00:00 2001 +From: Michael Simacek +Date: Mon, 16 Apr 2018 15:29:50 +0200 +Subject: [PATCH] Use apache-commons-compress for manifest injection and native + code detection + +--- + xmvn-parent/pom.xml | 8 +- + xmvn-tools/xmvn-install/pom.xml | 4 + + .../fedoraproject/xmvn/tools/install/JarUtils.java | 178 +++++++++------------ + .../xmvn/tools/install/impl/JarUtilsTest.java | 32 ++++ + .../src/test/resources/recompression-size.jar | Bin 0 -> 4376 bytes + xmvn.spec | 3 +- + 6 files changed, 119 insertions(+), 106 deletions(-) + create mode 100644 xmvn-tools/xmvn-install/src/test/resources/recompression-size.jar + +diff --git a/xmvn-parent/pom.xml b/xmvn-parent/pom.xml +index df6af7fb..f6465d90 100644 +--- a/xmvn-parent/pom.xml ++++ b/xmvn-parent/pom.xml +@@ -92,6 +92,7 @@ + 3.0.24 + 3.5 + 1.7.25 ++ 1.16.1 + + + 1.3.2.GA +@@ -102,7 +103,7 @@ + 3.6.1 + 3.0.0 + 2.8.2 +- 3.4 ++ 3.5 + 1.6 + 2.5.2 + 0.7.9 +@@ -321,6 +322,11 @@ + plexus-container-default + ${plexusVersion} + ++ ++ org.apache.commons ++ commons-compress ++ ${commonsCompressVersion} ++ + + + +diff --git a/xmvn-tools/xmvn-install/pom.xml b/xmvn-tools/xmvn-install/pom.xml +index 66ac01d7..fbb36a68 100644 +--- a/xmvn-tools/xmvn-install/pom.xml ++++ b/xmvn-tools/xmvn-install/pom.xml +@@ -61,5 +61,9 @@ + org.ow2.asm + asm + ++ ++ org.apache.commons ++ commons-compress ++ + + +diff --git a/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/JarUtils.java b/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/JarUtils.java +index 98d3a57e..0a885460 100644 +--- a/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/JarUtils.java ++++ b/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/JarUtils.java +@@ -16,19 +16,17 @@ + package org.fedoraproject.xmvn.tools.install; + + import java.io.IOException; +-import java.lang.reflect.Field; ++import java.io.InputStream; + import java.nio.file.Files; + import java.nio.file.Path; +-import java.util.Collection; ++import java.nio.file.StandardCopyOption; ++import java.util.Enumeration; + import java.util.jar.Attributes; +-import java.util.jar.JarEntry; +-import java.util.jar.JarInputStream; +-import java.util.jar.JarOutputStream; + import java.util.jar.Manifest; +-import java.util.zip.ZipEntry; +-import java.util.zip.ZipInputStream; +-import java.util.zip.ZipOutputStream; + ++import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; ++import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; ++import org.apache.commons.compress.archivers.zip.ZipFile; + import org.objectweb.asm.ClassReader; + import org.objectweb.asm.ClassVisitor; + import org.objectweb.asm.MethodVisitor; +@@ -43,6 +41,8 @@ import org.fedoraproject.xmvn.artifact.Artifact; + */ + public final class JarUtils + { ++ private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; ++ + private static final Logger LOGGER = LoggerFactory.getLogger( JarUtils.class ); + + // From /usr/include/linux/elf.h +@@ -67,28 +67,33 @@ public final class JarUtils + * + * @return {@code true} if native code was found inside given JAR + */ +- public static boolean containsNativeCode( Path jar ) ++ public static boolean containsNativeCode( Path jarPath ) + { +- try ( ZipInputStream jis = new ZipInputStream( Files.newInputStream( jar ) ) ) ++ try ( ZipFile jar = new ZipFile( jarPath.toFile() ) ) + { +- ZipEntry ent; +- while ( ( ent = jis.getNextEntry() ) != null ) ++ Enumeration entries = jar.getEntries(); ++ while ( entries.hasMoreElements() ) + { +- if ( ent.isDirectory() ) ++ ZipArchiveEntry entry = entries.nextElement(); ++ if ( entry.isDirectory() ) + continue; +- if ( jis.read() == ELFMAG0 && jis.read() == ELFMAG1 && jis.read() == ELFMAG2 && jis.read() == ELFMAG3 ) ++ try ( InputStream jis = jar.getInputStream( entry ) ) + { +- LOGGER.debug( "Native code found inside {}: {}", jar, ent.getName() ); +- return true; ++ if ( jis.read() == ELFMAG0 && jis.read() == ELFMAG1 && jis.read() == ELFMAG2 ++ && jis.read() == ELFMAG3 ) ++ { ++ LOGGER.debug( "Native code found inside {}: {}", jarPath, entry.getName() ); ++ return true; ++ } + } + } + +- LOGGER.trace( "Native code not found inside {}", jar ); ++ LOGGER.trace( "Native code not found inside {}", jarPath ); + return false; + } + catch ( IOException e ) + { +- LOGGER.debug( "I/O exception caught when trying to determine whether JAR contains native code: {}", jar, ++ LOGGER.debug( "I/O exception caught when trying to determine whether JAR contains native code: {}", jarPath, + e ); + return false; + } +@@ -122,40 +127,47 @@ public final class JarUtils + * + * @return {@code true} given JAR as found inside to use native code + */ +- public static boolean usesNativeCode( Path jar ) ++ public static boolean usesNativeCode( Path jarPath ) + { +- try ( ZipInputStream jis = new ZipInputStream( Files.newInputStream( jar ) ) ) ++ try ( ZipFile jar = new ZipFile( jarPath.toFile() ) ) + { +- ZipEntry ent; +- while ( ( ent = jis.getNextEntry() ) != null ) ++ Enumeration entries = jar.getEntries(); ++ while ( entries.hasMoreElements() ) + { +- final String entryName = ent.getName(); +- if ( ent.isDirectory() || !entryName.endsWith( ".class" ) ) ++ ZipArchiveEntry entry = entries.nextElement(); ++ final String entryName = entry.getName(); ++ if ( entry.isDirectory() || !entryName.endsWith( ".class" ) ) + continue; + +- new ClassReader( jis ).accept( new ClassVisitor( Opcodes.ASM4 ) ++ try ( InputStream jis = jar.getInputStream( entry ) ) + { +- @Override +- public MethodVisitor visitMethod( int flags, String name, String desc, String sig, String[] exc ) ++ new ClassReader( jis ).accept( new ClassVisitor( Opcodes.ASM4 ) + { +- if ( ( flags & Opcodes.ACC_NATIVE ) != 0 ) +- throw new NativeMethodFound( entryName, name, sig ); ++ @Override ++ public MethodVisitor visitMethod( int flags, String name, String desc, String sig, ++ String[] exc ) ++ { ++ if ( ( flags & Opcodes.ACC_NATIVE ) != 0 ) ++ throw new NativeMethodFound( entryName, name, sig ); + +- return super.visitMethod( flags, name, desc, sig, exc ); +- } +- }, ClassReader.SKIP_CODE ); ++ return super.visitMethod( flags, name, desc, sig, exc ); ++ } ++ }, ClassReader.SKIP_CODE ); ++ } + } + + return false; + } + catch ( NativeMethodFound e ) + { +- LOGGER.debug( "Native method {}({}) found in {}: {}", e.methodName, e.methodSignature, jar, e.className ); ++ LOGGER.debug( "Native method {}({}) found in {}: {}", e.methodName, e.methodSignature, jarPath, ++ e.className ); + return true; + } + catch ( IOException e ) + { +- LOGGER.debug( "I/O exception caught when trying to determine whether JAR uses native code: {}", jar, e ); ++ LOGGER.debug( "I/O exception caught when trying to determine whether JAR uses native code: {}", jarPath, ++ e ); + return false; + } + catch ( RuntimeException e ) +@@ -178,29 +190,13 @@ public final class JarUtils + } + } + +- /** +- * OpenJDK has a sanity check that prevents adding duplicate entries to ZIP streams. The problem is that some of +- * JARs we try to inject manifests to (especially the ones created by Gradle) already contain duplicate entries, so +- * manifest injection would always fail for them with "ZipException: duplicate entry". +- *

+- * This function tries to work around this OpenJDK sanity check, effectively allowing creating ZIP files with +- * duplicated entries. It should be called on particular ZIP output stream before adding each duplicate entry. +- * +- * @param zipOutputStream ZIP stream to hack +- */ +- private static void openJdkAvoidDuplicateEntryHack( ZipOutputStream zipOutputStream ) ++ private static void updateManifest( Artifact artifact, Manifest mf ) + { +- try +- { +- Field namesField = ZipOutputStream.class.getDeclaredField( "names" ); +- namesField.setAccessible( true ); +- Collection names = (Collection) namesField.get( zipOutputStream ); +- names.clear(); +- } +- catch ( ReflectiveOperationException e ) +- { +- // This hack relies on OpenJDK internals and therefore is not guaranteed to work. Ignore failures. +- } ++ putAttribute( mf, Artifact.MF_KEY_GROUPID, artifact.getGroupId(), null ); ++ putAttribute( mf, Artifact.MF_KEY_ARTIFACTID, artifact.getArtifactId(), null ); ++ putAttribute( mf, Artifact.MF_KEY_EXTENSION, artifact.getExtension(), Artifact.DEFAULT_EXTENSION ); ++ putAttribute( mf, Artifact.MF_KEY_CLASSIFIER, artifact.getClassifier(), "" ); ++ putAttribute( mf, Artifact.MF_KEY_VERSION, artifact.getVersion(), Artifact.DEFAULT_VERSION ); + } + + /** +@@ -213,65 +209,39 @@ public final class JarUtils + public static void injectManifest( Path targetJar, Artifact artifact ) + { + LOGGER.trace( "Trying to inject manifest to {}", artifact ); +- Manifest mf = null; + try + { +- try ( JarInputStream jis = new JarInputStream( Files.newInputStream( targetJar ) ) ) ++ try ( ZipFile jar = new ZipFile( targetJar.toFile() ) ) + { +- mf = jis.getManifest(); +- if ( mf == null ) ++ ZipArchiveEntry manifestEntry = jar.getEntry( MANIFEST_PATH ); ++ if ( manifestEntry != null ) + { +- // getManifest sometimes doesn't find the manifest, try finding it as plain entry +- ZipEntry ent; +- while ( ( ent = jis.getNextEntry() ) != null ) ++ Manifest mf = new Manifest( jar.getInputStream( manifestEntry ) ); ++ updateManifest( artifact, mf ); ++ Path tempJar = Files.createTempFile( "xmvn", ".tmp" ); ++ try ( ZipArchiveOutputStream os = new ZipArchiveOutputStream( tempJar.toFile() ) ) + { +- if ( ent.getName().equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) +- { +- mf = new Manifest( jis ); +- break; +- } ++ // write manifest ++ ZipArchiveEntry newManifestEntry = new ZipArchiveEntry( MANIFEST_PATH ); ++ os.putArchiveEntry( newManifestEntry ); ++ mf.write( os ); ++ os.closeArchiveEntry(); ++ // copy the rest of content ++ jar.copyRawEntries( os, entry -> !entry.equals( manifestEntry ) ); + } +- } +- } +- +- if ( mf == null ) +- { +- LOGGER.trace( "Manifest injection skipped: no pre-existing manifest found to update" ); +- return; +- } +- +- putAttribute( mf, Artifact.MF_KEY_GROUPID, artifact.getGroupId(), null ); +- putAttribute( mf, Artifact.MF_KEY_ARTIFACTID, artifact.getArtifactId(), null ); +- putAttribute( mf, Artifact.MF_KEY_EXTENSION, artifact.getExtension(), Artifact.DEFAULT_EXTENSION ); +- putAttribute( mf, Artifact.MF_KEY_CLASSIFIER, artifact.getClassifier(), "" ); +- putAttribute( mf, Artifact.MF_KEY_VERSION, artifact.getVersion(), Artifact.DEFAULT_VERSION ); +- +- try ( JarInputStream jis = new JarInputStream( Files.newInputStream( targetJar ) ) ) +- { +- +- targetJar = targetJar.toRealPath(); +- Files.delete( targetJar ); +- try ( JarOutputStream jos = new JarOutputStream( Files.newOutputStream( targetJar ), mf ) ) +- { +- byte[] buf = new byte[512]; +- JarEntry entry; +- while ( ( entry = jis.getNextJarEntry() ) != null ) ++ catch ( IOException e ) + { +- openJdkAvoidDuplicateEntryHack( jos ); +- jos.putNextEntry( entry ); +- +- int sz; +- while ( ( sz = jis.read( buf ) ) > 0 ) +- jos.write( buf, 0, sz ); ++ // Re-throw exceptions that occur when processing JAR file after reading header and manifest. ++ throw new RuntimeException( e ); + } ++ Files.move( tempJar, targetJar, StandardCopyOption.REPLACE_EXISTING ); ++ LOGGER.trace( "Manifest injected successfully" ); + } +- catch ( IOException e ) ++ else + { +- // Re-throw exceptions that occur when processing JAR file after reading header and manifest. +- throw new RuntimeException( e ); ++ LOGGER.trace( "Manifest injection skipped: no pre-existing manifest found to update" ); ++ return; + } +- +- LOGGER.trace( "Manifest injected successfully" ); + } + } + catch ( IOException e ) +diff --git a/xmvn-tools/xmvn-install/src/test/java/org/fedoraproject/xmvn/tools/install/impl/JarUtilsTest.java b/xmvn-tools/xmvn-install/src/test/java/org/fedoraproject/xmvn/tools/install/impl/JarUtilsTest.java +index 3ec10cfa..2fc3d619 100644 +--- a/xmvn-tools/xmvn-install/src/test/java/org/fedoraproject/xmvn/tools/install/impl/JarUtilsTest.java ++++ b/xmvn-tools/xmvn-install/src/test/java/org/fedoraproject/xmvn/tools/install/impl/JarUtilsTest.java +@@ -116,6 +116,38 @@ public class JarUtilsTest + } + } + ++ /** ++ * Regression test for a jar which contains an entry that can recompress with a different size, which caused a ++ * mismatch in sizes. ++ * ++ * @throws Exception ++ */ ++ @Test ++ public void testManifestInjectionRecompressionCausesSizeMismatch() ++ throws Exception ++ { ++ Path testResource = Paths.get( "src/test/resources/recompression-size.jar" ); ++ Path testJar = workDir.resolve( "manifest.jar" ); ++ Files.copy( testResource, testJar, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING ); ++ ++ Artifact artifact = ++ new DefaultArtifact( "org.eclipse.osgi", "osgi.compatibility.state", "1.1.0.v20180409-1212" ); ++ JarUtils.injectManifest( testJar, artifact ); ++ ++ try ( JarInputStream jis = new JarInputStream( Files.newInputStream( testJar ) ) ) ++ { ++ Manifest mf = jis.getManifest(); ++ assertNotNull( mf ); ++ ++ Attributes attr = mf.getMainAttributes(); ++ assertNotNull( attr ); ++ ++ assertEquals( "org.eclipse.osgi", attr.getValue( "JavaPackages-GroupId" ) ); ++ assertEquals( "osgi.compatibility.state", attr.getValue( "JavaPackages-ArtifactId" ) ); ++ assertEquals( "1.1.0.v20180409-1212", attr.getValue( "JavaPackages-Version" ) ); ++ } ++ } ++ + /** + * Test JAR if manifest injection works as expected when some artifact fields have default values. + * +diff --git a/xmvn-tools/xmvn-install/src/test/resources/recompression-size.jar b/xmvn-tools/xmvn-install/src/test/resources/recompression-size.jar +new file mode 100644 +index 0000000000000000000000000000000000000000..976481eab0095a5a86e162ef6802bfebde20866e +GIT binary patch +literal 4376 +zcmdT|T}V_>5Wef`uHIa>K+#B+Afkx;>7}BS+=nzvZKJI~Fc7jZ%AgW9B=T>2^&yC$ +zmxUk}0-+xf`g^b^=`9OFBUlKEV8fS$lBk(E=bk&qz4v<8w~(vrc%7ebX1^XSk +zW6w}(gApiwdc-j3TUX!U!1?}b*XvLBU%fVPW$?h(Jq=9}Gh!ydOcj_ZH>SYM&%0-P +zA4d)2;fn}J>Wp&(R~yd{T#TGt<0ip)956sCP&b!l%Uo3>CrLsN_1y-X?f&% +zlk^E3Ml&)xG$VY@R?CBaOt%oz~$8?Dt8TV5lLBHz4xF>7J}qXW_B?s9i;rWfb)w%ic4f1;!+Zb +z%WPaT((FbkjKjC94}( +zO3;Q~Tz;C>M-A&m#Q4D*XFavZRxD_1vVjVWl~N1T29c3c$q$~`0&YcN%jv%mTUK1Y +zu=T8A8K{pM)&u3u%5Wx!^wetRair0l5U8$&vm9(_0C+LO%uIAhH9cY2zPTBugu%0&K++;~!hHt?>g%+_2T=osU30vHf(sI&mye +z5(~QBE!4sdgJA^Vc{YlloNpS+#7)Dhs{o= 3.5.0 BuildRequires: maven-local +BuildRequires: apache-commons-compress BuildRequires: beust-jcommander BuildRequires: cglib BuildRequires: maven-dependency-plugin @@ -179,6 +181,7 @@ artifact repository. %package install Summary: XMvn Install +Requires: apache-commons-compress %description install This package provides XMvn Install, which is a command-line interface @@ -199,6 +202,7 @@ This package provides %{summary}. %patch3 -p1 %patch4 -p1 %patch5 -p1 +%patch6 -p1 # Bisect IT has no chances of working in local, offline mode, without # network access - it needs to access remote repositories. @@ -273,7 +277,7 @@ done # helper scripts %jpackage_script org.fedoraproject.xmvn.tools.bisect.BisectCli "" "-Dxmvn.home=%{_datadir}/%{name}" xmvn/xmvn-bisect:beust-jcommander:maven-invoker:plexus/utils xmvn-bisect -%jpackage_script org.fedoraproject.xmvn.tools.install.cli.InstallerCli "" "" xmvn/xmvn-install:xmvn/xmvn-api:xmvn/xmvn-core:beust-jcommander:slf4j/api:slf4j/simple:objectweb-asm/asm xmvn-install +%jpackage_script org.fedoraproject.xmvn.tools.install.cli.InstallerCli "" "" xmvn/xmvn-install:xmvn/xmvn-api:xmvn/xmvn-core:beust-jcommander:slf4j/api:slf4j/simple:objectweb-asm/asm:objenesis/objenesis:commons-compress xmvn-install %jpackage_script org.fedoraproject.xmvn.tools.resolve.ResolverCli "" "" xmvn/xmvn-resolve:xmvn/xmvn-api:xmvn/xmvn-core:beust-jcommander xmvn-resolve %jpackage_script org.fedoraproject.xmvn.tools.subst.SubstCli "" "" xmvn/xmvn-subst:xmvn/xmvn-api:xmvn/xmvn-core:beust-jcommander xmvn-subst @@ -348,6 +352,10 @@ cp -P ${maven_home}/bin/m2.conf %{buildroot}%{_datadir}/%{name}/bin/ %doc LICENSE NOTICE %changelog +* Wed May 9 2018 Mikolaj Izdebski - 3.0.0-17 +- Switch to commons-compress for manifest manipulation +- Resolves: rhbz#1576358 + * Fri Apr 27 2018 Mikolaj Izdebski - 3.0.0-16 - Remove use of JAXB from xmvn-core to make it work with Java 9