/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.vfs.server.util; import org.apache.commons.io.input.CountingInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** @author andrew00x */ public final class ZipContent { /** Memory threshold. If zip stream over this size it spooled in file. */ private static final int BUFFER = 100 * 1024; // 100k private static final int BUFFER_SIZE = 8 * 1024; // 8k /** The threshold after that checking of ZIP ratio started. */ private static final long ZIP_THRESHOLD = 1000000; /** * Max compression ratio. If the number of bytes uncompressed data is exceed the number * of bytes of compressed stream more than this ratio (and number of uncompressed data * is more than threshold) then VirtualFileSystemRuntimeException is thrown. */ private static final int ZIP_RATIO = 100; public static ZipContent newInstance(InputStream in) throws IOException { java.io.File file = null; byte[] inMemory = null; int count = 0; ByteArrayOutputStream inMemorySpool = new ByteArrayOutputStream(BUFFER); int bytes; final byte[] buff = new byte[BUFFER_SIZE]; while (count <= BUFFER && (bytes = in.read(buff)) != -1) { inMemorySpool.write(buff, 0, bytes); count += bytes; } InputStream spool; if (count > BUFFER) { file = java.io.File.createTempFile("import", ".zip"); try (FileOutputStream fileSpool = new FileOutputStream(file)) { inMemorySpool.writeTo(fileSpool); while ((bytes = in.read(buff)) != -1) { fileSpool.write(buff, 0, bytes); } } spool = new FileInputStream(file); } else { inMemory = inMemorySpool.toByteArray(); spool = new ByteArrayInputStream(inMemory); } ZipInputStream zip = null; try { // Counts numbers of compressed data. final CountingInputStream compressedCounter = new CountingInputStream(spool); zip = new ZipInputStream(compressedCounter); // Counts number of uncompressed data. CountingInputStream uncompressedCounter = new CountingInputStream(zip) { @Override public int read() throws IOException { int i = super.read(); checkCompressionRatio(); return i; } @Override public int read(byte[] b, int off, int len) throws IOException { int i = super.read(b, off, len); checkCompressionRatio(); return i; } @Override public int read(byte[] b) throws IOException { int i = super.read(b); checkCompressionRatio(); return i; } @Override public long skip(long length) throws IOException { long i = super.skip(length); checkCompressionRatio(); return i; } private void checkCompressionRatio() { long uncompressedBytes = getByteCount(); // number of uncompressed bytes if (uncompressedBytes > ZIP_THRESHOLD) { long compressedBytes = compressedCounter.getByteCount(); // number of compressed bytes if (uncompressedBytes > (ZIP_RATIO * compressedBytes)) { throw new RuntimeException("Zip bomb detected. "); } } } }; ZipEntry zipEntry; while ((zipEntry = zip.getNextEntry()) != null) { if (!zipEntry.isDirectory()) { while (uncompressedCounter.read(buff) != -1) { // Read full data from stream to be able detect zip-bomb. } } } return new ZipContent(inMemory != null ? new ByteArrayInputStream(inMemory) : new DeleteOnCloseFileInputStream(file), file == null); } finally { if (zip != null) { zip.close(); } } } public final InputStream zippedData; public final boolean inMemory; private ZipContent(InputStream zippedData, boolean inMemory) { this.zippedData = zippedData; this.inMemory = inMemory; } }