A ZIP entry can expand far beyond its compressed size, so enforce a decompressed-byte limit during extraction.
Java ZIP entry extraction: cap expanded bytes while reading
Count actual output
Metadata such as an entry's compressed size is not a safe allocation or acceptance limit. Count bytes returned by ZipInputStream.read and stop as soon as a configured expanded limit is exceeded. The fixture creates its own in-memory ZIP; its extraction path rejects a 47-byte entry under a 32-byte cap.
Path traversal is a different boundary. If extracting to disk, validate destination paths as described in ZIP path validation and decide how to clean up a partial file after rejection.
Budget the whole archive
A per-entry cap alone permits many entries to consume unlimited total output. Add entry count, aggregate expanded bytes, compression ratio or CPU budget where appropriate. Read incrementally; a bounded copy loop avoids materializing the entire expanded entry in memory.
Working program
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class ArchiveExpansionGate {
static byte[] archive(int payloadLength) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ZipOutputStream zip = new ZipOutputStream(bytes)) {
zip.putNextEntry(new ZipEntry("inventory.txt"));
for (int index = 0; index < payloadLength; index++) zip.write('X');
zip.closeEntry();
}
return bytes.toByteArray();
}
static int expandedSize(byte[] archive, int maximum) throws IOException {
try (ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(archive))) {
if (zip.getNextEntry() == null) throw new IOException("missing entry");
byte[] chunk = new byte[16];
int total = 0;
int count;
while ((count = zip.read(chunk)) != -1) {
if (count > maximum - total) throw new IOException("expanded limit exceeded");
total += count;
}
return total;
}
}
public static void main(String[] args) throws IOException {
System.out.println(expandedSize(archive(12), 32));
try { expandedSize(archive(47), 32); }
catch (IOException rejected) { System.out.println(rejected.getMessage()); }
}
}Output
12
expanded limit exceededCost and ownership
The extractor uses a 16-byte chunk and constant working memory per entry. Counting is O(n) in expanded bytes read until rejection. The in-memory test archive itself uses O(n) storage only to keep this standalone program runnable.
Common Mistakes
- Do not trust ZIP size metadata as the only limit.
- Do not omit an aggregate archive budget.
- Do not treat a byte cap as protection against unsafe destination paths.
Read next
Java ZIP inputs: bounded staging and rejected path traversal, Java byte streams: partial reads and bounded copying, Java InputStream short reads: assemble a complete record header, Java Files.move: atomic publication is a filesystem contract.
Continue with: Java GZIPInputStream: cap bytes after decompression, Java ZIP CRC32: detect corruption without claiming authenticity.
