diff --git a/src/main/java/io/github/zlika/reproducible/ArchiveStripper.java b/src/main/java/io/github/zlika/reproducible/ArchiveStripper.java new file mode 100644 index 0000000..6bbf323 --- /dev/null +++ b/src/main/java/io/github/zlika/reproducible/ArchiveStripper.java @@ -0,0 +1,191 @@ +/* + * 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 io.github.zlika.reproducible; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.ArchiveOutputStream; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.apache.commons.compress.archivers.ar.ArArchiveEntry; +import org.apache.commons.compress.archivers.cpio.CpioArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.apache.commons.compress.compressors.CompressorException; + +/** + * Strip archives of file dates and users,groups informations that are not reproducible. + */ +public class ArchiveStripper implements Stripper +{ + private final long timestamp; + + /** + * Constructor. + * @param reproducibleDateTime the date/time to use in TAR entries. + */ + public ArchiveStripper(LocalDateTime reproducibleDateTime) + { + this.timestamp = reproducibleDateTime.atZone(ZoneOffset.UTC).toInstant().toEpochMilli(); + } + + @Override + public void strip(File in, File out) throws IOException + { + Path tmp = Files.createTempDirectory("tmp-" + in.getName()); + + try (InputStream is = new BufferedInputStream(new FileInputStream(in)); + OutputStream os = new BufferedOutputStream(new FileOutputStream(out))) + { + strip(is, os, tmp); + } + catch (ArchiveException | CompressorException e) + { + throw new IOException(e); + } + finally + { + org.codehaus.plexus.util.FileUtils.deleteDirectory(tmp.toFile()); + } + } + + void strip(InputStream in, OutputStream out, Path tmp) + throws IOException, ArchiveException, CompressorException + { + String format = ArchiveStreamFactory.detect(in); + try (ArchiveInputStream ain = ArchiveStreamFactory.DEFAULT.createArchiveInputStream(format, in); + ArchiveOutputStream aout = ArchiveStreamFactory.DEFAULT.createArchiveOutputStream(format, out)) + { + if (aout instanceof TarArchiveOutputStream) + { + ((TarArchiveOutputStream) aout).setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); + } + strip(ain, aout, tmp); + } + } + + void strip(ArchiveInputStream ain, ArchiveOutputStream aout, Path tmp) throws IOException + { + List sortedNames = new ArrayList<>(); + ArchiveEntry entry; + while ((entry = ain.getNextEntry()) != null) + { + sortedNames.add(entry); + File copyTo = new File(tmp.toFile(), entry.getName()); + zipSlipProtection(copyTo, tmp); + if (entry.isDirectory()) + { + FileUtils.mkdirs(copyTo); + } + else + { + File destParent = copyTo.getParentFile(); + FileUtils.mkdirs(destParent); + Files.copy(ain, copyTo.toPath()); + } + } + + sortedNames.sort(Comparator.comparing(ArchiveEntry::getName)); + + for (ArchiveEntry sortedEntry : sortedNames) + { + File copyFrom = new File(tmp.toFile(), sortedEntry.getName()); + if (!sortedEntry.isDirectory()) + { + byte[] fileContent = Files.readAllBytes(copyFrom.toPath()); + if (sortedEntry instanceof TarArchiveEntry) + { + TarArchiveEntry tarEntry = (TarArchiveEntry) sortedEntry; + tarEntry.setSize(fileContent.length); + } + else if (sortedEntry instanceof ArArchiveEntry) + { + ArArchiveEntry arEntry = (ArArchiveEntry) sortedEntry; + sortedEntry = new ArArchiveEntry(arEntry.getName(), fileContent.length, arEntry.getUserId(), + arEntry.getGroupId(), arEntry.getMode(), arEntry.getLastModified()); + } + else if (sortedEntry instanceof CpioArchiveEntry) + { + CpioArchiveEntry cpioEntry = (CpioArchiveEntry) sortedEntry; + cpioEntry.setSize(fileContent.length); + } + aout.putArchiveEntry(filterEntry(sortedEntry)); + aout.write(fileContent); + aout.closeArchiveEntry(); + } + else + { + aout.putArchiveEntry(filterEntry(sortedEntry)); + aout.closeArchiveEntry(); + } + } + } + + private ArchiveEntry filterEntry(ArchiveEntry entry) + { + if (entry instanceof TarArchiveEntry) + { + TarArchiveEntry tarEntry = (TarArchiveEntry) entry; + tarEntry.setModTime(timestamp); + tarEntry.setGroupId(0); + tarEntry.setUserId(0); + tarEntry.setUserName(""); + tarEntry.setGroupName(""); + } + else if (entry instanceof ArArchiveEntry) + { + ArArchiveEntry arEntry = (ArArchiveEntry) entry; + return new ArArchiveEntry(arEntry.getName(), arEntry.getSize(), 0, 0, arEntry.getMode(), timestamp / 1000); + } + else if (entry instanceof CpioArchiveEntry) + { + CpioArchiveEntry cpioEntry = (CpioArchiveEntry) entry; + cpioEntry.setTime(timestamp / 1000); + cpioEntry.setUID(0); + cpioEntry.setGID(0); + } + return entry; + } + + /** + * Protection against 'Zip Slip' vulnerability. + * This method checks that the file path is located inside the expected folder. + * @param file the file path to check. + * @param extractFolder the folder. + * @throws IOException if a 'Zip Slip' attack is detected. + */ + private void zipSlipProtection(File file, Path extractFolder) throws IOException + { + if (!file.toPath().normalize().startsWith(extractFolder)) + { + throw new IOException("Bad zip entry"); + } + } +} diff --git a/src/main/java/io/github/zlika/reproducible/CompressedArchiveStripper.java b/src/main/java/io/github/zlika/reproducible/CompressedArchiveStripper.java new file mode 100644 index 0000000..83a43a0 --- /dev/null +++ b/src/main/java/io/github/zlika/reproducible/CompressedArchiveStripper.java @@ -0,0 +1,56 @@ +/* + * 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 io.github.zlika.reproducible; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.time.LocalDateTime; + +import org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.compressors.CompressorException; +import org.apache.commons.compress.compressors.CompressorStreamFactory; + +/** + * Stripper implementation for compressed archives. + */ +public class CompressedArchiveStripper extends ArchiveStripper +{ + /** + * Constructor. + * @param reproducibleDateTime the date/time to use in the archive entries. + */ + public CompressedArchiveStripper(LocalDateTime reproducibleDateTime) + { + super(reproducibleDateTime); + } + + @Override + void strip(InputStream in, OutputStream out, Path tmp) + throws IOException, ArchiveException, CompressorException + { + String format = CompressorStreamFactory.detect(in); + + CompressorStreamFactory compressorFactory = CompressorStreamFactory.getSingleton(); + try (InputStream cis = new BufferedInputStream(compressorFactory.createCompressorInputStream(format, in)); + OutputStream cout = new BufferedOutputStream(compressorFactory.createCompressorOutputStream(format, out))) + { + super.strip(cis, cout, tmp); + } + } +} diff --git a/src/main/java/io/github/zlika/reproducible/SmartTarStripper.java b/src/main/java/io/github/zlika/reproducible/SmartArchiveStripper.java similarity index 69% rename from src/main/java/io/github/zlika/reproducible/SmartTarStripper.java rename to src/main/java/io/github/zlika/reproducible/SmartArchiveStripper.java index e7d5ac3..1245a9d 100644 --- a/src/main/java/io/github/zlika/reproducible/SmartTarStripper.java +++ b/src/main/java/io/github/zlika/reproducible/SmartArchiveStripper.java @@ -18,19 +18,19 @@ import java.time.LocalDateTime; /** - * Process tar formats: tar, tar.gz, tar.bz2 using the default configuriaton - * and the right tar stripper implementation. + * Process archive formats: tar, tar.gz, tar.bz2, ar, cpio using the default configuration + * and the right stripper implementation. * @author Umberto Nicoletti (umberto.nicoletti@gmail.com) */ -final class SmartTarStripper implements Stripper +final class SmartArchiveStripper implements Stripper { private final LocalDateTime reproducibleDateTime; /** * Constructor. - * @param reproducibleDateTime the date/time to use in TAR entries. + * @param reproducibleDateTime the date/time to use in the archive entries. */ - public SmartTarStripper(LocalDateTime reproducibleDateTime) + public SmartArchiveStripper(LocalDateTime reproducibleDateTime) { this.reproducibleDateTime = reproducibleDateTime; } @@ -50,19 +50,13 @@ public void strip(final File file, final File stripped) throws IOException private Stripper findImplementation(File file) { final String name = file.getName(); - final Stripper impl; - if (name.endsWith(".tar.gz")) + if (name.endsWith(".tar.gz") || name.endsWith(".tar.bz2")) { - impl = new TarGzStripper(reproducibleDateTime); - } - else if (name.endsWith(".tar.bz2")) - { - impl = new TarBzStripper(reproducibleDateTime); + return new CompressedArchiveStripper(reproducibleDateTime); } else { - impl = new TarStripper(reproducibleDateTime); + return new ArchiveStripper(reproducibleDateTime); } - return impl; } } diff --git a/src/main/java/io/github/zlika/reproducible/StripJarMojo.java b/src/main/java/io/github/zlika/reproducible/StripJarMojo.java index e7e33a4..6d4db93 100644 --- a/src/main/java/io/github/zlika/reproducible/StripJarMojo.java +++ b/src/main/java/io/github/zlika/reproducible/StripJarMojo.java @@ -30,16 +30,15 @@ import org.apache.maven.plugins.annotations.Parameter; /** - * Fixes the produced artifacts (ZIP/JAR/WAR/EAR) to make the build reproducible. + * Fixes the produced artifacts to make the build reproducible. */ @Mojo(name = "strip-jar", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST, requiresProject = false, threadSafe = true) public final class StripJarMojo extends AbstractMojo { private static final List ZIP_EXT = Arrays.asList("zip", "jar", "war", "ear", "hpi", "adapter"); - private static final List TAR_GZ_EXT = Collections.singletonList("tar.gz"); - private static final List TAR_BZ_EXT = Collections.singletonList("tar.bz2"); - private static final List TAR_EXT = Collections.singletonList("tar"); + private static final List ARCHIVE_EXT = + Arrays.asList(".tar", ".tar.gz", ".tar.bz2", ".tgz", ".cpio", ".rpm", ".ar", ".deb"); private static final byte[] ZIP_FILE_HEADER = new byte[] { 0x50, 0x4B, 0x03, 0x04 }; private static final byte[] SPRING_BOOT_EXEC_HEADER = new byte[] { 0x23, 0x21, 0x2F, 0x62, 0x69, 0x6E }; @@ -181,16 +180,8 @@ public void execute() throws MojoExecutionException new DefaultZipStripper(zipStripper, this.manifestAttributes))) ); this.process( - this.findTarFiles(this.outputDirectory), - new OverwriteStripper(this.overwrite, new SmartTarStripper(reproducibleDateTime)) - ); - this.process( - this.findTarBzFiles(this.outputDirectory), - new OverwriteStripper(this.overwrite, new SmartTarStripper(reproducibleDateTime)) - ); - this.process( - this.findTarGzFiles(this.outputDirectory), - new OverwriteStripper(this.overwrite, new SmartTarStripper(reproducibleDateTime)) + this.findArchiveFiles(this.outputDirectory), + new OverwriteStripper(this.overwrite, new SmartArchiveStripper(reproducibleDateTime)) ); } } @@ -267,28 +258,12 @@ private byte[] getFileHeader(final File file, final int length) return header; } - private File[] findTarBzFiles(final File folder) - { - final PatternFileNameFilter filter = - PatternFileNameFilter.of(this.getLog(), this.includes, this.excludes, TAR_BZ_EXT); - final File[] tbzFiles = folder.listFiles(filter); - return tbzFiles != null ? tbzFiles : new File[0]; - } - - private File[] findTarGzFiles(final File folder) - { - final PatternFileNameFilter filter = - PatternFileNameFilter.of(this.getLog(), this.includes, this.excludes, TAR_GZ_EXT); - final File[] tgzFiles = folder.listFiles(filter); - return tgzFiles != null ? tgzFiles : new File[0]; - } - - private File[] findTarFiles(final File folder) + private File[] findArchiveFiles(final File folder) { final PatternFileNameFilter filter = - PatternFileNameFilter.of(this.getLog(), this.includes, this.excludes, TAR_EXT); - final File[] tarFiles = folder.listFiles(filter); - return tarFiles != null ? tarFiles : new File[0]; + PatternFileNameFilter.of(this.getLog(), this.includes, this.excludes, ARCHIVE_EXT); + final File[] archiveFiles = folder.listFiles(filter); + return archiveFiles != null ? archiveFiles : new File[0]; } private File createStrippedFilename(final File originalFile) diff --git a/src/main/java/io/github/zlika/reproducible/TarBzStripper.java b/src/main/java/io/github/zlika/reproducible/TarBzStripper.java deleted file mode 100644 index 898df45..0000000 --- a/src/main/java/io/github/zlika/reproducible/TarBzStripper.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 io.github.zlika.reproducible; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.time.LocalDateTime; - -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; -import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; -import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; - -/** - * Stripper implementation for tar compressed with bz2. - */ -public class TarBzStripper extends TarStripper -{ - /** - * Constructor. - * @param reproducibleDateTime the date/time to use in TAR entries. - */ - public TarBzStripper(LocalDateTime reproducibleDateTime) - { - super(reproducibleDateTime); - } - - @Override - protected TarArchiveInputStream createInputStream(File in) throws FileNotFoundException, IOException - { - return new TarArchiveInputStream(new BZip2CompressorInputStream(new FileInputStream(in))); - } - - @Override - protected TarArchiveOutputStream createOutputStream(File out) throws FileNotFoundException, IOException - { - final TarArchiveOutputStream stream = new TarArchiveOutputStream( - new BZip2CompressorOutputStream(new FileOutputStream(out))); - stream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); - return stream; - } -} diff --git a/src/main/java/io/github/zlika/reproducible/TarGzStripper.java b/src/main/java/io/github/zlika/reproducible/TarGzStripper.java deleted file mode 100644 index cc73564..0000000 --- a/src/main/java/io/github/zlika/reproducible/TarGzStripper.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 io.github.zlika.reproducible; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.time.LocalDateTime; - -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; -import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; -import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; - -/** - * Stripper implementation for support Tar compressed with gzip. - */ -public class TarGzStripper extends TarStripper -{ - /** - * Constructor. - * @param reproducibleDateTime the date/time to use in TAR entries. - */ - public TarGzStripper(LocalDateTime reproducibleDateTime) - { - super(reproducibleDateTime); - } - - @Override - protected TarArchiveInputStream createInputStream(File in) throws FileNotFoundException, IOException - { - return new TarArchiveInputStream(new GzipCompressorInputStream(new FileInputStream(in))); - } - - @Override - protected TarArchiveOutputStream createOutputStream(File out) throws FileNotFoundException, IOException - { - final TarArchiveOutputStream stream = new TarArchiveOutputStream( - new GzipCompressorOutputStream(new FileOutputStream(out))); - stream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); - return stream; - } -} diff --git a/src/main/java/io/github/zlika/reproducible/TarStripper.java b/src/main/java/io/github/zlika/reproducible/TarStripper.java deleted file mode 100644 index f6db2a0..0000000 --- a/src/main/java/io/github/zlika/reproducible/TarStripper.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 io.github.zlika.reproducible; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; - -/** - * Strip Tar archives of file dates and users,groups informations that are not reproducible. - * - * @author tglman - * - */ -public class TarStripper implements Stripper -{ - private long tarTime; - - /** - * Constructor. - * @param reproducibleDateTime the date/time to use in TAR entries. - */ - public TarStripper(LocalDateTime reproducibleDateTime) - { - this.tarTime = reproducibleDateTime.atZone(ZoneOffset.UTC).toInstant().toEpochMilli(); - } - - /** - * Factory that create a new instance of tar input stream, for allow extension for different file compression - * format. - * - * @param in the input file. - * @return a TarArchiveInputStream for the file. - * @throws FileNotFoundException - * if the file as parameter is not found - * @throws IOException - * if there are error reading the file given as parameter - */ - protected TarArchiveInputStream createInputStream(File in) throws FileNotFoundException, IOException - { - return new TarArchiveInputStream(new FileInputStream(in)); - } - - /** - * Factory that create a new instance of tar output stream, for allow extension for different file compression - * format. - * - * @param out the output file. - * @return a TarArchiveOutputStream for the file. - * @throws FileNotFoundException - * if the file as parameter is not found - * @throws IOException - * if there are error reading the file given as parameter - * - */ - protected TarArchiveOutputStream createOutputStream(File out) throws FileNotFoundException, IOException - { - final TarArchiveOutputStream stream = new TarArchiveOutputStream(new FileOutputStream(out)); - stream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); - return stream; - } - - @Override - public void strip(File in, File out) throws IOException - { - final Path tmp = Files.createTempDirectory("tmp-" + in.getName()); - - List sortedNames = new ArrayList<>(); - try (final TarArchiveInputStream tar = createInputStream(in); - final TarArchiveOutputStream tout = createOutputStream(out)) - { - TarArchiveEntry entry; - while ((entry = tar.getNextTarEntry()) != null) - { - sortedNames.add(entry); - final File copyTo = new File(tmp.toFile(), entry.getName()); - zipSlipProtection(copyTo, tmp); - if (entry.isDirectory()) - { - FileUtils.mkdirs(copyTo); - } - else - { - final File destParent = copyTo.getParentFile(); - FileUtils.mkdirs(destParent); - Files.copy(tar, copyTo.toPath()); - } - } - sortedNames = sortTarEntries(sortedNames); - for (TarArchiveEntry sortedEntry : sortedNames) - { - final File copyFrom = new File(tmp.toFile(), sortedEntry.getName()); - if (!sortedEntry.isDirectory()) - { - final byte[] fileContent = Files.readAllBytes(copyFrom.toPath()); - sortedEntry.setSize(fileContent.length); - tout.putArchiveEntry(filterTarEntry(sortedEntry)); - tout.write(fileContent); - tout.closeArchiveEntry(); - } - else - { - tout.putArchiveEntry(filterTarEntry(sortedEntry)); - tout.closeArchiveEntry(); - } - } - } - finally - { - org.codehaus.plexus.util.FileUtils.deleteDirectory(tmp.toFile()); - } - } - - private List sortTarEntries(List sortedNames) - { - return sortedNames.stream().sorted((a, b) -> a.getName().compareTo(b.getName())) - .collect(Collectors.toList()); - } - - private TarArchiveEntry filterTarEntry(TarArchiveEntry entry) - { - entry.setModTime(tarTime); - entry.setGroupId(0); - entry.setUserId(0); - entry.setUserName(""); - entry.setGroupName(""); - return entry; - } - - /** - * Protection against 'Zip Slip' vulnerability. - * This method checks that the file path is located inside the expected folder. - * @param file the file path to check. - * @param extractFolder the folder. - * @throws IOException if a 'Zip Slip' attack is detected. - */ - private void zipSlipProtection(File file, Path extractFolder) throws IOException - { - if (!file.toPath().normalize().startsWith(extractFolder)) - { - throw new IOException("Bad zip entry"); - } - } -} diff --git a/src/site/apt/index.apt.vm b/src/site/apt/index.apt.vm index 2696cf0..e6453d3 100644 --- a/src/site/apt/index.apt.vm +++ b/src/site/apt/index.apt.vm @@ -31,7 +31,7 @@ ${project.name} ** removes comments in pom.properties file (some of them can contain time/date). - <> + <> * The "strip-jaxb" goal normalizes ObjectFactory.java files generated by the JAXB xjc tool (before JAXB 2.2.11, xjc generates ObjectFactory.java files where the methods diff --git a/src/test/java/io/github/zlika/reproducible/ArchiveStripperTest.java b/src/test/java/io/github/zlika/reproducible/ArchiveStripperTest.java new file mode 100644 index 0000000..2f646c4 --- /dev/null +++ b/src/test/java/io/github/zlika/reproducible/ArchiveStripperTest.java @@ -0,0 +1,165 @@ +/* + * 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 io.github.zlika.reproducible; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.compress.archivers.ar.ArArchiveEntry; +import org.apache.commons.compress.archivers.ar.ArArchiveInputStream; +import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream; +import org.apache.commons.compress.archivers.cpio.CpioArchiveEntry; +import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream; +import org.apache.commons.compress.archivers.cpio.CpioArchiveOutputStream; +import org.apache.commons.compress.archivers.cpio.CpioConstants; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for tar Stripper. + * + * @author tglman + * @author unicolet + * + */ +public class ArchiveStripperTest +{ + /** + * Tests stripping on a reference Tar file. + * + * @throws IOException in case of error on test file operations + */ + @Test + public void testStripTar() throws IOException + { + final File original = new File(this.getClass().getResource("test-tar.tar").getFile()); + final File stripped = File.createTempFile("test-tar", ".tar"); + stripped.deleteOnExit(); + + final LocalDateTime dateTime = LocalDateTime.now(); + new ArchiveStripper(dateTime).strip(original, stripped); + + final TarArchiveEntry[] entries = new TarFile(stripped).entries(); + Assert.assertEquals(8, entries.length); + for (final TarArchiveEntry entry : entries) + { + final String name = entry.getName(); + Assert.assertEquals(name + " user id", 0L, entry.getLongUserId()); + Assert.assertEquals(name + " user name", "", entry.getUserName()); + Assert.assertEquals(name + " group id", 0L, entry.getLongGroupId()); + Assert.assertEquals(name + " group name", "", entry.getGroupName()); + // TAR timestamps have 1s accuracy + final long expectedTimestamp = (dateTime.toInstant(ZoneOffset.UTC).toEpochMilli() / 1000) * 1000; + Assert.assertEquals(name + " modified time", expectedTimestamp, entry.getModTime().getTime()); + } + Assert.assertFalse( + "Original tar should not match the stripped tar", + entries.equals(new TarFile(original).entries()) + ); + } + + @Test + public void testStripCpio() throws Exception + { + File original = new File("target/test-classes/test.cpio"); + CpioArchiveOutputStream out = new CpioArchiveOutputStream(new FileOutputStream(original)); + + CpioArchiveEntry entry = new CpioArchiveEntry("foo.txt"); + entry.setTime(123456789); + entry.setUID(1000); + entry.setGID(1000); + entry.setMode(CpioConstants.C_ISREG); + entry.setSize(3); + + out.putArchiveEntry(entry); + out.write("Foo".getBytes()); + out.closeArchiveEntry(); + + entry = new CpioArchiveEntry("bar.txt"); + entry.setTime(123456789); + entry.setUID(1000); + entry.setGID(1000); + entry.setMode(CpioConstants.C_ISREG); + entry.setSize(3); + + out.putArchiveEntry(entry); + out.write("Bar".getBytes()); + out.closeArchiveEntry(); + + out.close(); + + File stripped = File.createTempFile("test", ".cpio"); + stripped.deleteOnExit(); + LocalDateTime dateTime = LocalDateTime.now(); + new ArchiveStripper(dateTime).strip(original, stripped); + + List entries = new ArrayList<>(); + CpioArchiveInputStream in = new CpioArchiveInputStream(new FileInputStream(stripped)); + while ((entry = in.getNextCPIOEntry()) != null) + { + String name = entry.getName(); + entries.add(name); + Assert.assertEquals(name + " user id", 0L, entry.getUID()); + Assert.assertEquals(name + " group id", 0L, entry.getGID()); + // CPIO timestamps have 1s accuracy + final long expectedTimestamp = dateTime.toInstant(ZoneOffset.UTC).toEpochMilli() / 1000; + Assert.assertEquals(name + " modified time", expectedTimestamp, entry.getTime()); + } + Assert.assertEquals("File order", "bar.txt,foo.txt", String.join(",", entries)); + } + + @Test + public void testStripAr() throws Exception + { + File original = new File("target/test-classes/test.ar"); + ArArchiveOutputStream out = new ArArchiveOutputStream(new FileOutputStream(original)); + + ArArchiveEntry entry = new ArArchiveEntry("foo.txt", 3, 1000, 1000, 0x644, 123456789); + out.putArchiveEntry(entry); + out.write("Foo".getBytes()); + out.closeArchiveEntry(); + + entry = new ArArchiveEntry("bar.txt", 3, 1000, 1000, 0x644, 123456789); + out.putArchiveEntry(entry); + out.write("Bar".getBytes()); + out.closeArchiveEntry(); + + out.close(); + + File stripped = File.createTempFile("test", ".ar"); + stripped.deleteOnExit(); + LocalDateTime dateTime = LocalDateTime.now(); + new ArchiveStripper(dateTime).strip(original, stripped); + + List entries = new ArrayList<>(); + ArArchiveInputStream in = new ArArchiveInputStream(new FileInputStream(stripped)); + while ((entry = in.getNextArEntry()) != null) + { + String name = entry.getName(); + entries.add(name); + Assert.assertEquals(name + " user id", 0L, entry.getUserId()); + Assert.assertEquals(name + " group id", 0L, entry.getGroupId()); + final long expectedTimestamp = dateTime.toInstant(ZoneOffset.UTC).toEpochMilli() / 1000; + Assert.assertEquals(name + " modified time", expectedTimestamp, entry.getLastModified()); + } + Assert.assertEquals("File order", "bar.txt,foo.txt", String.join(",", entries)); + } +} diff --git a/src/test/java/io/github/zlika/reproducible/TarStripperTest.java b/src/test/java/io/github/zlika/reproducible/TarStripperTest.java deleted file mode 100644 index 9c047f8..0000000 --- a/src/test/java/io/github/zlika/reproducible/TarStripperTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 io.github.zlika.reproducible; - -import java.io.File; -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.ZoneOffset; - -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.junit.Assert; -import org.junit.Test; - -/** - * Tests for tar Stripper. - * - * @author tglman - * @author unicolet - * - */ -public class TarStripperTest -{ - /** - * Tests stripping on a reference Tar file. - * - * @throws IOException in case of error on test file operations - */ - @Test - public void testStripTar() throws IOException - { - final File original = new File(this.getClass().getResource("test-tar.tar").getFile()); - final File stripped = File.createTempFile("test-tar", ".tar"); - stripped.deleteOnExit(); - - final LocalDateTime dateTime = LocalDateTime.now(); - new TarStripper(dateTime).strip(original, stripped); - - final TarArchiveEntry[] entries = new TarFile(stripped).entries(); - Assert.assertEquals(8, entries.length); - for (final TarArchiveEntry entry : entries) - { - final String name = entry.getName(); - Assert.assertEquals(name + " user id", 0L, entry.getLongUserId()); - Assert.assertEquals(name + " user name", "", entry.getUserName()); - Assert.assertEquals(name + " group id", 0L, entry.getLongGroupId()); - Assert.assertEquals(name + " group name", "", entry.getGroupName()); - // TAR timestamps have 1s accuracy - final long expectedTimestamp = (dateTime.toInstant(ZoneOffset.UTC).toEpochMilli() / 1000) * 1000; - Assert.assertEquals(name + " modified time", expectedTimestamp, entry.getModTime().getTime()); - } - Assert.assertFalse( - "Original tar should not match the stripped tar", - entries.equals(new TarFile(original).entries()) - ); - } - -}