001package jmri.util;
002
003import java.io.File;
004import java.io.FileInputStream;
005import java.io.InputStream;
006import java.io.FileOutputStream;
007import java.io.IOException;
008import java.util.zip.ZipEntry;
009import java.util.zip.ZipInputStream;
010
011/**
012 * Unzip a local file or URL to into a specified directory
013 * <p>
014 * Largely from
015 * https://examples.javacodegeeks.com/core-java/util/zip/zipinputstream/java-unzip-file-example/
016 */
017public class UnzipFileClass {
018
019    /**
020     * Unzip contents into a directory.
021     *
022     * @param destinationFolder Destination for contents, created if need be;
023     *                          relative or absolute, but must be pre-expanded.
024     * @param zipFile           .zip file name; relative or absolute, but must
025     *                          be pre-expanded.
026     * @throws                  java.io.FileNotFoundException if File Not Found.
027     */
028    public static void unzipFunction(String destinationFolder, String zipFile) throws java.io.FileNotFoundException {
029        File directory = new File(destinationFolder);
030        FileInputStream fInput = new FileInputStream(zipFile);
031
032        unzipFunction(directory, fInput);
033    }
034
035    /**
036     * Unzip contents into a directory.
037     *
038     * @param directory Destination for contents, created if need be
039     * @param input     in .zip format
040     */
041    public static void unzipFunction(File directory, InputStream input) {
042        // if the output directory doesn't exist, create it
043        if (!directory.exists()) {
044            if (!directory.mkdirs()) {
045                log.error("Unable to create output directory {}", directory);
046            }
047        }
048        String destinationFolder = directory.getPath();
049
050        // buffer for read and write data to file
051        byte[] buffer = new byte[2048];
052
053        try (ZipInputStream zipInput = new ZipInputStream(input)) {
054
055            ZipEntry entry = zipInput.getNextEntry();
056
057            while (entry != null) {
058                String entryName = entry.getName();
059                File file = new File(destinationFolder + File.separator + entryName);
060
061                // Security check: Validate that the resolved path is within the target directory
062                if (!file.toPath().normalize().startsWith(directory.toPath().normalize())) {
063                    throw new IOException("Zip Slip vulnerability detected: " + entryName + 
064                                        " would extract outside target directory");
065                }
066
067                log.info("Unzip file {} to {}", entryName, file.getAbsolutePath());
068
069                // create the directories of the zip directory
070                if (entry.isDirectory()) {
071                    File newDir = new File(file.getAbsolutePath());
072                    if (!newDir.exists()) {
073                        boolean success = newDir.mkdirs();
074                        if (success == false) {
075                            log.error("Problem creating Folder {}", newDir);
076                        }
077                    }
078                } else {
079                    try (FileOutputStream fOutput = new FileOutputStream(file)) {
080                        int count = 0;
081                        while ((count = zipInput.read(buffer)) > 0) {
082                            // write 'count' bytes to the file output stream
083                            fOutput.write(buffer, 0, count);
084                        }
085                    } catch (IOException e) {
086                        log.error("Error writing unpacked zip file contents", e);
087                        return;
088                    }
089                }
090                // close ZipEntry and take the next one
091                zipInput.closeEntry();
092                entry = zipInput.getNextEntry();
093            }
094
095            // close the last ZipEntry
096            zipInput.closeEntry();
097
098            zipInput.close();
099            input.close();
100        } catch (IOException e) {
101            log.error("Error unpacking zip file", e);
102        }
103    }
104
105    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UnzipFileClass.class);
106}