How to get an InputStream representing one file in a ZipInputStream - java

I have the following situation:
I am getting a Zip File sent over the network in the Form of a Byte Array. I don't want to save that file locally, but instead target an individual file in that archive and import it into my solution as an InputStream.
Thus far, I have managed to get to the point where I can identify the correct ZipEntry. However, from here all the Zip-tutorials continue by reading the entry by its file name, which naturally does not work in my case since the zip file does not exist locally.
private void InputStream getReqifInputStreamFrom(byte[] reqifzFileBytes){
ByteArrayInputStream inputStream = new ByteArrayInputStream(reqifzFileBytes);
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
String fileName = zipEntry.getName();
if (fileName.endsWith(REQIF_FILE_SUFFIX)) {
return ???;
}
zipEntry = zipInputStream.getNextEntry();
}
}
So, what can I do at that point to return an InputStream that represents exactly the one file that the ZipEntry represents at that point? I have considered just returning the zipInputStream, but since I don't know exactly how that one works I'm afraid that doing so would also include all files that are still in the stream after that file to be returned as well.

You're virtually there. Try
private InputStream getReqifInputStreamFrom(byte[] reqifzFileBytes) throws IOException {
InputStream result = null;
ByteArrayInputStream inputStream = new ByteArrayInputStream(reqifzFileBytes);
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
String fileName = zipEntry.getName();
if (fileName.endsWith(REQIF_FILE_SUFFIX)) {
result = zipInputStream;
break;
}
zipEntry = zipInputStream.getNextEntry();
}
return result;
}

Related

Java program ignoring all the files inside the zip file [duplicate]

