/*******************************************************************************
* Copyright (c) 2012-2017 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.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 KEEP_IN_MEMORY_THRESHOLD = 200 * 1024;
private static final int COPY_BUFFER_SIZE = 8 * 1024;
/** 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 IOException is thrown.
*/
private static final int ZIP_RATIO = 100;
public static ZipContent of(InputStream in) throws IOException {
java.io.File file = null;
byte[] inMemory = null;
int count = 0;
ByteArrayOutputStream inMemorySpool = new ByteArrayOutputStream(KEEP_IN_MEMORY_THRESHOLD);
int bytes;
final byte[] buff = new byte[COPY_BUFFER_SIZE];
while (count <= KEEP_IN_MEMORY_THRESHOLD && (bytes = in.read(buff)) != -1) {
inMemorySpool.write(buff, 0, bytes);
count += bytes;
}
InputStream spool;
if (count > KEEP_IN_MEMORY_THRESHOLD) {
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);
}
try (CountingInputStream compressedDataCounter = new CountingInputStream(spool);
ZipInputStream zip = new ZipInputStream(compressedDataCounter)) {
try (CountingInputStream uncompressedDataCounter = new CountingInputStream(zip)) {
ZipEntry zipEntry;
while ((zipEntry = zip.getNextEntry()) != null) {
if (!zipEntry.isDirectory()) {
while (uncompressedDataCounter.read(buff) != -1) {
long uncompressedBytes = uncompressedDataCounter.getByteCount();
if (uncompressedBytes > ZIP_THRESHOLD) {
long compressedBytes = compressedDataCounter.getByteCount();
if (uncompressedBytes > (ZIP_RATIO * compressedBytes)) {
throw new IOException("Zip bomb detected");
}
}
}
}
}
}
return new ZipContent(inMemory == null ? new DeleteOnCloseFileInputStream(file) : new ByteArrayInputStream(inMemory));
}
}
private final InputStream zipContent;
private ZipContent(InputStream zipContent) {
this.zipContent = zipContent;
}
public InputStream getContent() {
return zipContent;
}
}