I have retrieved a zip entry from a zip file like so.
InputStream input = params[0];
ZipInputStream zis = new ZipInputStream(input);
ZipEntry entry;
try {
while ((entry = zis.getNextEntry())!= null) {
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
This works fine and its getting my ZipEntry no problem.
My Question
How can I get the contents of these ZipEntries into a String as they are xml and csv files.
you have to read from the ZipInputStream:
StringBuilder s = new StringBuilder();
byte[] buffer = new byte[1024];
int read = 0;
ZipEntry entry;
while ((entry = zis.getNextEntry())!= null) {
while ((read = zis.read(buffer, 0, 1024)) >= 0) {
s.append(new String(buffer, 0, read));
}
}
When you exit from the inner while save the StringBuilder content, and reset it.
With defined encoding (e.g. UTF-8) and without creation of Strings:
import java.util.zip.ZipInputStream;
import java.util.zip.ZipEntry;
import java.io.ByteArrayOutputStream;
import static java.nio.charset.StandardCharsets.UTF_8;
try (
ZipInputStream zis = new ZipInputStream(input, UTF_8);
ByteArrayOutputStream baos = new ByteArrayOutputStream()
) {
byte[] buffer = new byte[1024];
int read = 0;
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null)
while ((read = zis.read(buffer, 0, buffer.length)) > 0)
baos.write(buffer, 0, read);
String content = baos.toString(UTF_8.name());
}
Here is the approach, which does not break Unicode characters:
final ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(content));
final InputStreamReader isr = new InputStreamReader(zis);
final StringBuilder sb = new StringBuilder();
final char[] buffer = new char[1024];
while (isr.read(buffer, 0, buffer.length) != -1) {
sb.append(new String(buffer));
}
System.out.println(sb.toString());
I would use apache's IOUtils
ZipEntry entry;
InputStream input = params[0];
ZipInputStream zis = new ZipInputStream(input);
try {
while ((entry = zis.getNextEntry())!= null) {
String entryAsString = IOUtils.toString(zis, StandardCharsets.UTF_8);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
IOUtils.closeQuietly(zis);
Kotlin version but can be used in Java as well
val zipInputStream = ZipInputStream(inStream)
var zipEntry = zipInputStream.nextEntry
while(zipEntry != null) {
println("Name of file : " + zipEntry.name)
val fileContent = String(zipInputStream.readAllBytes(), StandardCharsets.UTF_8)
println("File content : $fileContent")
zipEntry = zipInputStream.nextEntry
}
Related
here is my code:
final InputStream inputStream = MY_RECEIVED_INPUT_STREAM;
ZipInputStream zis = new ZipInputStream(inputStream);
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
zipEntry = zis.getNextEntry();
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
}
zis.closeEntry();
zis.close();
I receive zip file with many files inside. I want to write these files to database. What I want is to get bytes from each and every ZipEntry and save them to database as Blob (xxxxxx.... part).
How can I get bytes from zipEntry?
I don't have ZipFile, so I can't use something like this:
InputStream stream = zipFile.getInputStream(entry);
or
byte[] bytes = IOUtils.readAllBytes(zipFile.getInputStream(entry));
Thanks in advance.
You can use something like this:
InputStream is = MY_RECEIVED_INPUT_STREAM;
BufferedInputStream bis = null;
ZipInputStream zis = null;
ByteArrayOutputStream out = null;
String name = null;
byte[] b = new byte[8192];
int len = 0;
try {
bis = new BufferedInputStream(is);
zis = new ZipInputStream(bis);
ZipEntry zipEntry = null;
while ((zipEntry = zis.getNextEntry()) != null) {
//name of file
name = zipEntry.getName();
if (zipEntry.isDirectory()) {
//I'm skipping directories in this example
continue;
}
out = new ByteArrayOutputStream();
while ((len = zis.read(b)) > 0) {
out.write(b, 0, len);
}
//save to DB - db_save(String file_name, byte[] file_bytes)
db_save(name,out.toByteArray());
out.close();
}
} finally {
if (zis != null) {
zis.close();
}
if (bis != null) {
bis.close();
}
if (is != null) {
is.close();
}
}
ZipFile makes this easier to read but the basic rule is that the ZipInputStream is lined up to the content relating to the current ZipEntry.
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/zip/ZipInputStream.html#read(byte%5B%5D,int,int)
Read directly from zis until 0 and don't close() until all entries are handled.
ZipEntry zipEntry;
while ((zipEntry = zis.getNextEntry()) != null) {
// xxx Do BLOB creation
zis.transferTo(outputStream); // Java9
}
(PS You don't need to call closeEntry())
i have a java service that take a byte array in order to convert it in one or more pdf files or jpg files. i know this service work because it's called from another java system that correctly send files with no problems. now i need to call this services from a angular js system, the byte array, once reached the java application, it's converted first to a ByteArrayInputStream with not problems then the ByteArrayInputStream it's converted to ZipInputStream but fail. i suspect the problem is the type of encoding of the array.
This is my code:
public static Hashtable<String, ByteArrayOutputStream> unzipFile(InputStream inputStream){
logger.info("Unzip del File");
Hashtable<String, ByteArrayOutputStream> fileOutputTable = new Hashtable<String, ByteArrayOutputStream>();
try{
byte[] buf = new byte[1024];
ZipEntry zipentry;
ByteArrayInputStream bis = (ByteArrayInputStream)inputStream;
ZipInputStream zipinputstream = new ZipInputStream(bis); // here conversion fail
while((zipentry = zipinputstream.getNextEntry()) != null){
String entryName = zipentry.getName();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int n = 0;
while((n = zipinputstream.read(buf, 0, 1024)) > -1)
baos.write(buf, 0, n);
baos.close();
fileOutputTable.put(entryName, baos);
zipinputstream.closeEntry();
}
zipinputstream.close();
}catch(Exception e){
logger.error("Errore nel tentativo di unzip del file");
e.printStackTrace();
}
logger.info("RETURN: " + fileOutputTable.toString());
return fileOutputTable;
}
public static Hashtable<String, ByteArrayOutputStream> unzipFile(InputStream inputStream){
logger.info("Unzip del File");
Hashtable<String, ByteArrayOutputStream> fileOutputTable = new Hashtable<String, ByteArrayOutputStream>();
try{
byte[] buf = new byte[1024];
ZipEntry zipentry;
ByteArrayInputStream bis = (ByteArrayInputStream)inputStream;
ZipInputStream zipinputstream = new ZipInputStream(bis); // here conversion fail
while((zipentry = zipinputstream.getNextEntry()) != null){
String entryName = zipentry.getName();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int n = 0;
while((n = zipinputstream.read(buf, 0, 1024)) > -1)
baos.write(buf, 0, n);
baos.close();
fileOutputTable.put(entryName, baos);
zipinputstream.closeEntry();
}
zipinputstream.close();
}catch(Exception e){
logger.error("Errore nel tentativo di unzip del file");
e.printStackTrace();
}
logger.info("RETURN: " + fileOutputTable.toString());
return fileOutputTable;
}
I'd like to create a zip which stores two different files with the same name, but I'm unable (using java.util.zip.ZipOutputStream) due to
java.util.zip.ZipException: duplicate entry:
exception. I know that it's possible, but I need an advise which library I can use for that purposes. Thanks!
UPD the code I'm using:
File zipFile = new File("C:\\Users\\user\\Desktop\\old.zip");
File outFile = new File("C:\\Users\\user\\Desktop\\new.zip");
if(!outFile.exists()) {
outFile.getParentFile().mkdirs();
outFile.createNewFile();
}
byte[] buf = new byte[1024];
ZipInputStream zin = new ZipInputStream(new FileInputStream(zipFile));
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outFile));
ZipEntry entry = zin.getNextEntry();
while (entry != null) {
String name = entry.getName();
out.putNextEntry(new ZipEntry(name));
int len;
while ((len = zin.read(buf)) > 0) {
out.write(buf, 0, len);
}
entry = zin.getNextEntry();
if("file".equals(name)) {
File fakeFile = new File("C:\\Users\\user\\Desktop\\file");
InputStream in = new FileInputStream(fakeFile);
out.putNextEntry(new ZipEntry("file"));
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.closeEntry();
in.close();
}
}
zin.close();
out.close();
I was able to bypass restriction thru reflection api:
Field namesField = ZipOutputStream.class.getDeclaredField("names");
namesField.setAccessible(true);
HashSet<String> names = (HashSet<String>) namesField.get(out);
And clearing names after each putNextEntry call
try (FileOutputStream fos = new FileOutputStream(zipFile);
ZipOutputStream zos = new ZipOutputStream(fos)) {
HashSet<String> names = new HashSet<String>();
for (String filePath : fileList) {
if(names.add(filePath))
{
String name = filePath.substring(directory.getAbsolutePath()
.length() + 1, filePath.length());
ZipEntry zipEntry = new ZipEntry(name);
zos.putNextEntry(zipEntry);
try (FileInputStream fis = new FileInputStream(filePath)) {
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) >= 0) {
zos.write(buffer, 0, length);
}
} catch (Exception e) {
e.printStackTrace();
throw new Exception();
}
zos.closeEntry();
}
}
names.clear();
} catch (IOException e) {
e.printStackTrace();
throw new Exception();
}
By using HashSet solved my issue of zip duplicate entry
This is my solution: adding postfixes for duplicate names:
String SPLIT_FILENAME_EXTENSION_REGEX = "\\.(?=[^\\.]+$)";
Map<String, Integer> namesPostfixes = new HashMap<>();
private void addZipPart(String fullName, InputStream attachmentStream, ZipOutputStream zos,
Map<String, Integer> namesPostfixes)
throws IOException, MessagingException {
String generatedName;
if (namesPostfixes.containsKey(fullName)) {
String namePart;
String extensionPart;
if (fullName.contains(".")) {
String[] splittedName = fullName.split(SPLIT_FILENAME_EXTENSION_REGEX);
namePart = splittedName[0];
extensionPart = "." + splittedName[1];
} else {
namePart = fullName;
extensionPart = "";
}
Integer counter = namesPostfixes.get(fullName);
generatedName = namePart + "_" + ++counter + extensionPart;
namesPostfixes.put(fullName, counter);
} else {
generatedName = fullName;
namesPostfixes.put(fullName, 0);
}
zos.putNextEntry(
new ZipEntry(generatedName));
IOUtils.copy(attachmentStream, zos);
zos.closeEntry();
}
Consider the code example that put a single file test_file.pdf into zip archive test.zip and then read this archive:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class Main {
public static void main(String[] args) {
File infile = new File("test_file.pdf");
try (
FileInputStream fis = new FileInputStream(infile);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("test.zip"));
) {
int bytesRead;
byte[] buffer = new byte[1024];
ZipEntry entry = new ZipEntry("data");
entry.setSize(infile.length());
zos.putNextEntry(entry);
while ((bytesRead = fis.read(buffer)) >= 0)
{
zos.write(buffer, 0, bytesRead);
}
zos.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
try (
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(
new FileInputStream(new File("test.zip"))));
) {
ZipEntry entry = zis.getNextEntry();
System.out.println("Entry size: " + entry.getSize());
zis.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Output:
Entry size: -1
But if create uncompressed zip archive (method ZipEntry.STORED), getSize() returns correct size:
import java.io.*;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class Main {
public static void main(String[] args) {
File infile = new File("test_file.pdf");
try (
FileInputStream fis = new FileInputStream(infile);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("test.zip"));
) {
int bytesRead;
byte[] buffer = new byte[1024];
CRC32 crc = new CRC32();
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(infile));
) {
crc.reset();
while ((bytesRead = bis.read(buffer)) != -1) {
crc.update(buffer, 0, bytesRead);
}
}
ZipEntry entry = new ZipEntry("data");
entry.setMethod(ZipEntry.STORED);
entry.setCompressedSize(infile.length());
entry.setSize(infile.length());
entry.setCrc(crc.getValue());
zos.putNextEntry(entry);
while ((bytesRead = fis.read(buffer)) >= 0)
{
zos.write(buffer, 0, bytesRead);
}
zos.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
try (
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(
new FileInputStream(new File("test.zip"))));
) {
ZipEntry entry = zis.getNextEntry();
System.out.println("Entry size: " + entry.getSize());
zis.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Output (for example but correct):
Entry size: 9223192
Compressed zip archives with correct entry.getSize() exists (e.g. zip archives by Ark program).
So question: how to create compressed (ZipEntry.DEFLATED or another if exists) zip archive that returns correct size of the entry using only the standard libraries?
I tried this recommendation but it also does not work:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class Main {
public static void main(String[] args) {
File infile = new File("test_file.pdf");
try (
FileInputStream fis = new FileInputStream(infile);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("test.zip"));
) {
int bytesRead;
byte[] buffer = new byte[1024];
ZipEntry entry = new ZipEntry("data");
entry.setSize(infile.length());
zos.putNextEntry(entry);
while ((bytesRead = fis.read(buffer)) >= 0)
{
zos.write(buffer, 0, bytesRead);
}
zos.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
try (
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(
new FileInputStream(new File("test.zip"))));
) {
ZipEntry entry = zis.getNextEntry();
byte[] buffer = new byte[1];
zis.read(buffer);
System.out.println("Entry size: " + entry.getSize());
zis.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Output:
Entry size: -1
You can only set the uncompressed size if you also set the CRC and compressed size as well. Since these information are stored before in a header before the actual data and ZipOutputStream can’t rewind arbitrary OutputStreams, it can’t calculate these values while writing and store them afterwards (but it will calculate them for verifying the provided values).
Here is a solution for calculating the values in one pass before the writing. It utilizes the fact that you can rewind a stream if it is backed by a file.
public static void main(String[] args) throws IOException {
File infile = new File("test_file.pdf");
File outfile = new File("test.zip");
try (FileInputStream fis = new FileInputStream(infile);
FileOutputStream fos = new FileOutputStream(outfile);
ZipOutputStream zos = new ZipOutputStream(fos) ) {
byte[] buffer = new byte[1024];
ZipEntry entry = new ZipEntry("data");
precalc(entry, fis.getChannel());
zos.putNextEntry(entry);
for(int bytesRead; (bytesRead = fis.read(buffer)) >= 0; )
zos.write(buffer, 0, bytesRead);
zos.closeEntry();
}
try(FileInputStream fin = new FileInputStream(outfile);
ZipInputStream zis = new ZipInputStream(fin) ) {
ZipEntry entry = zis.getNextEntry();
System.out.println("Entry size: " + entry.getSize());
System.out.println("Compressed size: " + entry.getCompressedSize());
System.out.println("CRC: " + entry.getCrc());
zis.closeEntry();
}
}
private static void precalc(ZipEntry entry, FileChannel fch) throws IOException {
long uncompressed = fch.size();
int method = entry.getMethod();
CRC32 crc = new CRC32();
Deflater def;
byte[] drain;
if(method != ZipEntry.STORED) {
def = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
drain = new byte[1024];
}
else {
def = null;
drain = null;
}
ByteBuffer buf = ByteBuffer.allocate((int)Math.min(uncompressed, 4096));
for(int bytesRead; (bytesRead = fch.read(buf)) != -1; buf.clear()) {
crc.update(buf.array(), buf.arrayOffset(), bytesRead);
if(def!=null) {
def.setInput(buf.array(), buf.arrayOffset(), bytesRead);
while(!def.needsInput()) def.deflate(drain, 0, drain.length);
}
}
entry.setSize(uncompressed);
if(def!=null) {
def.finish();
while(!def.finished()) def.deflate(drain, 0, drain.length);
entry.setCompressedSize(def.getBytesWritten());
}
entry.setCrc(crc.getValue());
fch.position(0);
}
It handles both, uncompressed and compressed entries, but unfortunately, only with the default compression level as ZipOutputStream has no method for querying the current level. So if you change the compression level you have to keep the precalc code in sync. Alternatively, you could move the logic into a subclass of ZipOutputStream and use the same Deflater so it will automatically have the same configuration.
A solution working with arbitrary source input streams would require buffering of the entire entry data.
A simple and elegant workaround is to write the ZipEntry to a temporary ZipOutputStream first. This is what the updateEntry method of the following code does. When the method has been called, the ZipEntry knows the size, compressed size and CRC, without having to calculate them explicitly. When it is written to the target ZipOutputStream, it will correctly write the values.
Original answer:
dirty but fast
public static void main(String[] args) throws IOException
{
FileInputStream fis = new FileInputStream( "source.txt" );
FileOutputStream fos = new FileOutputStream( "result.zip" );
ZipOutputStream zos = new ZipOutputStream( fos );
byte[] buf = new byte[fis.available()];
fis.read(buf);
ZipEntry e = new ZipEntry( "source.txt" );
updateEntry(e, buf);
zos.putNextEntry(e);
zos.write(buf);
zos.closeEntry();
zos.close();
}
private static void updateEntry(ZipEntry entry, byte[] buffer) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream( bos );
zos.putNextEntry(entry);
zos.write(buffer);
zos.closeEntry();
zos.close();
bos.close();
}
I am trying to convert an array of bytes into a ZIP file. I got bytes using the following code:
byte[] originalContentBytes= new Verification().readBytesFromAFile(new File("E://file.zip"));
private byte[] readBytesFromAFile(File file) {
int start = 0;
int length = 1024;
int offset = -1;
byte[] buffer = new byte[length];
try {
//convert the file content into a byte array
FileInputStream fileInuptStream = new FileInputStream(file);
BufferedInputStream bufferedInputStream = new BufferedInputStream(
fileInuptStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while ((offset = bufferedInputStream.read(buffer, start, length)) != -1) {
byteArrayOutputStream.write(buffer, start, offset);
}
bufferedInputStream.close();
byteArrayOutputStream.flush();
buffer = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
} catch (FileNotFoundException fileNotFoundException) {
fileNotFoundException.printStackTrace();
} catch (IOException ioException) {
ioException.printStackTrace();
}
return buffer;
}
But my problem now is with converting the byte array back into a ZIP file - how can it be done?
Note : The specified ZIP contains two files.
To get the contents from the bytes you can use
ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream(bytes));
ZipEntry entry = null;
while ((entry = zipStream.getNextEntry()) != null) {
String entryName = entry.getName();
FileOutputStream out = new FileOutputStream(entryName);
byte[] byteBuff = new byte[4096];
int bytesRead = 0;
while ((bytesRead = zipStream.read(byteBuff)) != -1)
{
out.write(byteBuff, 0, bytesRead);
}
out.close();
zipStream.closeEntry();
}
zipStream.close();
You probably are looking for code like this:
ZipInputStream z = new ZipInputStream(new ByteArrayInputStream(buffer))
now you can get the zip file contents via getNextEntry()
Here is a helper method
private fun getZipData(): ByteArray {
val zipFile: File = getTempZipFile() // Return a zip File
val encoded = Files.readAllBytes(Paths.get(zipFile.absolutePath))
zipFile.delete() // If you wish to delete the zip file
return encoded
}