You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
i2p-zero/org.getmonero.util.normaliz.../src/org/getmonero/util/normalizeZip/NormalizeZip.java

195 lines
7.3 KiB

package org.getmonero.util.normalizeZip;/*
* 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.
*
* Based on: https://github.com/Zlika/reproducible-build-maven-plugin/blob/master/src/main/java/io/github/zlika/reproducible/ZipStripper.java
*/
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.compress.archivers.zip.X5455_ExtendedTimestamp;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipExtraField;
import org.apache.commons.compress.archivers.zip.ZipFile;
/**
* Strips non-reproducible data from a ZIP file.
* It rebuilds the ZIP file with a predictable order for the zip entries and sets zip entry dates to a fixed value.
*/
public final class NormalizeZip {
public static void main(String[] args) throws Exception {
if(args.length!=2) {
System.out.println("Arguments: timestamp zip");
System.exit(1);
}
long timestamp = Long.parseLong(args[0]);
File sourceZipFile = Path.of(args[1]).toFile();
File destZipFile = Path.of(args[1]+".tmp").toFile();
if(!sourceZipFile.exists()) {
System.out.println("Source zip file does not exist");
System.exit(1);
}
System.out.println("Normalizing zip " + sourceZipFile.getCanonicalPath() + " to timestamp: " + new Date(timestamp));
NormalizeZip nz = new NormalizeZip(timestamp, true).addFileStripper("META-INF/MANIFEST.MF", new ManifestStripper());
nz.strip(sourceZipFile, destZipFile);
if(!sourceZipFile.delete()) System.out.println("Cannot delete source file");
if(!destZipFile.renameTo(sourceZipFile)) System.out.println("Cannot rename temporary zip file");
destZipFile = sourceZipFile;
if(!destZipFile.setLastModified(timestamp)) System.out.println("Cannot update zip last modified date");
}
/**
* Comparator used to sort the files in the ZIP file.
* This is mostly an alphabetical order comparator, with the exception that
* META-INF/MANIFEST.MF and META-INF/ must be the 2 first entries (if they exist)
* because this is required by some tools
* (cf. https://github.com/Zlika/reproducible-build-maven-plugin/issues/16).
*/
private static final Comparator<String> MANIFEST_FILE_SORT_COMPARATOR = new Comparator<String>() {
// CHECKSTYLE IGNORE LINE: ReturnCount
@Override
public int compare(String o1, String o2) {
if ("META-INF/MANIFEST.MF".equals(o1)) {
return -1;
}
if ("META-INF/MANIFEST.MF".equals(o2)) {
return 1;
}
if ("META-INF/".equals(o1)) {
return -1;
}
if ("META-INF/".equals(o2)) {
return 1;
}
return o1.compareTo(o2);
}
};
private final long zipTimestamp;
private final boolean fixZipExternalFileAttributes;
public NormalizeZip(long zipTimestamp, boolean fixZipExternalFileAttributes) {
this.zipTimestamp = zipTimestamp;
this.fixZipExternalFileAttributes = fixZipExternalFileAttributes;
}
private final Map<String, Stripper> subFilters = new HashMap<>();
private Stripper getSubFilter(String name) {
for (Map.Entry<String, Stripper> filter : subFilters.entrySet()) {
if (name.matches(filter.getKey())) {
return filter.getValue();
}
}
return null;
}
/**
* Adds a stripper for a given file in the Zip.
* @param filename the name of the file in the Zip (regular expression).
* @param stripper the stripper to apply on the file.
* @return this object (for method chaining).
*/
public NormalizeZip addFileStripper(String filename, Stripper stripper) {
subFilters.put(filename, stripper);
return this;
}
public void strip(File in, File out) throws IOException {
try (final ZipFile zip = new ZipFile(in);
final ZipArchiveOutputStream zout = new ZipArchiveOutputStream(out)) {
final List<String> sortedNames = sortEntriesByName(zip.getEntries());
for (String name : sortedNames) {
final ZipArchiveEntry entry = zip.getEntry(name);
// Strip Zip entry
final ZipArchiveEntry strippedEntry = filterZipEntry(entry);
// Fix external file attributes if required
if (in.getName().endsWith(".jar") || in.getName().endsWith(".war")) {
fixAttributes(strippedEntry);
}
// Strip file if required
final Stripper stripper = getSubFilter(name);
if (stripper != null)
{
// Unzip entry to temp file
final File tmp = File.createTempFile("tmp", null);
tmp.deleteOnExit();
Files.copy(zip.getInputStream(entry), tmp.toPath(), StandardCopyOption.REPLACE_EXISTING);
final File tmp2 = File.createTempFile("tmp", null);
tmp2.deleteOnExit();
stripper.strip(tmp, tmp2);
final byte[] fileContent = Files.readAllBytes(tmp2.toPath());
strippedEntry.setSize(fileContent.length);
zout.putArchiveEntry(strippedEntry);
zout.write(fileContent);
zout.closeArchiveEntry();
}
else {
// Copy the Zip entry as-is
zout.addRawArchiveEntry(strippedEntry, zip.getRawInputStream(entry));
}
}
}
}
private void fixAttributes(ZipArchiveEntry entry) {
if (fixZipExternalFileAttributes) {
/* ZIP external file attributes:
TTTTsstrwxrwxrwx0000000000ADVSHR
^^^^____________________________ file type
(file: 1000 , dir: 0100)
^^^_________________________ setuid, setgid, sticky
^^^^^^^^^________________ Unix permissions
^^^^^^ DOS attributes
The argument of setUnixMode() only takes the 2 upper bytes. */
if (entry.isDirectory()) {
entry.setUnixMode((0b0100 << 12) + 0755);
} else {
entry.setUnixMode((0b1000 << 12) + 0644);
}
}
}
private List<String> sortEntriesByName(Enumeration<ZipArchiveEntry> entries) {
return Collections.list(entries).stream()
.map(e -> e.getName())
.sorted(MANIFEST_FILE_SORT_COMPARATOR)
.collect(Collectors.toList());
}
private ZipArchiveEntry filterZipEntry(ZipArchiveEntry entry) {
// Set times
entry.setCreationTime(FileTime.fromMillis(zipTimestamp));
entry.setLastAccessTime(FileTime.fromMillis(zipTimestamp));
entry.setLastModifiedTime(FileTime.fromMillis(zipTimestamp));
entry.setTime(zipTimestamp);
// Remove extended timestamps
for (ZipExtraField field : entry.getExtraFields()) {
if (field instanceof X5455_ExtendedTimestamp) {
entry.removeExtraField(field.getHeaderId());
}
}
return entry;
}
}