This question already has answers here:
How to unzip files recursively in Java?
(10 answers)
Closed last month.
I have program when I give a zip folder path via console. It will go through each item inside that folder (every child item, children of child, etc..). But if it encounters a zip folder it will ignore everything inside the zip folder, I need to read everything including files inside zip folders.
Here is the method that goes through each item:
public static String[] getLogBuffers(String path) throws IOException//path is given via console
{
String zipFileName = path;
String destDirectory = path;
BufferedInputStream errorLogBuffer = null;
BufferedInputStream windowLogBuffer = null;
String strErrorLogFileContents="";
String strWindowLogFileContents="";
String[] errorString=new String[2];
byte[] buffer = new byte[1024];
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName));
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null)
{
String filePath = destDirectory + "/" + zipEntry.getName();
System.out.println("unzipping" + filePath);
if (!zipEntry.isDirectory())
{
if (zipEntry.getName().endsWith("errorlog.txt"))
{
ZipFile zipFile = new ZipFile(path);
InputStream errorStream = zipFile.getInputStream(zipEntry);
BufferedInputStream bufferedInputStream=new BufferedInputStream(errorStream);
byte[] contents = new byte[1024];
System.out.println("ERRORLOG NAME"+zipEntry.getName());
int bytesRead = 0;
while((bytesRead = bufferedInputStream.read(contents)) != -1) {
strErrorLogFileContents += new String(contents, 0, bytesRead);
}
}
if (zipEntry.getName().endsWith("windowlog.txt"))
{ ZipFile zipFile = new ZipFile(path);
InputStream windowStream = zipFile.getInputStream(zipEntry);
BufferedInputStream bufferedInputStream=new BufferedInputStream(windowStream);
byte[] contents = new byte[1024];
System.out.println("WINDOWLOG NAME"+zipEntry.getName());
int bytesRead = 0;
while((bytesRead = bufferedInputStream.read(contents)) != -1) {
strWindowLogFileContents += new String(contents, 0, bytesRead);
}
}
}
zis.closeEntry();
zipEntry = zis.getNextEntry();
}
errorString[0]=strErrorLogFileContents;
errorString[1]=strWindowLogFileContents;
zis.closeEntry();
zis.close();
System.out.println("Buffers ready");
return errorString;
}
Items accessed inside the parent zip folder (my console output):
unzippingC:logFolders/logX3.zip/logX3/
unzippingC:logFolders/logX3.zip/logX3/Anan/
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/2021-11-23_103518.zip
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/errorlog.txt
unzippingC:logX3.zip/logX3/Anan/errorreports/version.txt
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/windowlog.txt
As you can see the program only go until 2021-11-23_103518.zip and goes in another path after that but 2021-11-23_103518.zip has children items(files) that I need to access
appreciate any help, thanks
A zip file is not a folder. Although Windows treats a zip file as if it’s a folder,* it is not a folder. A .zip file is a single file with an internal table of entries, each containing compressed data.
Each inner .zip file you read requires a new ZipFile or ZipInputStream. There is no way around that.
You should not create new ZipFile instances to read the same .zip file’s entries. You only need one ZipFile object. You can go through its entries with its entries() method, and you can read each entry with the ZipFile’s getInputStream method.
(I wouldn’t be surprised if using multiple objects to read the same zip file were to run into file locking problems on Windows.)
try (ZipFile zipFile = new ZipFile(path))
{
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().endsWith("errorlog.txt"))
{
try (InputStream errorStream = zipFile.getInputStream(zipEntry))
{
// ...
}
}
}
}
Notice that no other ZipFile or ZipInputStream objects are created. Only zipFile reads and traverses the file. Also notice the use of a try-with-resources statement to implicitly close the ZipFile and the InputStream.
You should not use += to build a String. Doing so creates a lot of intermediate String objects which will have to be garbage collected, which can hurt your program’s performance. You should wrap each zip entry’s InputStream in an InputStreamReader, then use that Reader’s transferTo method to append to a single StringWriter that holds your combined log.
String strErrorLogFileContents = new StringWriter();
String strWindowLogFileContents = new StringWriter();
try (ZipFile zipFile = new ZipFile(path))
{
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().endsWith("errorlog.txt"))
{
try (Reader entryReader = new InputStreamReader(
zipFile.getInputStream(zipEntry),
StandardCharsets.UTF_8))
{
entryReader.transferTo(strErrorLogFileContents);
}
}
}
}
Notice the use of StandardCharsets.UTF_8. It is almost never correct to create a String from bytes without specifying the Charset. If you don’t provide the Charset, Java will use the system’s default Charset, which means your program will behave differently in Windows than it will on other operating systems.
If you are stuck with Java 8, you won’t have the transferTo method of Reader, so you will have to do the work yourself:
if (zipEntry.getName().endsWith("errorlog.txt"))
{
try (Reader entryReader = new BufferedReader(
new InputStreamReader(
zipFile.getInputStream(zipEntry),
StandardCharsets.UTF_8)))
{
int c;
while ((c = entryReader.read()) >= 0)
{
strErrorLogFileContents.write(c);
}
}
}
The use of BufferedReader means you don’t need to create your own array and implement bulk reads yourself. BufferedReader already does that for you.
As mentioned above, a zip entry which is itself an inner zip file requires a brand new ZipFile or ZipInputStream object to read it. I recommend copying the entry to a temporary file, since reading from a ZipInputStream made from another ZipInputStream is known to be slow, then deleting the temporary file after you’re done reading it.
try (ZipFile zipFile = new ZipFile(path))
{
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().endsWith(".zip"))
{
Path tempZipFile = Files.createTempFile(null, ".zip");
try (InputStream errorStream = zipFile.getInputStream(zipEntry))
{
Files.copy(errorStream, tempZipFile,
StandardCopyOption.REPLACE_EXISTING);
}
String[] logsFromZip = getLogBuffers(tempZipFile.toString());
strErrorLogFileContents.write(logsFromZip[0]);
strWindowLogFileContents.write(logsFromZip[1]);
Files.delete(tempZipFile);
}
}
}
Finally, consider creating a meaningful class for your return value. An array of Strings is difficult to understand. A caller won’t know that it always contains exactly two elements and won’t know what those two elements are. A custom return type would be pretty short:
public class Logs {
private final String errorLog;
private final String windowLog;
public Logs(String errorLog,
String windowLog)
{
this.errorLog = errorLog;
this.windowLog = windowLog;
}
public String getErrorLog()
{
return errorLog;
}
public String getWindowLog()
{
return windowLog;
}
}
As of Java 16, you can use a record to make the declaration much shorter:
public record Logs(String errorLog,
String windowLog)
{ }
Whether you use a record or write out the class, you can use it as a return type in your method:
public static Logs getLogBuffers(String path) throws IOException
{
// ...
return new Logs(
strErrorLogFileContents.toString(),
strWindowLogFileContents.toString());
}
* The Windows explorer shell’s practice of treating zip files as folders is a pretty bad user interface. I know I’m not the only one who thinks so. It often ends up making things more difficult for users instead of easier.

ZipEntry to File

