I cannot extract my zip files completely using ZipInputStream - android

I have several zip files that each contains several files too which I want to extract using the ZipInputStream class. Among them are some images. When I try to extract these images using the BufferedOutputStream they are decompressed partially and the images are incomplete.
private void extractArchives() {
ZipInputStream zis;
File archiveDir = new File(
Environment.getExternalStorageDirectory().getAbsolutePath() +
"/archives/");
File[] files = archiveDir.listFiles();
for (int i = 0; i < files.length; ++i)
{
File file = files[i];
try
{
zis = new ZipInputStream(new FileInputStream(file));
ZipEntry ze;
while ((ze = zis.getNextEntry()) != null)
{
BufferedOutputStream bos;
byte[] buffer = new byte[102400];
int count;
while ((count = zis.read(buffer)) != -1)
{
String fileName = ze.getName();
if (fileName.endsWith(".jpg"))
{
path += File.separator + fileName;
bos = new BufferedOutputStream(new FileOutputStream(path));
bos.write(buffer, 0, count);
bos.close();
}
}
}
zis.close();
}
catch(FileNotFoundException e) { continue; }
//If the file is not a zip file or is a directory
catch (IOException e) { continue; }
}
}
Is there anything wrong with the code above? Does using BufferedOutputStream cause this problem? I appreciate any ideas. Thanks.

I modified the method according to what Erwin said and now it works:
private void extractArchives() {
File archiveDir = new File(
Environment.getExternalStorageDirectory().getAbsolutePath() +
"/archives/");
String archivePath = archiveDir.getAbsolutePath();
File[] files = archiveDir.listFiles();
for (int i = 0; i < files.length; ++i)
{
File file = files[i];
if(!file.isDirectory())
{
try {
ZipInputStream zis = new ZipInputStream(new FileInputStream(file));
ZipEntry entry = zis.getNextEntry();
while (entry != null)
{
if(entry.getName().endsWith(".jpg"))
{
String imagePath = themePath + File.separator + entry.getName();
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(imagePath));
byte[] buffer = new byte[4096];
int read = 0;
while ((read = zis.read(buffer)) != -1) bos.write(buffer, 0, read);
imagePath = "";
bos.close();
}
zis.closeEntry();
entry = zis.getNextEntry();
}
zis.close();
}
catch (FileNotFoundException e) {}
catch (IOException e) {}
}
}
}

Focusing on the loop that iterates over the zip entries, the problem is that you were reading at most 102400 bytes from a zip entry and then writing that to a new file. The next maximum 102400 bytes if the same image file was written to a new file. If the path name was the same as the old file, it would overwrite the old file. But since you were constantly appending to the local variable 'path', I'm not sure where the data ended up.
Pull the opening of the FileOutputStream out of the inner loop, and keep writing to the same OutputStream until you can read no more bytes from the current ZipEntry. Only then move on to the next ZipEntry and to the next OutputStream.
This is a standard pattern to read and copy between streams in Java so I can fix your code without having the definitions of the files, path and zis but it would help if you can post an actual compiling code example so that other people can benefit from this too.
Another improvement you need in production code, is to wrap bos.close() in a try/finally block so that the output file is also closed if there was an exception while reading from the input.
while ((ze = zis.getNextEntry()) != null) {
String fileName = ze.getName();
if (fileName.endsWith(".jpg")) {
String filepath = path + File.separator + fileName;
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filepath));
byte[] buffer = new byte[102400];
int count;
while ((count = zis.read(buffer)) != -1) {
bos.write(buffer, 0, count);
}
bos.close();
}
}

Related

Zip Path Traversal Vulnerability Alert (Android)

