KuLdip PaTel
KuLdip PaTel

Reputation: 1069

How to unzip file with sub-directories in android?

When i was unzip file.it's make file name like Movies\Hollywood\spider-Man. Actually Movies is a Folder,Hollywood is a Folder in Movies and spider-Man is file in Hollywood.

Upvotes: 0

Views: 1265

Answers (2)

Renzo Sampietro
Renzo Sampietro

Reputation: 149

My app contains Kotlin version of A.B.'s answer fixing a Zip Path Traversal Vulnerability.

About the fix a Zip Path Traversal Vulnerability (from the website https://support.google.com/faqs/answer/9294009 ):

"Zip files can contain an entry (file or directory) having path traversal characters (“../”) in its name. If developers unzip such zip file entries without validating their name, it can potentially cause a path traversal attack, leading to writes in arbitrary directories or even overwriting the files in the app's private folders.

We recommend fixing this issue in your app by checking if canonical paths to unzipped files are underneath an expected directory. Specifically, before using a File object created using the return value of ZipEntry's getName() method, always check if the return value of File.GetCanonicalPath() belongs to the intended directory path."

     /**
     * unzip file with subdirectories.
     *
     * Refer to [stackoverflow](https://stackoverflow.com/questions/43672241/how-to-unzip-file-with-sub-directories-in-android)
     * answer of [A.B.](https://stackoverflow.com/users/4914757/a-b)
     * Refer to [support.google.com](https://support.google.com/faqs/answer/9294009)
     * Refer to [stackoverflow](https://stackoverflow.com/questions/56303842/fixing-a-zip-path-traversal-vulnerability-in-android)
     * answer of [Indra Kumar S](https://stackoverflow.com/users/3577946/indra-kumar-s)
     *
     * @param destination file
     * @param zipFile file
     * @return boolean
     */
    @Throws(ZipException::class, IOException::class)
    private fun extractFolder(destination: File, zipFile: File): Boolean {
        val BUFFER = 8192
        //      File file = zipFile;
        //This can throw ZipException if file is not valid zip archive
        val zip = ZipFile(zipFile)
        //      String newPath = destination.getAbsolutePath() + File.separator + FilenameUtils.removeExtension(zipFile.getName());
        val newPath = destination.absolutePath + File.separator + stripExtension(
            zipFile.name.substring(zipFile.name.lastIndexOf("/") + 1)
        )
        //Create destination directory
        File(newPath).mkdir()
        val zipFileEntries: Enumeration<*> = zip.entries()

        //Iterate overall zip file entries
        while (zipFileEntries.hasMoreElements()) {
            val entry = zipFileEntries.nextElement() as ZipEntry
            val currentEntry = entry.name
            val destFile = File(destination.absolutePath, currentEntry)
            //
            // String canonicalPath = destFile.getCanonicalPath();
            try {
                ensureZipPathSafety(destFile, destination)
            } catch (e: Exception) {
                // SecurityException
                e.printStackTrace()
                return false
            }
            //            if (!canonicalPath.startsWith(destination.getAbsolutePath())) {
            // SecurityException
//            }
            // Finish unzipping…
            //
            val destinationParent = destFile.parentFile
            //If entry is directory create sub directory on file system
            destinationParent!!.mkdirs()
            if (!entry.isDirectory) {
                //Copy over data into destination file
                val `is` = BufferedInputStream(
                    zip
                        .getInputStream(entry)
                )
                var currentByte: Int
                val data = ByteArray(BUFFER)
                //orthodox way of copying file data using streams
                val fos = FileOutputStream(destFile)
                val dest = BufferedOutputStream(fos, BUFFER)
                while (`is`.read(data, 0, BUFFER).also { currentByte = it } != -1) {
                    dest.write(data, 0, currentByte)
                }
                dest.flush()
                dest.close()
                `is`.close()
            }
        }
        return true //some error codes etc.
    }

    /**
     * ensure Zip Path Safety.
     *
     *
     * Refer to [stackoverflow](https://stackoverflow.com/questions/56303842/fixing-a-zip-path-traversal-vulnerability-in-android)
     * answer of [Indra Kumar S](https://stackoverflow.com/users/3577946/indra-kumar-s)
     *
     * @param destFile file
     * @param destination directory
     */
    @Throws(Exception::class)
    private fun ensureZipPathSafety(destFile: File, destination: File) {
        val destDirCanonicalPath = destination.canonicalPath
        val destFileCanonicalPath = destFile.canonicalPath
        if (!destFileCanonicalPath.startsWith(destDirCanonicalPath)) {
            throw Exception(
                String.format(
                    "Found Zip Path Traversal Vulnerability with %s",
                    destFileCanonicalPath
                )
            )
        }
    }
    /**
    * strip file name extension.
    *
    *
    * Refer to [stackoverflow](https://stackoverflow.com/questions/7541550/remove-the-extension-of-a-file)
    * answer of [palacsint](https://stackoverflow.com/users/843804/palacsint)
    *
    * @param s string file name
    * @return string file name without extension
    */
    fun stripExtension(s: String?): String {
            return if (s != null && s.lastIndexOf(".") > 0) s.substring(
                0,
                s.lastIndexOf(".")
            ) else s!!
    }

Upvotes: 0

A.B.
A.B.

Reputation: 1604

If Movies\Hollywood\spider-Man is a file while creating the zip it should be extracted as file, no matters whether it has extension or not (like *.mp4, *.flv)

You can rely on java APIs under namespace java.util.zip, the documentation link is here

Written some code which extracts only zip files, it should extract the file entry as file (no gzip, rar is supported).

private boolean extractFolder(File destination, File zipFile) throws ZipException, IOException
{
    int BUFFER = 8192;
    File file = zipFile;
    //This can throw ZipException if file is not valid zip archive
    ZipFile zip = new ZipFile(file);
    String newPath = destination.getAbsolutePath() + File.separator + FilenameUtils.removeExtension(zipFile.getName());
    //Create destination directory
    new File(newPath).mkdir();
    Enumeration zipFileEntries = zip.entries();

    //Iterate overall zip file entries
    while (zipFileEntries.hasMoreElements())
    {
        ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
        String currentEntry = entry.getName();
        File destFile = new File(newPath, currentEntry);
        File destinationParent = destFile.getParentFile();
        //If entry is directory create sub directory on file system
        destinationParent.mkdirs();

        if (!entry.isDirectory())
        {
            //Copy over data into destination file
            BufferedInputStream is = new BufferedInputStream(zip
            .getInputStream(entry));
            int currentByte;
            byte data[] = new byte[BUFFER];
            //orthodox way of copying file data using streams
            FileOutputStream fos = new FileOutputStream(destFile);
            BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
            while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
                dest.write(data, 0, currentByte);
            }
            dest.flush();
            dest.close();
            is.close();
        }
    }
    return true;//some error codes etc.
}

The routine does not perform any exception handling, please catch the ZipException and IOException inside driver code.

Upvotes: 3

Related Questions