Is there a direct way to unpack a java.util.zip.ZipEntry to a File?
I want to specify a location (like "C:\temp\myfile.java") and unpack the Entry to that location.
There is some code with streams on the net, but I would prefer a tested library function.
Use ZipFile class
ZipFile zf = new ZipFile("zipfile");
Get entry
ZipEntry e = zf.getEntry("name");
Get inpustream
InputStream is = zf.getInputStream(e);
Save bytes
Files.copy(is, Paths.get("C:\\temp\\myfile.java"));
Use the below code to extract the "zip file" into File's then added in the list using ZipEntry. Hopefully, this will help you.
private List<File> unzip(Resource resource) {
List<File> files = new ArrayList<>();
try {
ZipInputStream zin = new ZipInputStream(resource.getInputStream());
ZipEntry entry = null;
while((entry = zin.getNextEntry()) != null) {
File file = new File(entry.getName());
FileOutputStream os = new FileOutputStream(file);
for (int c = zin.read(); c != -1; c = zin.read()) {
os.write(c);
}
os.close();
files.add(file);
}
} catch (IOException e) {
log.error("Error while extract the zip: "+e);
}
return files;
}
Use ZipInputStream to move to the desired ZipEntry by iterating using the getNextEntry() method. Then use the ZipInputStream.read(...) method to read the bytes for the current ZipEntry. Output those bytes to a FileOutputStream pointing to a file of your choice.

How to access a zip file into a zip file in Java

I'm trying to read .srt files that are located in zip file itself located in a zip file. I succeed to read .srt files that were in a simple zip with the extract of code below :
for (Enumeration enume = fis.entries(); enume.hasMoreElements();) {
ZipEntry entry = (ZipEntry) enume.nextElement();
fileName = entry.toString().substring(0,entry.toString().length()-4);
try {
InputStream in = fis.getInputStream(entry);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String ext = entry.toString().substring(entry.toString().length()-4, entry.toString().length());
But now i don't know how i could get to the zip file inside the zip file.
I tried using ZipFile fis = new ZipFile(filePath) with filePath being the path of the zip file + the name of zip file inside. It didn't recognize the path so i don't know if i am clear.
Thanks.
ZipFile only works with real files, because it's intended for use as a random access mechanism which needs to be able to seek directly to specific locations in the file to read entries by name. But as VGR suggests in the comments, while you can't get random access to the zip-inside-a-zip you can use ZipInputStream, which provides strictly sequential access to the entries and works with any InputStream of zip-format data.
However, ZipInputStream has a slightly odd usage pattern compared to other streams - calling getNextEntry reads the entry metadata and positions the stream to read that entry's data, you read from the ZipInputStream until it reports EOF, then you (optionally) call closeEntry() before moving on to the next entry in the stream.
The critical point is that you must not close() the ZipInputStream until you have finished reading the final entry, so depending what you want to do with the entry data you might need to use something like the commons-io CloseShieldInputStream to guard against the stream getting closed prematurely.
try(ZipInputStream outerZip = new ZipInputStream(fis)) {
ZipEntry outerEntry = null;
while((outerEntry = outerZip.getNextEntry()) != null) {
if(outerEntry.getName().endsWith(".zip")) {
try(ZipInputStream innerZip = new ZipInputStream(
new CloseShieldInputStream(outerZip))) {
ZipEntry innerEntry = null;
while((innerEntry = innerZip.getNextEntry()) != null) {
if(innerEntry.getName().endsWith(".srt")) {
// read the data from the innerZip stream
}
}
}
}
}
}
Find the code to extract .zip files recursively:
public void extractFolder(String zipFile) throws ZipException, IOException {
System.out.println(zipFile);
int BUFFER = 2048;
File file = new File(zipFile);
ZipFile zip = new ZipFile(file);
String newPath = zipFile.substring(0, zipFile.length() - 4);
new File(newPath).mkdir();
Enumeration zipFileEntries = zip.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(newPath, currentEntry);
//destFile = new File(newPath, destFile.getName());
File destinationParent = destFile.getParentFile();
// create the parent directory structure if needed
destinationParent.mkdirs();
if (!entry.isDirectory())
{
BufferedInputStream is = new BufferedInputStream(zip
.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();
}
if (currentEntry.endsWith(".zip"))
{
// found a zip file, try to open
extractFolder(destFile.getAbsolutePath());
}
}
}

How to decompress a zip archive which has sub directories?

Say I have a zip file MyZipFile.zip which contains (1) a file MyFile.txt and (2) a folder MyFolder which contains a file MyFileInMyFolder.txt, i.e. something as follows:
MyZipFile.zip
|-> MyFile.txt
|-> MyFolder
|-> MyFileInMyFolder.txt
I want to decompress this zip archive. The most common code sample I have been able to find searching online uses the ZipInputStream class somewhat like the code pasted at the bottom of this question. The problem with this however, using the example above, is that it will create MyFolder but will not decompress the contents of MyFolder. Anyone know whether it is possible to decompress the contents of a folder in a zip archive using ZipInputStream or by any other means?
public static boolean unzip(File sourceZipFile, File targetFolder)
{
// pre-stuff
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(sourceZipFile));
ZipEntry zipEntry = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null)
{
File zipEntryFile = new File(targetFolder, zipEntry.getName());
if (zipEntry.isDirectory())
{
if (!zipEntryFile.exists() && !zipEntryFile.mkdirs())
return false;
}
else
{
FileOutputStream fileOutputStream = new FileOutputStream(zipEntryFile);
byte buffer[] = new byte[1024];
int count;
while ((count = zipInputStream.read(buffer, 0, buffer.length)) != -1)
fileOutputStream.write(buffer, 0, count);
fileOutputStream.flush();
fileOutputStream.close();
zipInputStream.closeEntry();
}
}
zipInputStream.close();
// post-stuff
}
Try this:
ZipInputStream zis = null;
try {
zis = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// Create a file on HDD in the destinationPath directory
// destinationPath is a "root" folder, where you want to extract your ZIP file
File entryFile = new File(destinationPath, entry.getName());
if (entry.isDirectory()) {
if (entryFile.exists()) {
logger.log(Level.WARNING, "Directory {0} already exists!", entryFile);
} else {
entryFile.mkdirs();
}
} else {
// Make sure all folders exists (they should, but the safer, the better ;-))
if (entryFile.getParentFile() != null && !entryFile.getParentFile().exists()) {
entryFile.getParentFile().mkdirs();
}
// Create file on disk...
if (!entryFile.exists()) {
entryFile.createNewFile();
}
// and rewrite data from stream
OutputStream os = null;
try {
os = new FileOutputStream(entryFile);
IOUtils.copy(zis, os);
} finally {
IOUtils.closeQuietly(os);
}
}
}
} finally {
IOUtils.closeQuietly(zis);
}
Note, that it uses Apache Commons IO to handle stream copying / closing.