I am getting the "Zip Path Traversal Vulnerability" alert in Google Play Console.
I followed official Google docs (https://support.google.com/faqs/answer/9294009) to fix it but the alert is still there.
Here is the code that handles unzipping. I have even tested using a zip file that has the vulnerability described and the exception is raised as expected.
What am I missing?
private boolean unpackZip(File zipFile, File outputDirectory, IOnResult<Integer> progress) {
InputStream is;
ZipInputStream zis;
ZipInputStream zisCount;
try {
int totalEntries = 0;
int entryCount = 0;
String filename;
ZipEntry ze;
is = new FileInputStream(zipFile);
zisCount = new ZipInputStream(new BufferedInputStream(is));
while ((ze = zisCount.getNextEntry()) != null) {
// Fixing a Zip Path Traversal Vulnerability
// (https://support.google.com/faqs/answer/9294009)
filename = ze.getName();
File targetFile = new File(outputDirectory, filename);
String targetPath = targetFile.getCanonicalPath();
if (!targetPath.startsWith(outputDirectory.getCanonicalPath())) {
throw new SecurityException("Archive security error");
}
// -----------------------------------------------------------------------------
totalEntries++;
zisCount.closeEntry();
}
zisCount.close();
is = new FileInputStream(zipFile);
zis = new ZipInputStream(new BufferedInputStream(is));
byte[] buffer = new byte[1024];
int count;
while ((ze = zis.getNextEntry()) != null) {
filename = ze.getName();
// Fixing a Zip Path Traversal Vulnerability (https://support.google.com/faqs/answer/9294009)
File targetFile = new File(outputDirectory, filename);
String targetPath = targetFile.getCanonicalPath();
if (!targetPath.startsWith(outputDirectory.getCanonicalPath())) {
throw new SecurityException("Archive security error");
}
// Need to create directories if not exists, or
// it will generate an Exception...
if (ze.isDirectory()) {
File fmd = new File(outputDirectory, filename);
fmd.mkdirs();
continue;
}
File outputFile = new File(outputDirectory, filename);
for( File parentFile = outputFile.getParentFile(); !parentFile.exists(); parentFile = parentFile.getParentFile() )
{
parentFile.mkdir();
}
FileOutputStream fout = new FileOutputStream(outputFile);
Log.d(TAG, "unzipped " + filename);
while ((count = zis.read(buffer)) != -1) {
fout.write(buffer, 0, count);
}
fout.close();
zis.closeEntry();
entryCount++;
}
zis.close();
} catch (IOException | SecurityException e) {
Log.e(TAG, "unpackZip", e);
return false;
}
return true;
}

How do you unzip a file with Storage Volume (/StorageAccessFramework)?

I'm using this code (Android 7.0/Nougat) to unpack a zip file in external storage (including multiple folder levels inside):
try {
ZipFile zip = new ZipFile(zippath);
Enumeration enu = zip.entries();
while(enu.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry) enu.nextElement();
BufferedInputStream bis = null;
String fileName = null;
try {
fileName = zipEntry.getName();
fileName = fileName.replace("\\",File.separator).replace("/",File.separator);
int p = fileName.lastIndexOf(File.separator);
if(p>=0) {
File fd=new File(folderpath+File.separator+fileName.substring(0,p));
fd.mkdirs();
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(folderpath+File.separator+fileName));
bis = new BufferedInputStream(zip.getInputStream(zipEntry));
byte[] buffer = new byte[10000];
int len = 0;
while ((len = bis.read(buffer, 0, 10000)) > 0) {
bos.write(buffer, 0, len);
}
bis.close();
bos.close();
} catch (IOException e1) {
e1.printStackTrace();
return;
}
}
} catch (IOException e2) {
e2.printStackTrace();
}
To get write access to the SD card I'm using createAccessIntent (Storage Volume) which uses DocumentFiles instead of the normal File.
I already did this to get a ZipInputStream:
InputStream inputStream = this.getContentResolver().openInputStream(myDocumentFileZip.getUri());
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream);
ZipEntry zipEntry;
...and I'm guessing that you continue like this:
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
But what do you do from there - how to you copy the files onto the SD card and still keep the folder structure like in the code above but using what Storage Volume (or Storage Access Framework) provides?
Unzip with Storage Volume:
Careful: This way it creates copies if you unzip the same .zip file multiple times, while my original code in the first post (which you can't use for an SD card) doesn't but instead overwrites automatically!
try {
InputStream is = getContentResolver().openInputStream(myZip.getUri());
BufferedInputStream bis = new BufferedInputStream(is);
ZipInputStream zis = new ZipInputStream(bis);
ZipEntry zipEntry;
while ((zipEntry = zis.getNextEntry()) != null) {
String fileName = null;
try {
fileName = zipEntry.getName();
fileName = fileName.replace("\\",File.separator).replace("/",File.separator);
int p=fileName.lastIndexOf(File.separator);
DocumentFile destFolder = myDestFolder; //DocumentFile of the destination folder
String destName = fileName;
if (p>=0) {
String[] split = fileName.split(File.separator);
//If the .zip file contains multiple folder levels, this is where you
//have to check and then create them, e.g. for 3 levels:
if(split.length==1) {
destFolder = myFolder;
destName = filename;
} else if(split.length==2) {
if(mySubFolder==null) {
mySubFolder = myFolder.createDirectory(split[0]);
}
destFolder = mySubFolder;
destName = split[1];
} else if(split.length==3) {
if(mySubFolder==null) {
mySubFolder = myFolder.createDirectory(split[0]);
}
if(mySubSubFolder==null) {
mySubSubFolder = mySubFolder.createDirectory(split[1]);
}
destFolder = mySubSubFolder;
destName = split[2];
}
}
DocumentFile df = null;
//Now you have to tell it what file extensions ("MIME" type) you want to use, e.g.:
if(destName.endsWith(".txt")) {
df = destFolder.createFile("text/plain",destName.substring(0,destName.length()-4));
} else if(destName.endsWith(".jpg")) {
df = destFolder.createFile("image/jpeg",destName.substring(0,destName.length()-4));
}
OutputStream out = getContentResolver().openOutputStream(df.getUri());
BufferedOutputStream bos = new BufferedOutputStream(out);
long zipfilesize = zipEntry.getSize();
byte[] buffer = new byte[10000];
int len = 0;
int totlen = 0;
while (((len = zis.read(buffer, 0, 10000)) > 0) && (totlen < zipfilesize)) {
bos.write(buffer, 0, len);
totlen += len;
}
bos.close();
} catch (IOException e1) {
e1.printStackTrace();
return;
}
}
is.close();
bis.close();
zis.close();
} catch (IOException e2) {
e2.printStackTrace();
}
Edit: Important: java.util.zip doesn't set the size or compressedSize (will return "-1"), that's why this code will only create files with a size of 0B with zip files that were created by the library - zip files that were created by hand (e.g. with WinRar) work fine. To fix it, replace
while (((len = zis.read(buffer, 0, 10000)) > 0) && (totlen < zipfilesize)) {
with
while (((len = zis.read(buffer, 0, 10000)) > 0)) {
It's possible to do this because:
the call to ZipInputStream.getNextEntry() positions the InputStream at the start of the entry and therefore supplying the ZipInputStream is the equivalent of supplying a ZipEntry's InputStream.
Source: https://stackoverflow.com/a/3233600/2016165
The disadvantages with this (in comparison to my non-StorageVolume version) are that you a) can't get the total amount of files in the zip and b) also can't get the (total) size of the files, which means that you can't set the progress bar in an "Unzipping..." dialog unless you iterate through all the zip entries first to count them.

Android created Zip does not hold contained file sizes

I have put in place the ability to archive user data for my application by creating a compressed file.
I create the compressed file like this :
try
{
int iBufferSize = 2048;
int iByteCount = 0;
byte btData[] = new byte[iBufferSize];
File fZipFile = new File(ARCHIVE_FILE);
FileOutputStream fos = new FileOutputStream(fZipFile);
ZipOutputStream zos = new ZipOutputStream(fos);
BufferedInputStream bis = null;
FileInputStream fIn = null;
ZipEntry ze = null;
File[] fPartitions = new File(FILES_PATH).listFiles();
for(File fPartition : fPartitions)
{
lFileSize = fPartition.length();
fIn = new FileInputStream(fPartition);
bis = new BufferedInputStream(fIn, iBufferSize);
ze = new ZipEntry(fPartition.getName());
ze.setSize(lFileSize);
zos.putNextEntry(ze);
while((iByteCount = bis.read(btData, 0, iBufferSize)) != -1)
{
zos.write(btData, 0, iByteCount);
}
bis.close();
zos.closeEntry();
}
zos.close();
bis.close();
fIn.close();
bOk = true;
}
catch(Exception e)
{
e.printStackTrace();
}
Now, when I try to restore the contents, I am not able to get the original file size. Here is how I am doing the expansion :
try
{
// open the archive
FileInputStream fis = new FileInputStream(fArchive);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
bBuffer = new byte[2048];
// iterate through all files, putting them back into the same place
ze = zis.getNextEntry();
while(ze != null)
{
strFileName = ze.getName();
strFileName = FILE_PATH + "/" + strFileName;
fCheck = new File(strFileName);
lZipFileSize = ze.getSize(); // <--- returns -1 systematically
lTargetFileSize = fCheck.length();
fCheck = null;
FileOutputStream fos = new FileOutputStream(strFileName);
// read the data
while((iCount = zis.read(bBuffer)) != -1)
{
fos.write(bBuffer, 0, iCount);
}
fos.close();
ze = zis.getNextEntry();
}
zis.close();
fis.close();
bOk = true;
}
catch(Exception e)
{
e.printStackTrace();
}
I have noted that ze.getSize() is returning -1 systematically.
How can I store and get the individual file sizes ?
Ok, so if you come across this problem, here is how I worked around it.
Use the ZipFile class for reading the zip file.
Inspired from ZipInputStream: getting file size of -1 when reading and The ZipFileTest.java Android example source code

Android - some unzipped files have 0 size (are empty)

i'm facing a problem with unzipping files in Android. Here is the code snippet:
public void unzip() {
try {
FileInputStream fin = new FileInputStream(_zipFile);
BufferedInputStream in = new BufferedInputStream(fin);
ZipInputStream zin = new ZipInputStream(in);
ZipEntry ze = null;
while ((ze = zin.getNextEntry()) != null) {
Log.v("Decompress", "Unzipping " + ze.getName());
if(ze.isDirectory()) {
_dirChecker(ze.getName());
} else {
FileOutputStream fout = new FileOutputStream(_location + ze.getName());
BufferedOutputStream out = new BufferedOutputStream(fout);
byte[] buffer = new byte[1024];
int length;
while ((length = zin.read(buffer,0,1024)) >= 0) {
out.write(buffer,0,length);
}
/* while ((length = zin.read(buffer))>0) {
out.write(buffer, 0, length);
}*/
/*for (int c = zin.read(); c != -1; c = zin.read()) {
fout.write(c);
}*/
zin.closeEntry();
fout.close();
}
}
zin.close();
} catch(Exception e) {
Log.e("Decompress", "unzip", e);
}
}
Smaller files (smaller than 10kB) are unzipped like empty - size 0 (html files, .jpg). Other files are ok. If I use this same code, but without buffers all the files are ok - ofcourse, unzipping without buffers is out of the question since it runs too long. Files are stored on SD card on real device. I have already tried setting smaller buffer size ( even new byte[2]). Thanks in advance...
Try this code instead,
public void doUnzip(String inputZipFile, String destinationDirectory)
throws IOException {
int BUFFER = 2048;
List zipFiles = new ArrayList();
File sourceZipFile = new File(inputZip);
File unzipDestinationDirectory = new File(destinationDirectory);
unzipDestinationDirectory.mkdir();
ZipFile zipFile;
// Open Zip file for reading
zipFile = new ZipFile(sourceZipFile, ZipFile.OPEN_READ);
// Create an enumeration of the entries in the zip file
Enumeration zipFileEntries = zipFile.entries();
// Process each entry
while (zipFileEntries.hasMoreElements()) {
// grab a zip file entry
ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
String currentEntry = entry.getName();
File destFile = new File(unzipDestinationDirectory, currentEntry);
// destFile = new File(unzipDestinationDirectory, destFile.getName());
if (currentEntry.endsWith(".zip")) {
zipFiles.add(destFile.getAbsolutePath());
}
// grab file's parent directory structure
File destinationParent = destFile.getParentFile();
// create the parent directory structure if needed
destinationParent.mkdirs();
try {
// extract file if not a directory
if (!entry.isDirectory()) {
BufferedInputStream is =
new BufferedInputStream(zipFile.getInputStream(entry));
int currentByte;
// establish buffer for writing file
byte data[] = new byte[BUFFER];
// write the current file to disk
FileOutputStream fos = new FileOutputStream(destFile);
BufferedOutputStream dest =
new BufferedOutputStream(fos, BUFFER);
// read and write until last byte is encountered
while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, currentByte);
}
dest.flush();
dest.close();
is.close();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
zipFile.close();
for (Iterator iter = zipFiles.iterator(); iter.hasNext();) {
String zipName = (String)iter.next();
doUnzip(
zipName,
destinationDirectory +
File.separatorChar +
zipName.substring(0,zipName.lastIndexOf(".zip"))
);
}
}

Extrakting Zip to SD-Card is very slow. How can i optimize performance?

my app downloads a zip with about 350 files. A mix of JPG and HTML files. The function i wrote to do it works just fine but the unzipping takes for ever.
At first i thought the reason might be that writing to the sd-card is slow. but when i unzip the same zip with an other app on my phone it works much faster. is there anything that i could do to optimize it?
here is the code:
private void extract() {
try {
FileInputStream inStream = new FileInputStream(targetFilePath);
ZipInputStream zipStream = new ZipInputStream(new BufferedInputStream(inStream));
ZipEntry entry;
ZipFile zip = new ZipFile(targetFilePath);
//i know the contents for the zip so i create the dirs i need in advance
new File(targetFolder).mkdirs();
new File(targetFolder + "META-INF").mkdir();
new File(targetFolder + "content").mkdir();
int extracted = 0;
while((entry = zipStream.getNextEntry()) != null) {
if (entry.isDirectory()) {
new File(targetFolder + entry.getName()).mkdirs();
} else {
FileOutputStream outStream = new FileOutputStream(targetFolder + entry.getName());
for (int c = zipStream.read(); c != -1; c = zipStream.read()) {
outStream.write(c);
}
zipStream.closeEntry();
outStream.close();
extracted ++;
}
publishProgress(""+(int)extracted*100/zip.size());
}
zipStream.close();
inStream.close();
//
new File(targetFilePath).delete();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
thanks to CommonsWare i modified my code like this:
int size;
byte[] buffer = new byte[2048];
FileOutputStream outStream = new FileOutputStream(targetFolder + entry.getName());
BufferedOutputStream bufferOut = new BufferedOutputStream(outStream, buffer.length);
while((size = zipStream.read(buffer, 0, buffer.length)) != -1) {
bufferOut.write(buffer, 0, size);
}
bufferOut.flush();
bufferOut.close();
big performance difference.
Thanks a lot.
You are reading and writing a byte at a time. Consider reading and writing a larger block at a time.
Just use this method once and believe me its a super fast process.. It will unzip all the files without skipping any file with in 1 second.
public boolean rajDhaniSuperFastUnzip(String inputZipFile, String destinationDirectory)
{
try {
int BUFFER = 2048;
List<String> zipFiles = new ArrayList<String>();
File sourceZipFile = new File(inputZipFile);
File unzipDestinationDirectory = new File(destinationDirectory);
unzipDestinationDirectory.mkdir();
ZipFile zipFile;
zipFile = new ZipFile(sourceZipFile, ZipFile.OPEN_READ);
Enumeration<?> zipFileEntries = zipFile.entries();
while (zipFileEntries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
String currentEntry = entry.getName();
File destFile = new File(unzipDestinationDirectory, currentEntry);
if (currentEntry.endsWith(".zip")) {
zipFiles.add(destFile.getAbsolutePath());
}
File destinationParent = destFile.getParentFile();
destinationParent.mkdirs();
try {
if (!entry.isDirectory()) {
BufferedInputStream is =
new BufferedInputStream(zipFile.getInputStream(entry));
int currentByte;
byte data[] = new byte[BUFFER];
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();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
zipFile.close();
for (Iterator<String> iter = zipFiles.iterator(); iter.hasNext();) {
String zipName = (String)iter.next();
doUnzip(
zipName,
destinationDirectory +
File.separatorChar +
zipName.substring(0,zipName.lastIndexOf(".zip"))
);
}
} catch (IOException e) {
e.printStackTrace();
return false ;
}
return true;
}
Hope this will help you.. Happy Coding :)

Categories

Resources