|
30 | 30 | import java.nio.charset.Charset;
|
31 | 31 | import java.nio.charset.StandardCharsets;
|
32 | 32 | import java.nio.file.Files;
|
| 33 | +import java.nio.file.Path; |
33 | 34 | import java.util.ArrayList;
|
34 | 35 | import java.util.Comparator;
|
35 | 36 | import java.util.List;
|
@@ -261,8 +262,94 @@ public static void unzip(File zipArchive, File destinationDirectory) throws IOEx
|
261 | 262 | unzip(zipArchive, destinationDirectory, null);
|
262 | 263 | }
|
263 | 264 |
|
| 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 | + */ |
264 | 272 | public static void unzip(File zipArchive, File destinationDirectory, @Nullable FileFilter skipFilter)
|
265 | 273 | 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 { |
266 | 353 | try (net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipArchive)) {
|
267 | 354 | FileUtil.ensureDirectoryExists(destinationDirectory);
|
268 | 355 |
|
|
0 commit comments