How to read a war/jar file which does not have manifest

I have a war file which does not contains manifest not even META-INF folder. Now my problem is that I wrote a code which was working fine with normal war files containing manifests. Now I am required to read a war file which does not contain manifest.
When I check
while ((ze = zis.getNextEntry()) != null)
This condition is just skipped. Is there any API which treats it just as a normal zip file or is there any workaround.
I have tried with JarEntry as well as ZipEntry. Here is a small snippet that should be explanatory.
try {
FileInputStream fis = new FileInputStream(applicationPack);
ZipArchiveInputStream zis = new ZipArchiveInputStream(fis);
ArchiveEntry ze = null;
File applicationPackConfiguration;
while ((ze = zis.getNextEntry()) != null) {
// do someting
}
What can be done ?
You can simply list contents with ZipFile class:
try {
// Open the ZIP file
ZipFile zf = new ZipFile("filename.zip");
// Enumerate each entry
for (Enumeration entries = zf.entries(); entries.hasMoreElements();) {
// Get the entry name
String zipEntryName = ((ZipEntry)entries.nextElement()).getName();
}
} catch (IOException e) {
}
Example taken from here. Another example for retrieving the file from zip.
Update:
Code above indeed has problems with zip files that contain only directory as a top-level element.
This code works (tested):
try {
// Open the ZIP file
FileInputStream fis = new FileInputStream(new File("/your.war"));
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
ZipEntry entry = null;
while ((entry = zis.getNextEntry()) != null)
// Get the entry name
System.out.println(entry.getName());
} catch (IOException e) {
}
You can use classes from java.util.zip package. Just replace ZipArchiveInputStream with ZipInputStream and ArchiveEntry with ZipEntry:
FileInputStream fis = new FileInputStream(new File("/path/to/your.war"));
ZipInputStream zis = new ZipInputStream(fis);
ZipEntry ze = null;
while ((ze = zis.getNextEntry()) != null) {
System.out.println(ze.getName());
}

Categories