Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 29 additions & 99 deletions android/src/main/java/com/rnziparchive/RNZipArchiveModule.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.rnziparchive;

import android.content.res.AssetFileDescriptor;
import android.os.Build;
import android.util.Log;

import com.facebook.react.bridge.Arguments;
Expand All @@ -22,21 +23,23 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;

import java.nio.charset.Charset;

public class RNZipArchiveModule extends ReactContextBaseJavaModule {
private static final String TAG = RNZipArchiveModule.class.getSimpleName();

private static final int BUFFER_SIZE = 4096;
private static final String PROGRESS_EVENT_NAME = "zipArchiveProgressEvent";
private static final String EVENT_KEY_FILENAME = "filePath";
private static final String EVENT_KEY_PROGRESS = "progress";
Expand All @@ -52,52 +55,16 @@ public String getName() {

@ReactMethod
public void isPasswordProtected(final String zipFilePath, final Promise promise) {
try {
net.lingala.zip4j.core.ZipFile zipFile = new net.lingala.zip4j.core.ZipFile(zipFilePath);
promise.resolve(zipFile.isEncrypted());
} catch (ZipException ex) {
promise.reject(null, String.format("Unable to check for encryption due to: %s", getStackTrace(ex)));
}
}

@ReactMethod
public void unzipWithPassword(final String zipFilePath, final String destDirectory,
final String password, final Promise promise) {
new Thread(new Runnable() {
@Override
public void run() {
try {
net.lingala.zip4j.core.ZipFile zipFile = new net.lingala.zip4j.core.ZipFile(zipFilePath);
if (zipFile.isEncrypted()) {
zipFile.setPassword(password);
} else {
promise.reject(null, String.format("Zip file: %s is not password protected", zipFilePath));
}

List fileHeaderList = zipFile.getFileHeaders();
List extractedFileNames = new ArrayList<>();
int totalFiles = fileHeaderList.size();

updateProgress(0, 1, zipFilePath); // force 0%
for (int i = 0; i < totalFiles; i++) {
FileHeader fileHeader = (FileHeader) fileHeaderList.get(i);
zipFile.extractFile(fileHeader, destDirectory);
if (!fileHeader.isDirectory()) {
extractedFileNames.add(fileHeader.getFileName());
}
updateProgress(i + 1, totalFiles, zipFilePath);
}
promise.resolve(Arguments.fromList(extractedFileNames));
} catch (ZipException ex) {
updateProgress(0, 1, zipFilePath); // force 0%
promise.reject(null, String.format("Failed to unzip file, due to: %s", getStackTrace(ex)));
}
}
}).start();
}

@ReactMethod
public void unzip(final String zipFilePath, final String destDirectory, final Promise promise) {
final String charset = "UTF-8";
new Thread(new Runnable() {
@Override
public void run() {
Expand All @@ -120,7 +87,7 @@ public void run() {
try {
// Find the total uncompressed size of every file in the zip, so we can
// get an accurate progress measurement
final long totalUncompressedBytes = getUncompressedSize(zipFilePath);
final long totalUncompressedBytes = getUncompressedSize(zipFilePath, charset);

File destDir = new File(destDirectory);
if (!destDir.exists()) {
Expand Down Expand Up @@ -159,11 +126,8 @@ public void onCopyProgress(long bytesRead) {
};

File fout = new File(destDirectory, entry.getName());
String destDirCanonicalPath = (new File(destDirectory)).getCanonicalPath();
String canonicalPath = fout.getCanonicalPath();
if (!canonicalPath.startsWith(destDirCanonicalPath)) {
throw new Exception(String.format("Found Zip Path Traversal Vulnerability with %s", canonicalPath));
}

ensureZipPathSafety(fout, destDirectory);

if (!fout.exists()) {
//noinspection ResultOfMethodCallIgnored
Expand Down Expand Up @@ -252,11 +216,7 @@ public void run() {
if (entry.isDirectory()) continue;
fout = new File(destDirectory, entry.getName());

String destDirCanonicalPath = (new File(destDirectory)).getCanonicalPath();
String canonicalPath = fout.getCanonicalPath();
if (!canonicalPath.startsWith(destDirCanonicalPath)) {
throw new Exception(String.format("Found Zip Path Traversal Vulnerability with %s", canonicalPath));
}
ensureZipPathSafety(fout, destDirectory);

if (!fout.exists()) {
//noinspection ResultOfMethodCallIgnored
Expand Down Expand Up @@ -306,54 +266,11 @@ public void onCopyProgress(long bytesRead) {

@ReactMethod
public void zip(String fileOrDirectory, String destDirectory, Promise promise) {
List<String> filePaths = new ArrayList<>();

String fromDirectory;
try {
File tmp = new File(fileOrDirectory);
if (tmp.exists()) {
if (tmp.isDirectory()) {
fromDirectory = fileOrDirectory;
List<File> files = getSubFiles(tmp, true);
for (int i = 0; i < files.size(); i++) {
filePaths.add(files.get(i).getAbsolutePath());
}
} else {
fromDirectory = fileOrDirectory.substring(0, fileOrDirectory.lastIndexOf("/"));
filePaths.add(fileOrDirectory);
}
} else {
throw new FileNotFoundException(fileOrDirectory);
}
} catch (FileNotFoundException | NullPointerException e) {
promise.reject(null, "Couldn't open file/directory " + fileOrDirectory + ".");
return;
}

try {
String[] filePathArray = filePaths.toArray(new String[filePaths.size()]);
new ZipTask(filePathArray, destDirectory, fromDirectory, promise, this).zip();
} catch (Exception ex) {
promise.reject(null, ex.getMessage());
return;
}
}

private List<File> getSubFiles(File baseDir, boolean isContainFolder) {
List<File> fileList = new ArrayList<>();
File[] tmpList = baseDir.listFiles();
for (File file : tmpList) {
if (file.isFile()) {
fileList.add(file);
}
if (file.isDirectory()) {
if (isContainFolder) {
fileList.add(file); //key code
}
fileList.addAll(getSubFiles(file, isContainFolder));
}
}
return fileList;
@ReactMethod
public void zipWithPassword(String fileOrDirectory, String destDirectory, String password,
String encryptionMethod, Promise promise) {
}

protected void updateProgress(long extractedBytes, long totalSize, String zipFilePath) {
Expand All @@ -373,10 +290,15 @@ protected void updateProgress(long extractedBytes, long totalSize, String zipFil
*
* @return -1 on failure
*/
private long getUncompressedSize(String zipFilePath) {
private long getUncompressedSize(String zipFilePath, String charset) {
long totalSize = 0;
try {
ZipFile zipFile = new ZipFile(zipFilePath);
ZipFile zipFile = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
zipFile = new ZipFile(zipFilePath, Charset.forName(charset));
} else {
zipFile = new ZipFile(zipFilePath);
}
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
Expand All @@ -402,4 +324,12 @@ private String getStackTrace(Exception e) {
return sw.toString();
}

private void ensureZipPathSafety(final File fout, final String destDirectory) throws Exception {
String canonicalPath = fout.getCanonicalPath();
String destDirCanonicalPath = (new File(destDirectory)).getCanonicalPath();
if (!canonicalPath.startsWith(destDirCanonicalPath)) {
throw new SecurityException(String.format("Found Zip Path Traversal Vulnerability with %s", canonicalPath));
}
}

}