1 /* Copyright 2016 Google Inc. All Rights Reserved. 2 3 Distributed under MIT license. 4 See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 */ 6 7 package org.brotli.integration; 8 9 import org.brotli.dec.BrotliInputStream; 10 import java.io.FileInputStream; 11 import java.io.FileNotFoundException; 12 import java.io.FilterInputStream; 13 import java.io.IOException; 14 import java.io.InputStream; 15 import java.util.concurrent.atomic.AtomicInteger; 16 import java.util.zip.ZipEntry; 17 import java.util.zip.ZipInputStream; 18 19 /** 20 * Decompress files and (optionally) checks their checksums. 21 * 22 * <p> File are read from ZIP archive passed as an array of bytes. Multiple checkers negotiate about 23 * task distribution via shared AtomicInteger counter. 24 * <p> All entries are expected to be valid brotli compressed streams and output CRC64 checksum 25 * is expected to match the checksum hex-encoded in the first part of entry name. 26 */ 27 public class BundleChecker implements Runnable { 28 private final AtomicInteger nextJob; 29 private final InputStream input; 30 private final boolean sanityCheck; 31 32 /** 33 * @param sanityCheck do not calculate checksum and ignore {@link IOException}. 34 */ BundleChecker(InputStream input, AtomicInteger nextJob, boolean sanityCheck)35 public BundleChecker(InputStream input, AtomicInteger nextJob, boolean sanityCheck) { 36 this.input = input; 37 this.nextJob = nextJob; 38 this.sanityCheck = sanityCheck; 39 } 40 decompressAndCalculateCrc(ZipInputStream input)41 private long decompressAndCalculateCrc(ZipInputStream input) throws IOException { 42 /* Do not allow entry readers to close the whole ZipInputStream. */ 43 FilterInputStream entryStream = new FilterInputStream(input) { 44 @Override 45 public void close() {} 46 }; 47 48 BrotliInputStream decompressedStream = new BrotliInputStream(entryStream); 49 long crc; 50 try { 51 crc = BundleHelper.fingerprintStream(decompressedStream); 52 } finally { 53 decompressedStream.close(); 54 } 55 return crc; 56 } 57 58 @Override run()59 public void run() { 60 String entryName = ""; 61 ZipInputStream zis = new ZipInputStream(input); 62 try { 63 int entryIndex = 0; 64 ZipEntry entry; 65 int jobIndex = nextJob.getAndIncrement(); 66 while ((entry = zis.getNextEntry()) != null) { 67 if (entry.isDirectory()) { 68 continue; 69 } 70 if (entryIndex++ != jobIndex) { 71 zis.closeEntry(); 72 continue; 73 } 74 entryName = entry.getName(); 75 long entryCrc = BundleHelper.getExpectedFingerprint(entryName); 76 try { 77 if (entryCrc != decompressAndCalculateCrc(zis) && !sanityCheck) { 78 throw new RuntimeException("CRC mismatch"); 79 } 80 } catch (IOException iox) { 81 if (!sanityCheck) { 82 throw new RuntimeException("Decompression failed", iox); 83 } 84 } 85 zis.closeEntry(); 86 entryName = ""; 87 jobIndex = nextJob.getAndIncrement(); 88 } 89 zis.close(); 90 input.close(); 91 } catch (Throwable ex) { 92 throw new RuntimeException(entryName, ex); 93 } 94 } 95 main(String[] args)96 public static void main(String[] args) throws FileNotFoundException { 97 int argsOffset = 0; 98 boolean sanityCheck = false; 99 if (args.length != 0) { 100 if (args[0].equals("-s")) { 101 sanityCheck = true; 102 argsOffset = 1; 103 } 104 } 105 if (args.length == argsOffset) { 106 throw new RuntimeException("Usage: BundleChecker [-s] <fileX.zip> ..."); 107 } 108 for (int i = argsOffset; i < args.length; ++i) { 109 new BundleChecker(new FileInputStream(args[i]), new AtomicInteger(0), sanityCheck).run(); 110 } 111 } 112 } 113