Skip to content

Commit 6c33ef5

Browse files
ZipUtil#unzip switched from net.lingala.zip4j to ZipInputStream
1 parent 72aad35 commit 6c33ef5

File tree

1 file changed

+87
-0
lines changed
  • code/src/main/java/com/codeforces/commons/compress

1 file changed

+87
-0
lines changed

code/src/main/java/com/codeforces/commons/compress/ZipUtil.java

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.nio.charset.Charset;
3131
import java.nio.charset.StandardCharsets;
3232
import java.nio.file.Files;
33+
import java.nio.file.Path;
3334
import java.util.ArrayList;
3435
import java.util.Comparator;
3536
import java.util.List;
@@ -261,8 +262,94 @@ public static void unzip(File zipArchive, File destinationDirectory) throws IOEx
261262
unzip(zipArchive, destinationDirectory, null);
262263
}
263264

265+
/**
266+
* Unzips a ZIP-archive to the specified directory.
267+
*
268+
* @param zipArchive ZIP-archive to unzip
269+
* @param destinationDirectory directory to unzip to
270+
* @throws IOException if any I/O-exception occurred
271+
*/
264272
public static void unzip(File zipArchive, File destinationDirectory, @Nullable FileFilter skipFilter)
265273
throws IOException {
274+
275+
FileUtil.ensureDirectoryExists(destinationDirectory);
276+
Path destPath = destinationDirectory.toPath().toRealPath();
277+
278+
int count = 0;
279+
280+
try (ZipInputStream zis = new ZipInputStream(
281+
new BufferedInputStream(Files.newInputStream(zipArchive.toPath())))) {
282+
ZipEntry entry;
283+
284+
while ((entry = zis.getNextEntry()) != null && count < MAX_ZIP_ENTRY_COUNT) {
285+
try {
286+
String entryName = entry.getName().replace('\\', '/');
287+
288+
File targetFile = new File(destinationDirectory, entryName).getCanonicalFile();
289+
if (!targetFile.getAbsolutePath().startsWith(destPath.toString())) {
290+
throw new IOException("ZIP entry tries to escape destination directory: " + entryName);
291+
}
292+
293+
if (skipFilter != null && skipFilter.accept(targetFile)) {
294+
continue; // Entry will be closed in finally block
295+
}
296+
297+
if (entry.isDirectory()) {
298+
FileUtil.ensureDirectoryExists(targetFile);
299+
} else {
300+
// Check size if known upfront
301+
long size = entry.getSize();
302+
if (size > MAX_ZIP_ENTRY_SIZE) {
303+
throw new IOException(String.format("Entry '%s' (%s) is larger than %s.",
304+
entryName, FileUtil.formatSize(size),
305+
FileUtil.formatSize(MAX_ZIP_ENTRY_SIZE)));
306+
}
307+
308+
// Ensure parent dirs exist
309+
File parent = targetFile.getParentFile();
310+
if (!parent.exists() && !parent.mkdirs()) {
311+
throw new IOException("Failed to create parent directory: " + parent);
312+
}
313+
314+
Files.deleteIfExists(targetFile.toPath());
315+
Path targetPath = targetFile.toPath();
316+
317+
try (OutputStream out = new BufferedOutputStream(
318+
Files.newOutputStream(targetPath))) {
319+
byte[] buffer = new byte[65536]; // 64 KiB buffer
320+
int read;
321+
long totalRead = 0;
322+
323+
while ((read = zis.read(buffer)) != -1) {
324+
totalRead += read;
325+
if (totalRead > MAX_ZIP_ENTRY_SIZE) {
326+
throw new IOException("Extracted data exceeds allowed size for: " + entryName);
327+
}
328+
out.write(buffer, 0, read);
329+
}
330+
} catch (IOException e) {
331+
// Clean up partially created file on error
332+
Files.deleteIfExists(targetPath);
333+
throw e;
334+
}
335+
}
336+
337+
++count;
338+
} finally {
339+
// Always close the current entry, even on exceptions
340+
try {
341+
zis.closeEntry();
342+
} catch (IOException e) {
343+
// Log warning but don't mask original exception
344+
// logger.warn("Failed to close ZIP entry", e);
345+
}
346+
}
347+
}
348+
}
349+
}
350+
351+
public static void unzip2(File zipArchive, File destinationDirectory, @Nullable FileFilter skipFilter)
352+
throws IOException {
266353
try (net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipArchive)) {
267354
FileUtil.ensureDirectoryExists(destinationDirectory);
268355

0 commit comments

Comments
 (0)