/* Copyright 2016 Google Inc. All Rights Reserved.
|
|
Distributed under MIT license.
|
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
|
*/
|
|
package org.brotli.integration;
|
|
import org.brotli.dec.BrotliInputStream;
|
import java.io.FileInputStream;
|
import java.io.FileNotFoundException;
|
import java.io.FilterInputStream;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipInputStream;
|
|
/**
|
* Decompress files and (optionally) checks their checksums.
|
*
|
* <p> File are read from ZIP archive passed as an array of bytes. Multiple checkers negotiate about
|
* task distribution via shared AtomicInteger counter.
|
* <p> All entries are expected to be valid brotli compressed streams and output CRC64 checksum
|
* is expected to match the checksum hex-encoded in the first part of entry name.
|
*/
|
public class BundleChecker implements Runnable {
|
private final AtomicInteger nextJob;
|
private final InputStream input;
|
private final boolean sanityCheck;
|
|
/**
|
* @param sanityCheck do not calculate checksum and ignore {@link IOException}.
|
*/
|
public BundleChecker(InputStream input, AtomicInteger nextJob, boolean sanityCheck) {
|
this.input = input;
|
this.nextJob = nextJob;
|
this.sanityCheck = sanityCheck;
|
}
|
|
private long decompressAndCalculateCrc(ZipInputStream input) throws IOException {
|
/* Do not allow entry readers to close the whole ZipInputStream. */
|
FilterInputStream entryStream = new FilterInputStream(input) {
|
@Override
|
public void close() {}
|
};
|
|
BrotliInputStream decompressedStream = new BrotliInputStream(entryStream);
|
long crc;
|
try {
|
crc = BundleHelper.fingerprintStream(decompressedStream);
|
} finally {
|
decompressedStream.close();
|
}
|
return crc;
|
}
|
|
@Override
|
public void run() {
|
String entryName = "";
|
ZipInputStream zis = new ZipInputStream(input);
|
try {
|
int entryIndex = 0;
|
ZipEntry entry;
|
int jobIndex = nextJob.getAndIncrement();
|
while ((entry = zis.getNextEntry()) != null) {
|
if (entry.isDirectory()) {
|
continue;
|
}
|
if (entryIndex++ != jobIndex) {
|
zis.closeEntry();
|
continue;
|
}
|
entryName = entry.getName();
|
long entryCrc = BundleHelper.getExpectedFingerprint(entryName);
|
try {
|
if (entryCrc != decompressAndCalculateCrc(zis) && !sanityCheck) {
|
throw new RuntimeException("CRC mismatch");
|
}
|
} catch (IOException iox) {
|
if (!sanityCheck) {
|
throw new RuntimeException("Decompression failed", iox);
|
}
|
}
|
zis.closeEntry();
|
entryName = "";
|
jobIndex = nextJob.getAndIncrement();
|
}
|
zis.close();
|
input.close();
|
} catch (Throwable ex) {
|
throw new RuntimeException(entryName, ex);
|
}
|
}
|
|
public static void main(String[] args) throws FileNotFoundException {
|
int argsOffset = 0;
|
boolean sanityCheck = false;
|
if (args.length != 0) {
|
if (args[0].equals("-s")) {
|
sanityCheck = true;
|
argsOffset = 1;
|
}
|
}
|
if (args.length == argsOffset) {
|
throw new RuntimeException("Usage: BundleChecker [-s] <fileX.zip> ...");
|
}
|
for (int i = argsOffset; i < args.length; ++i) {
|
new BundleChecker(new FileInputStream(args[i]), new AtomicInteger(0), sanityCheck).run();
|
}
|
}
|
}
|