/*
 * Copyright (c) 2019 yvolk (Yuri Volkov), http://yurivolkov.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.andstatus.app.util;

import org.andstatus.app.context.MyStorage;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import io.vavr.control.Try;

public class ZipUtils {

    private ZipUtils() { /* Empty */ }


    public static Try<File> zipFiles(File sourceFolder, File zipped) {
        try (FileOutputStream fos = new FileOutputStream(zipped);
            ZipOutputStream zos = new ZipOutputStream(fos)) {
            for (File file : sourceFolder.listFiles()) {
                if (!file.isDirectory() && !MyStorage.isTempFile(file)) addToZip(file, zos);
            }
        } catch (IOException e) {
            return Try.failure(new IOException("Error zipping " + sourceFolder.getAbsolutePath() + " folder to " +
                    zipped.getAbsolutePath() + ", " + e.getMessage()));
        }
        return Try.success(zipped);
    }

    private static void addToZip(File file, ZipOutputStream zos) throws IOException {
        try (FileInputStream fis = new FileInputStream(file)) {
            ZipEntry zipEntry = new ZipEntry(file.getName());
            zipEntry.setTime(file.lastModified());
            zos.putNextEntry(zipEntry);

            byte[] bytes = new byte[MyStorage.FILE_CHUNK_SIZE];
            int length;
            while ((length = fis.read(bytes)) >= 0) {
                zos.write(bytes, 0, length);
            }
        } finally {
            zos.closeEntry();
        }
    }

    public static void unzipFiles(File zipped, File targetFolder) throws IOException {
        if (!targetFolder.exists() && !targetFolder.mkdir()) {
            throw new IOException("Couldn't create folder: '" + targetFolder.getAbsolutePath() + "'");
        }
        try(ZipFile zipFile = new ZipFile(zipped)) {
            Enumeration<?> enu = zipFile.entries();
            while (enu.hasMoreElements()) {
                ZipEntry zipEntry = (ZipEntry) enu.nextElement();
                File file = new File(targetFolder, zipEntry.getName());
                try (InputStream is = zipFile.getInputStream(zipEntry);
                     FileOutputStream fos = new FileOutputStream(file)) {
                    byte[] bytes = new byte[MyStorage.FILE_CHUNK_SIZE];
                    int length;
                    while ((length = is.read(bytes)) >= 0) {
                        fos.write(bytes, 0, length);
                    }
                }
                file.setLastModified(zipEntry.getTime());
            }
        }
    }
}

@@ -658,16 +658,42 @@
 .util;%0A%0A
+import android.os.Build;%0A%0A
 import o
@@ -2847,32 +2847,98 @@
 try.getName());%0A
+                if (isFileInsideFolder(file, targetFolder)) %7B%0A    
                 
@@ -3010,24 +3010,28 @@
             
+   
+ 
 FileOutputSt
@@ -3083,32 +3083,36 @@
                 
+    
 byte%5B%5D bytes = n
@@ -3159,32 +3159,36 @@
                 
+  
+  
 int length;%0A    
@@ -3175,32 +3175,36 @@
     int length;%0A
+    
                 
@@ -3272,16 +3272,20 @@
         
+ 
+   
 fos.writ
@@ -3317,34 +3317,42 @@
                 
-%7D%0A
+    %7D%0A    
                 
@@ -3369,16 +3369,20 @@
         
+   
+ 
 file.set
@@ -3427,27 +3427,807 @@
         
-%7D%0A        %7D
+    %7D else %7B%0A                    MyLog.i(ZipUtils.class,%0A                            %22ZipEntry skipped as file is outside target folder: %22 + file.getAbsolutePath());%0A                %7D%0A            %7D%0A        %7D%0A    %7D%0A%0A    private static boolean isFileInsideFolder(File file, File folder) %7B%0A        if (Build.VERSION.SDK_INT %3E= Build.VERSION_CODES.O) %7B%0A            return file.toPath().normalize().startsWith(folder.toPath());%0A        %7D else %7B%0A            try %7B%0A                return file.getCanonicalPath().startsWith(folder.getCanonicalPath());%0A            %7D catch (Exception e) %7B%0A                MyLog.d(ZipUtils.class, %22Failed to check path of the file: %22 + file.getAbsolutePath() +%0A                        %22. Error message:%22 + e.getMessage());%0A            %7D%0A        %7D%0A        return false;
 %0A    %7D%0A%7D

Fix vulnerability: Arbitrary file write during archive extraction ("Zip Slip")