From bbd0234e78dab37a1f5425915bc75d6f6bed334e Mon Sep 17 00:00:00 2001 From: Lee Date: Sun, 29 Sep 2019 21:46:00 +0800 Subject: [PATCH] #64 Enable charset selection for ZipFile and ZipInputStream (#67) * enable charset selection for ZipFile and InputStream * add test cases for charset specification in ZipFile and ZipInputStream * charset selection for ZipFile and ZipOutputStream enable charset selection for ZipFile(output APIs) and ZipOutputStream * add testcase for charset in ZipOutputStream * utf-8 bit flag should not be set when charset is specified * change the type of charset: from String to Charset * charset is not allowed to be null * Merge with master and adjust code * Minor cleanup --- src/main/java/net/lingala/zip4j/ZipFile.java | 40 ++++++-- .../zip4j/headers/FileHeaderFactory.java | 11 ++- .../lingala/zip4j/headers/HeaderReader.java | 15 +-- .../net/lingala/zip4j/headers/HeaderUtil.java | 7 +- .../lingala/zip4j/headers/HeaderWriter.java | 39 +++----- .../zip4j/io/inputstream/ZipInputStream.java | 21 ++++- .../io/outputstream/ZipOutputStream.java | 28 ++++-- .../zip4j/tasks/AbstractAddFileToZipTask.java | 20 ++-- .../tasks/AbstractZipTaskParameters.java | 12 +++ .../zip4j/tasks/AddFilesToZipTask.java | 8 +- .../zip4j/tasks/AddFolderToZipTask.java | 8 +- .../zip4j/tasks/AddStreamToZipTask.java | 15 +-- .../zip4j/tasks/ExtractAllFilesTask.java | 12 ++- .../lingala/zip4j/tasks/ExtractFileTask.java | 12 ++- .../zip4j/tasks/MergeSplitZipFileTask.java | 25 +++-- .../tasks/RemoveEntryFromZipFileTask.java | 29 ++++-- .../lingala/zip4j/tasks/SetCommentTask.java | 23 +++-- .../java/net/lingala/zip4j/AbstractIT.java | 4 + .../net/lingala/zip4j/AddFilesToZipIT.java | 67 ++++++++++++++ .../net/lingala/zip4j/CreateZipFileIT.java | 15 +++ .../net/lingala/zip4j/ExtractZipFileIT.java | 16 ++++ .../lingala/zip4j/RemoveFilesFromZipIT.java | 15 +++ .../net/lingala/zip4j/ZipFileZip64IT.java | 3 +- .../zip4j/headers/FileHeaderFactoryTest.java | 47 ++++++---- .../lingala/zip4j/headers/HeaderReaderIT.java | 32 +++---- .../lingala/zip4j/headers/HeaderUtilTest.java | 32 ++++++- .../lingala/zip4j/headers/HeaderWriterIT.java | 87 +++++++++++------- .../io/inputstream/ZipInputStreamIT.java | 16 ++++ .../io/outputstream/ZipOutputStreamIT.java | 35 +++++-- .../zip4j/testutils/ZipFileVerifier.java | 9 ++ ...testfile_with_chinese_filename_by_7zip.zip | Bin 0 -> 313 bytes 31 files changed, 519 insertions(+), 184 deletions(-) create mode 100644 src/main/java/net/lingala/zip4j/tasks/AbstractZipTaskParameters.java create mode 100644 src/test/resources/test-archives/testfile_with_chinese_filename_by_7zip.zip diff --git a/src/main/java/net/lingala/zip4j/ZipFile.java b/src/main/java/net/lingala/zip4j/ZipFile.java index e37f31d8..de61f300 100755 --- a/src/main/java/net/lingala/zip4j/ZipFile.java +++ b/src/main/java/net/lingala/zip4j/ZipFile.java @@ -37,8 +37,11 @@ import net.lingala.zip4j.tasks.ExtractFileTask; import net.lingala.zip4j.tasks.ExtractFileTask.ExtractFileTaskParameters; import net.lingala.zip4j.tasks.MergeSplitZipFileTask; +import net.lingala.zip4j.tasks.MergeSplitZipFileTask.MergeSplitZipFileTaskParameters; import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask; +import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.RemoveEntryFromZipFileTaskParameters; import net.lingala.zip4j.tasks.SetCommentTask; +import net.lingala.zip4j.tasks.SetCommentTask.SetCommentTaskTaskParameters; import net.lingala.zip4j.util.FileUtils; import net.lingala.zip4j.util.Zip4jUtil; @@ -46,6 +49,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; @@ -74,6 +79,7 @@ public class ZipFile { private boolean runInThread; private char[] password; private HeaderWriter headerWriter = new HeaderWriter(); + private Charset charset = StandardCharsets.UTF_8; /** * Creates a new ZipFile instance with the zip file at the location specified in zipFile. @@ -144,7 +150,7 @@ public void createSplitZipFile(List filesToAdd, ZipParameters parameters, zipModel.setSplitLength(splitLength); new AddFilesToZipTask(progressMonitor, runInThread, zipModel, password, headerWriter).execute( - new AddFilesToZipTaskParameters(filesToAdd, parameters)); + new AddFilesToZipTaskParameters(filesToAdd, parameters, charset)); } /** @@ -285,7 +291,7 @@ public void addFiles(List filesToAdd, ZipParameters parameters) throws Zip } new AddFilesToZipTask(progressMonitor, runInThread, zipModel, password, headerWriter).execute( - new AddFilesToZipTaskParameters(filesToAdd, parameters)); + new AddFilesToZipTaskParameters(filesToAdd, parameters, charset)); } /** @@ -357,7 +363,7 @@ private void addFolder(File folderToAdd, ZipParameters zipParameters, boolean ch } new AddFolderToZipTask(progressMonitor, runInThread, zipModel, password, headerWriter).execute( - new AddFolderToZipTaskParameters(folderToAdd, zipParameters)); + new AddFolderToZipTaskParameters(folderToAdd, zipParameters, charset)); } /** @@ -394,7 +400,7 @@ public void addStream(InputStream inputStream, ZipParameters parameters) throws } new AddStreamToZipTask(progressMonitor, runInThread, zipModel, password, headerWriter).execute( - new AddStreamToZipTaskParameters(inputStream, parameters)); + new AddStreamToZipTaskParameters(inputStream, parameters, charset)); } /** @@ -429,7 +435,7 @@ public void extractAll(String destinationPath) throws ZipException { } new ExtractAllFilesTask(progressMonitor, runInThread, zipModel, password).execute( - new ExtractAllFilesTaskParameters(destinationPath)); + new ExtractAllFilesTaskParameters(destinationPath, charset)); } /** @@ -473,7 +479,7 @@ public void extractFile(FileHeader fileHeader, String destinationPath, String ne readZipInfo(); new ExtractFileTask(progressMonitor, runInThread, zipModel, password).execute( - new ExtractFileTaskParameters(destinationPath, fileHeader, newFileName)); + new ExtractFileTaskParameters(destinationPath, fileHeader, newFileName, charset)); } /** @@ -670,7 +676,8 @@ public void removeFile(FileHeader fileHeader) throws ZipException { throw new ZipException("Zip file format does not allow updating split/spanned files"); } - new RemoveEntryFromZipFileTask(progressMonitor, runInThread, zipModel).execute(fileHeader); + new RemoveEntryFromZipFileTask(progressMonitor, runInThread, zipModel).execute( + new RemoveEntryFromZipFileTaskParameters(fileHeader, charset)); } /** @@ -695,7 +702,8 @@ public void mergeSplitFiles(File outputZipFile) throws ZipException { throw new ZipException("zip model is null, corrupt zip file?"); } - new MergeSplitZipFileTask(progressMonitor, runInThread, zipModel).execute(outputZipFile); + new MergeSplitZipFileTask(progressMonitor, runInThread, zipModel).execute( + new MergeSplitZipFileTaskParameters(outputZipFile, charset)); } /** @@ -723,7 +731,8 @@ public void setComment(String comment) throws ZipException { throw new ZipException("end of central directory is null, cannot set comment"); } - new SetCommentTask(progressMonitor, runInThread, zipModel).execute(comment); + new SetCommentTask(progressMonitor, runInThread, zipModel).execute( + new SetCommentTaskTaskParameters(comment, charset)); } /** @@ -835,7 +844,7 @@ private void readZipInfo() throws ZipException { try (RandomAccessFile randomAccessFile = new RandomAccessFile(zipFile, RandomAccessFileMode.READ.getValue())) { HeaderReader headerReader = new HeaderReader(); - zipModel = headerReader.readAllHeaders(randomAccessFile); + zipModel = headerReader.readAllHeaders(randomAccessFile, charset); zipModel.setZipFile(zipFile); } catch (IOException e) { throw new ZipException(e); @@ -879,6 +888,17 @@ public File getFile() { return zipFile; } + public Charset getCharset() { + return charset; + } + + public void setCharset(Charset charset) throws IllegalArgumentException { + if(charset == null) { + throw new IllegalArgumentException("charset cannot be null"); + } + this.charset = charset; + } + @Override public String toString() { return zipFile.toString(); diff --git a/src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java b/src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java index a5c5dc5a..d4b17540 100644 --- a/src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java +++ b/src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java @@ -11,6 +11,7 @@ import net.lingala.zip4j.model.enums.EncryptionMethod; import net.lingala.zip4j.util.Zip4jUtil; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import static net.lingala.zip4j.util.BitUtils.setBit; @@ -19,7 +20,7 @@ public class FileHeaderFactory { - public FileHeader generateFileHeader(ZipParameters zipParameters, boolean isSplitZip, int currentDiskNumberStart) + public FileHeader generateFileHeader(ZipParameters zipParameters, boolean isSplitZip, int currentDiskNumberStart, Charset charset) throws ZipException { FileHeader fileHeader = new FileHeader(); @@ -63,7 +64,7 @@ public FileHeader generateFileHeader(ZipParameters zipParameters, boolean isSpli fileHeader.setCrc(zipParameters.getEntryCRC()); } - fileHeader.setGeneralPurposeFlag(determineGeneralPurposeBitFlag(fileHeader.isEncrypted(), zipParameters)); + fileHeader.setGeneralPurposeFlag(determineGeneralPurposeBitFlag(fileHeader.isEncrypted(), zipParameters, charset)); fileHeader.setDataDescriptorExists(zipParameters.isWriteExtendedLocalFileHeader()); return fileHeader; } @@ -87,10 +88,12 @@ public LocalFileHeader generateLocalFileHeader(FileHeader fileHeader) { return localFileHeader; } - private byte[] determineGeneralPurposeBitFlag(boolean isEncrypted, ZipParameters zipParameters) { + private byte[] determineGeneralPurposeBitFlag(boolean isEncrypted, ZipParameters zipParameters, Charset charset) { byte[] generalPurposeBitFlag = new byte[2]; generalPurposeBitFlag[0] = generateFirstGeneralPurposeByte(isEncrypted, zipParameters); - generalPurposeBitFlag[1] = setBit(generalPurposeBitFlag[1], 3); // set 3rd bit which corresponds to utf-8 file name charset + if(charset.equals(StandardCharsets.UTF_8)) { + generalPurposeBitFlag[1] = setBit(generalPurposeBitFlag[1], 3); // set 3rd bit which corresponds to utf-8 file name charset + } return generalPurposeBitFlag; } diff --git a/src/main/java/net/lingala/zip4j/headers/HeaderReader.java b/src/main/java/net/lingala/zip4j/headers/HeaderReader.java index 421c5cd3..af011abf 100755 --- a/src/main/java/net/lingala/zip4j/headers/HeaderReader.java +++ b/src/main/java/net/lingala/zip4j/headers/HeaderReader.java @@ -39,6 +39,7 @@ import java.io.InputStream; import java.io.RandomAccessFile; import java.math.BigInteger; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; @@ -57,7 +58,7 @@ public class HeaderReader { private RawIO rawIO = new RawIO(); private byte[] intBuff = new byte[4]; - public ZipModel readAllHeaders(RandomAccessFile zip4jRaf) throws IOException { + public ZipModel readAllHeaders(RandomAccessFile zip4jRaf, Charset charset) throws IOException { zipModel = new ZipModel(); try { @@ -79,7 +80,7 @@ public ZipModel readAllHeaders(RandomAccessFile zip4jRaf) throws IOException { } } - zipModel.setCentralDirectory(readCentralDirectory(zip4jRaf, rawIO)); + zipModel.setCentralDirectory(readCentralDirectory(zip4jRaf, rawIO, charset)); return zipModel; } @@ -127,7 +128,7 @@ private EndOfCentralDirectoryRecord readEndOfCentralDirectoryRecord(RandomAccess return endOfCentralDirectoryRecord; } - private CentralDirectory readCentralDirectory(RandomAccessFile zip4jRaf, RawIO rawIO) throws IOException { + private CentralDirectory readCentralDirectory(RandomAccessFile zip4jRaf, RawIO rawIO, Charset charset) throws IOException { CentralDirectory centralDirectory = new CentralDirectory(); List fileHeaders = new ArrayList<>(); @@ -195,7 +196,7 @@ private CentralDirectory readCentralDirectory(RandomAccessFile zip4jRaf, RawIO r if (fileNameLength > 0) { byte[] fileNameBuff = new byte[fileNameLength]; zip4jRaf.readFully(fileNameBuff); - String fileName = decodeStringWithCharset(fileNameBuff, fileHeader.isFileNameUTF8Encoded()); + String fileName = decodeStringWithCharset(fileNameBuff, fileHeader.isFileNameUTF8Encoded(), charset); if (fileName.contains(":\\")) { fileName = fileName.substring(fileName.indexOf(":\\") + 2); @@ -214,7 +215,7 @@ private CentralDirectory readCentralDirectory(RandomAccessFile zip4jRaf, RawIO r if (fileCommentLength > 0) { byte[] fileCommentBuff = new byte[fileCommentLength]; zip4jRaf.readFully(fileCommentBuff); - fileHeader.setFileComment(decodeStringWithCharset(fileCommentBuff, fileHeader.isFileNameUTF8Encoded())); + fileHeader.setFileComment(decodeStringWithCharset(fileCommentBuff, fileHeader.isFileNameUTF8Encoded(), charset)); } if (fileHeader.isEncrypted()) { @@ -522,7 +523,7 @@ private void setFilePointerToReadZip64EndCentralDirLoc(RandomAccessFile zip4jRaf zip4jRaf.seek(zip4jRaf.getFilePointer() - 4 - 4 - 8 - 4 - 4); } - public LocalFileHeader readLocalFileHeader(InputStream inputStream) throws IOException { + public LocalFileHeader readLocalFileHeader(InputStream inputStream, Charset charset) throws IOException { LocalFileHeader localFileHeader = new LocalFileHeader(); byte[] intBuff = new byte[4]; @@ -565,7 +566,7 @@ public LocalFileHeader readLocalFileHeader(InputStream inputStream) throws IOExc // Modified after user reported an issue http://www.lingala.net/zip4j/forum/index.php?topic=2.0 // String fileName = new String(fileNameBuf, "Cp850"); // String fileName = Zip4jUtil.getCp850EncodedString(fileNameBuf); - String fileName = decodeStringWithCharset(fileNameBuf, localFileHeader.isFileNameUTF8Encoded()); + String fileName = decodeStringWithCharset(fileNameBuf, localFileHeader.isFileNameUTF8Encoded(), charset); if (fileName == null) { throw new ZipException("file name is null, cannot assign file name to local file header"); diff --git a/src/main/java/net/lingala/zip4j/headers/HeaderUtil.java b/src/main/java/net/lingala/zip4j/headers/HeaderUtil.java index 8fac484b..15fd8e5e 100644 --- a/src/main/java/net/lingala/zip4j/headers/HeaderUtil.java +++ b/src/main/java/net/lingala/zip4j/headers/HeaderUtil.java @@ -5,6 +5,7 @@ import net.lingala.zip4j.model.ZipModel; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; @@ -62,7 +63,11 @@ public static int getIndexOfFileHeader(ZipModel zipModel, FileHeader fileHeader) return -1; } - public static String decodeStringWithCharset(byte[] data, boolean isUtf8Encoded) { + public static String decodeStringWithCharset(byte[] data, boolean isUtf8Encoded, Charset charset) { + if(charset != null) { + return new String(data, charset); + } + if (isUtf8Encoded) { return new String(data, StandardCharsets.UTF_8); } diff --git a/src/main/java/net/lingala/zip4j/headers/HeaderWriter.java b/src/main/java/net/lingala/zip4j/headers/HeaderWriter.java index 56dff670..063fe8bb 100755 --- a/src/main/java/net/lingala/zip4j/headers/HeaderWriter.java +++ b/src/main/java/net/lingala/zip4j/headers/HeaderWriter.java @@ -25,7 +25,6 @@ import net.lingala.zip4j.model.Zip64EndOfCentralDirectoryLocator; import net.lingala.zip4j.model.Zip64EndOfCentralDirectoryRecord; import net.lingala.zip4j.model.ZipModel; -import net.lingala.zip4j.util.BitUtils; import net.lingala.zip4j.util.InternalZipConstants; import net.lingala.zip4j.util.RawIO; @@ -48,8 +47,8 @@ public class HeaderWriter { private RawIO rawIO = new RawIO(); private byte[] longBuff = new byte[8]; - public void writeLocalFileHeader(ZipModel zipModel, LocalFileHeader localFileHeader, OutputStream outputStream) - throws IOException { + public void writeLocalFileHeader(ZipModel zipModel, LocalFileHeader localFileHeader, OutputStream outputStream, + Charset charset) throws IOException { try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { rawIO.writeIntLittleEndian(byteArrayOutputStream, (int) localFileHeader.getSignature().getValue()); @@ -88,10 +87,6 @@ public void writeLocalFileHeader(ZipModel zipModel, LocalFileHeader localFileHea byte[] fileNameBytes = new byte[0]; if (isStringNotNullAndNotEmpty(localFileHeader.getFileName())) { - Charset charset = Charset.forName(InternalZipConstants.ZIP_STANDARD_CHARSET); - if (BitUtils.isBitSet(localFileHeader.getGeneralPurposeFlag()[1], 3)) { - charset = StandardCharsets.UTF_8; - } fileNameBytes = localFileHeader.getFileName().getBytes(charset); } rawIO.writeShortLittleEndian(byteArrayOutputStream, fileNameBytes.length); @@ -169,7 +164,7 @@ public void writeExtendedLocalHeader(LocalFileHeader localFileHeader, OutputStre } } - public void finalizeZipFile(ZipModel zipModel, OutputStream outputStream) throws IOException { + public void finalizeZipFile(ZipModel zipModel, OutputStream outputStream, Charset charset) throws IOException { if (zipModel == null || outputStream == null) { throw new ZipException("input parameters is null, cannot finalize zip file"); } @@ -177,7 +172,7 @@ public void finalizeZipFile(ZipModel zipModel, OutputStream outputStream) throws try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { processHeaderData(zipModel, outputStream); long offsetCentralDir = zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory(); - writeCentralDirectory(zipModel, byteArrayOutputStream, rawIO); + writeCentralDirectory(zipModel, byteArrayOutputStream, rawIO, charset); int sizeOfCentralDir = byteArrayOutputStream.size(); if (zipModel.isZip64Format() || offsetCentralDir >= InternalZipConstants.ZIP_64_SIZE_LIMIT @@ -209,11 +204,11 @@ public void finalizeZipFile(ZipModel zipModel, OutputStream outputStream) throws } writeEndOfCentralDirectoryRecord(zipModel, sizeOfCentralDir, offsetCentralDir, byteArrayOutputStream, rawIO); - writeZipHeaderBytes(zipModel, outputStream, byteArrayOutputStream.toByteArray()); + writeZipHeaderBytes(zipModel, outputStream, byteArrayOutputStream.toByteArray(), charset); } } - public void finalizeZipFileWithoutValidations(ZipModel zipModel, OutputStream outputStream) throws IOException { + public void finalizeZipFileWithoutValidations(ZipModel zipModel, OutputStream outputStream, Charset charset) throws IOException { if (zipModel == null || outputStream == null) { throw new ZipException("input parameters is null, cannot finalize zip file without validations"); @@ -221,7 +216,7 @@ public void finalizeZipFileWithoutValidations(ZipModel zipModel, OutputStream ou try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { long offsetCentralDir = zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory(); - writeCentralDirectory(zipModel, byteArrayOutputStream, rawIO); + writeCentralDirectory(zipModel, byteArrayOutputStream, rawIO, charset); int sizeOfCentralDir = byteArrayOutputStream.size(); if (zipModel.isZip64Format() || offsetCentralDir >= InternalZipConstants.ZIP_64_SIZE_LIMIT @@ -243,7 +238,7 @@ public void finalizeZipFileWithoutValidations(ZipModel zipModel, OutputStream ou } writeEndOfCentralDirectoryRecord(zipModel, sizeOfCentralDir, offsetCentralDir, byteArrayOutputStream, rawIO); - writeZipHeaderBytes(zipModel, outputStream, byteArrayOutputStream.toByteArray()); + writeZipHeaderBytes(zipModel, outputStream, byteArrayOutputStream.toByteArray(), charset); } } @@ -334,7 +329,7 @@ private int getCurrentSplitFileCounter(OutputStream outputStream) { return ((CountingOutputStream) outputStream).getCurrentSplitFileCounter(); } - private void writeZipHeaderBytes(ZipModel zipModel, OutputStream outputStream, byte[] buff) throws IOException { + private void writeZipHeaderBytes(ZipModel zipModel, OutputStream outputStream, byte[] buff, Charset charset) throws IOException { if (buff == null) { throw new ZipException("invalid buff to write as zip headers"); } @@ -342,7 +337,7 @@ private void writeZipHeaderBytes(ZipModel zipModel, OutputStream outputStream, b if (outputStream instanceof CountingOutputStream) { if (((CountingOutputStream) outputStream).checkBuffSizeAndStartNextSplitFile(buff.length)) { //TODO check if this is correct - finalizeZipFile(zipModel, outputStream); + finalizeZipFile(zipModel, outputStream, charset); return; } } @@ -374,7 +369,7 @@ private void processHeaderData(ZipModel zipModel, OutputStream outputStream) thr zipModel.getEndOfCentralDirectoryRecord().setNumberOfThisDiskStartOfCentralDir(currentSplitFileCounter); } - private void writeCentralDirectory(ZipModel zipModel, ByteArrayOutputStream byteArrayOutputStream, RawIO rawIO) + private void writeCentralDirectory(ZipModel zipModel, ByteArrayOutputStream byteArrayOutputStream, RawIO rawIO, Charset charset) throws ZipException { if (zipModel.getCentralDirectory() == null || zipModel.getCentralDirectory().getFileHeaders() == null @@ -383,12 +378,12 @@ private void writeCentralDirectory(ZipModel zipModel, ByteArrayOutputStream byte } for (FileHeader fileHeader: zipModel.getCentralDirectory().getFileHeaders()) { - writeFileHeader(zipModel, fileHeader, byteArrayOutputStream, rawIO); + writeFileHeader(zipModel, fileHeader, byteArrayOutputStream, rawIO, charset); } } private void writeFileHeader(ZipModel zipModel, FileHeader fileHeader, ByteArrayOutputStream byteArrayOutputStream, - RawIO rawIO) throws ZipException { + RawIO rawIO, Charset charset) throws ZipException { if (fileHeader == null) { throw new ZipException("input parameters is null, cannot write local file header"); } @@ -427,10 +422,6 @@ private void writeFileHeader(ZipModel zipModel, FileHeader fileHeader, ByteArray byte[] fileNameBytes = new byte[0]; if (isStringNotNullAndNotEmpty(fileHeader.getFileName())) { - Charset charset = Charset.forName(InternalZipConstants.ZIP_STANDARD_CHARSET); - if (BitUtils.isBitSet(fileHeader.getGeneralPurposeFlag()[1], 3)) { - charset = StandardCharsets.UTF_8; - } fileNameBytes = fileHeader.getFileName().getBytes(charset); } rawIO.writeShortLittleEndian(byteArrayOutputStream, fileNameBytes.length); @@ -458,10 +449,6 @@ private void writeFileHeader(ZipModel zipModel, FileHeader fileHeader, ByteArray String fileComment = fileHeader.getFileComment(); byte[] fileCommentBytes = new byte[0]; if (isStringNotNullAndNotEmpty(fileComment)) { - Charset charset = Charset.forName(InternalZipConstants.ZIP_STANDARD_CHARSET); - if (BitUtils.isBitSet(fileHeader.getGeneralPurposeFlag()[1], 3)) { - charset = StandardCharsets.UTF_8; - } fileCommentBytes = fileComment.getBytes(charset); } rawIO.writeShortLittleEndian(byteArrayOutputStream, fileCommentBytes.length); diff --git a/src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java b/src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java index e2c85992..3f2920bd 100755 --- a/src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java +++ b/src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java @@ -32,6 +32,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.zip.CRC32; import java.util.zip.DataFormatException; @@ -50,14 +52,28 @@ public class ZipInputStream extends InputStream { private byte[] endOfEntryBuffer; private boolean extraDataRecordReadForThisEntry = false; private boolean canSkipExtendedLocalFileHeader = false; + private Charset charset; public ZipInputStream(InputStream inputStream) { - this(inputStream, null); + this(inputStream, null, StandardCharsets.UTF_8); + } + + public ZipInputStream(InputStream inputStream, Charset charset) { + this(inputStream, null, charset); } public ZipInputStream(InputStream inputStream, char[] password) { + this(inputStream, password, StandardCharsets.UTF_8); + } + + public ZipInputStream(InputStream inputStream, char[] password, Charset charset) { + if(charset == null) { + charset = StandardCharsets.UTF_8; + } + this.inputStream = new PushbackInputStream(inputStream, 512); this.password = password; + this.charset = charset; } public LocalFileHeader getNextEntry() throws IOException { @@ -69,7 +85,7 @@ public LocalFileHeader getNextEntry(FileHeader fileHeader) throws IOException { readUntilEndOfEntry(); } - localFileHeader = headerReader.readLocalFileHeader(inputStream); + localFileHeader = headerReader.readLocalFileHeader(inputStream, charset); if (localFileHeader == null) { return null; @@ -313,5 +329,4 @@ private void readUntilEndOfEntry() throws IOException { private boolean isEncryptionMethodZipStandard(LocalFileHeader localFileHeader) { return localFileHeader.isEncrypted() && EncryptionMethod.ZIP_STANDARD.equals(localFileHeader.getEncryptionMethod()); } - } diff --git a/src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java b/src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java index 991b1944..67774ec8 100755 --- a/src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java +++ b/src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java @@ -15,6 +15,8 @@ import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.zip.CRC32; public class ZipOutputStream extends OutputStream { @@ -30,18 +32,32 @@ public class ZipOutputStream extends OutputStream { private CRC32 crc32 = new CRC32(); private RawIO rawIO = new RawIO(); private long uncompressedSizeForThisEntry = 0; + private Charset charset; public ZipOutputStream(OutputStream outputStream) throws IOException { - this(outputStream, null); + this(outputStream, null, StandardCharsets.UTF_8); + } + + public ZipOutputStream(OutputStream outputStream, Charset charset) throws IOException { + this(outputStream, null, charset); } public ZipOutputStream(OutputStream outputStream, char[] password) throws IOException { - this(outputStream, password, new ZipModel()); + this(outputStream, password, StandardCharsets.UTF_8); + } + + public ZipOutputStream(OutputStream outputStream, char[] password, Charset charset) throws IOException { + this(outputStream, password, charset, new ZipModel()); } - public ZipOutputStream(OutputStream outputStream, char[] password, ZipModel zipModel) throws IOException { + public ZipOutputStream(OutputStream outputStream, char[] password, Charset charset, ZipModel zipModel) throws IOException { + if(charset == null) { + charset = StandardCharsets.UTF_8; + } + this.countingOutputStream = new CountingOutputStream(outputStream); this.password = password; + this.charset = charset; this.zipModel = initializeZipModel(zipModel, countingOutputStream); writeSplitZipHeaderIfApplicable(); } @@ -98,7 +114,7 @@ public FileHeader closeEntry() throws IOException { @Override public void close() throws IOException { zipModel.getEndOfCentralDirectoryRecord().setOffsetOfStartOfCentralDirectory(countingOutputStream.getNumberOfBytesWritten()); - headerWriter.finalizeZipFile(zipModel, countingOutputStream); + headerWriter.finalizeZipFile(zipModel, countingOutputStream, charset); countingOutputStream.close(); } @@ -117,11 +133,11 @@ private ZipModel initializeZipModel(ZipModel zipModel, CountingOutputStream coun private void initializeAndWriteFileHeader(ZipParameters zipParameters) throws IOException { fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, countingOutputStream.isSplitZipFile(), - countingOutputStream.getCurrentSplitFileCounter()); + countingOutputStream.getCurrentSplitFileCounter(), charset); fileHeader.setOffsetLocalHeader(countingOutputStream.getOffsetForNextEntry()); localFileHeader = fileHeaderFactory.generateLocalFileHeader(fileHeader); - headerWriter.writeLocalFileHeader(zipModel, localFileHeader, countingOutputStream); + headerWriter.writeLocalFileHeader(zipModel, localFileHeader, countingOutputStream, charset); } private void reset() throws IOException { diff --git a/src/main/java/net/lingala/zip4j/tasks/AbstractAddFileToZipTask.java b/src/main/java/net/lingala/zip4j/tasks/AbstractAddFileToZipTask.java index 469647b9..b7f3d903 100644 --- a/src/main/java/net/lingala/zip4j/tasks/AbstractAddFileToZipTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/AbstractAddFileToZipTask.java @@ -12,11 +12,13 @@ import net.lingala.zip4j.progress.ProgressMonitor; import net.lingala.zip4j.util.FileUtils; import net.lingala.zip4j.util.Zip4jUtil; +import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.RemoveEntryFromZipFileTaskParameters; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -47,13 +49,13 @@ public abstract class AbstractAddFileToZipTask extends AsyncZipTask { this.headerWriter = headerWriter; } - void addFilesToZip(List filesToAdd, ProgressMonitor progressMonitor, ZipParameters zipParameters) + void addFilesToZip(List filesToAdd, ProgressMonitor progressMonitor, ZipParameters zipParameters, Charset charset) throws IOException { - List updatedFilesToAdd = removeFilesIfExists(filesToAdd, zipParameters, progressMonitor); + List updatedFilesToAdd = removeFilesIfExists(filesToAdd, zipParameters, progressMonitor, charset); try (SplitOutputStream splitOutputStream = new SplitOutputStream(zipModel.getZipFile(), zipModel.getSplitLength()); - ZipOutputStream zipOutputStream = initializeOutputStream(splitOutputStream)) { + ZipOutputStream zipOutputStream = initializeOutputStream(splitOutputStream, charset)) { byte[] readBuff = new byte[BUFF_SIZE]; int readLen = -1; @@ -110,7 +112,7 @@ long calculateWorkForFiles(List filesToAdd, ZipParameters zipParameters) t return totalWork; } - ZipOutputStream initializeOutputStream(SplitOutputStream splitOutputStream) throws IOException { + ZipOutputStream initializeOutputStream(SplitOutputStream splitOutputStream, Charset charset) throws IOException { if (zipModel.getZipFile().exists()) { if (zipModel.getEndOfCentralDirectoryRecord() == null) { throw new ZipException("invalid end of central directory record"); @@ -118,7 +120,7 @@ ZipOutputStream initializeOutputStream(SplitOutputStream splitOutputStream) thro splitOutputStream.seek(zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory()); } - return new ZipOutputStream(splitOutputStream, password, zipModel); + return new ZipOutputStream(splitOutputStream, password, charset, zipModel); } void verifyZipParameters(ZipParameters parameters) throws ZipException { @@ -185,7 +187,7 @@ private ZipParameters cloneAndAdjustZipParameters(ZipParameters zipParameters, F return clonedZipParameters; } - private List removeFilesIfExists(List files, ZipParameters zipParameters, ProgressMonitor progressMonitor) + private List removeFilesIfExists(List files, ZipParameters zipParameters, ProgressMonitor progressMonitor, Charset charset) throws ZipException { List filesToAdd = new ArrayList<>(files); @@ -200,7 +202,7 @@ private List removeFilesIfExists(List files, ZipParameters zipParame if (fileHeader != null) { if (zipParameters.isOverrideExistingFilesInZip()) { progressMonitor.setCurrentTask(REMOVE_ENTRY); - removeFile(fileHeader, progressMonitor); + removeFile(fileHeader, progressMonitor, charset); verifyIfTaskIsCancelled(); progressMonitor.setCurrentTask(ADD_ENTRY); } else { @@ -212,10 +214,10 @@ private List removeFilesIfExists(List files, ZipParameters zipParame return filesToAdd; } - private void removeFile(FileHeader fileHeader, ProgressMonitor progressMonitor) throws ZipException { + private void removeFile(FileHeader fileHeader, ProgressMonitor progressMonitor, Charset charset) throws ZipException { RemoveEntryFromZipFileTask removeEntryFromZipFileTask = new RemoveEntryFromZipFileTask(progressMonitor, false, zipModel); - removeEntryFromZipFileTask.execute(fileHeader); + removeEntryFromZipFileTask.execute(new RemoveEntryFromZipFileTaskParameters(fileHeader, charset)); } @Override diff --git a/src/main/java/net/lingala/zip4j/tasks/AbstractZipTaskParameters.java b/src/main/java/net/lingala/zip4j/tasks/AbstractZipTaskParameters.java new file mode 100644 index 00000000..cbdde97c --- /dev/null +++ b/src/main/java/net/lingala/zip4j/tasks/AbstractZipTaskParameters.java @@ -0,0 +1,12 @@ +package net.lingala.zip4j.tasks; + +import java.nio.charset.Charset; + +public abstract class AbstractZipTaskParameters { + + protected Charset charset; + + protected AbstractZipTaskParameters(Charset charset) { + this.charset = charset; + } +} diff --git a/src/main/java/net/lingala/zip4j/tasks/AddFilesToZipTask.java b/src/main/java/net/lingala/zip4j/tasks/AddFilesToZipTask.java index 5a984de8..40be2f42 100644 --- a/src/main/java/net/lingala/zip4j/tasks/AddFilesToZipTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/AddFilesToZipTask.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; import java.util.List; public class AddFilesToZipTask extends AbstractAddFileToZipTask { @@ -23,7 +24,7 @@ protected void executeTask(AddFilesToZipTaskParameters taskParameters, ProgressM throws IOException { verifyZipParameters(taskParameters.zipParameters); - addFilesToZip(taskParameters.filesToAdd, progressMonitor, taskParameters.zipParameters); + addFilesToZip(taskParameters.filesToAdd, progressMonitor, taskParameters.zipParameters, taskParameters.charset); } @Override @@ -36,11 +37,12 @@ protected ProgressMonitor.Task getTask() { return super.getTask(); } - public static class AddFilesToZipTaskParameters { + public static class AddFilesToZipTaskParameters extends AbstractZipTaskParameters { private List filesToAdd; private ZipParameters zipParameters; - public AddFilesToZipTaskParameters(List filesToAdd, ZipParameters zipParameters) { + public AddFilesToZipTaskParameters(List filesToAdd, ZipParameters zipParameters, Charset charset) { + super(charset); this.filesToAdd = filesToAdd; this.zipParameters = zipParameters; } diff --git a/src/main/java/net/lingala/zip4j/tasks/AddFolderToZipTask.java b/src/main/java/net/lingala/zip4j/tasks/AddFolderToZipTask.java index db8dce0a..c8e0878e 100644 --- a/src/main/java/net/lingala/zip4j/tasks/AddFolderToZipTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/AddFolderToZipTask.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; import java.util.List; import static net.lingala.zip4j.util.FileUtils.getFilesInDirectoryRecursive; @@ -25,7 +26,7 @@ protected void executeTask(AddFolderToZipTaskParameters taskParameters, Progress throws IOException { List filesToAdd = getFilesToAdd(taskParameters); setDefaultFolderPath(taskParameters); - addFilesToZip(filesToAdd, progressMonitor, taskParameters.zipParameters); + addFilesToZip(filesToAdd, progressMonitor, taskParameters.zipParameters, taskParameters.charset); } @Override @@ -65,11 +66,12 @@ private List getFilesToAdd(AddFolderToZipTaskParameters taskParameters) th return filesToAdd; } - public static class AddFolderToZipTaskParameters { + public static class AddFolderToZipTaskParameters extends AbstractZipTaskParameters { private File folderToAdd; private ZipParameters zipParameters; - public AddFolderToZipTaskParameters(File folderToAdd, ZipParameters zipParameters) { + public AddFolderToZipTaskParameters(File folderToAdd, ZipParameters zipParameters, Charset charset) { + super(charset); this.folderToAdd = folderToAdd; this.zipParameters = zipParameters; } diff --git a/src/main/java/net/lingala/zip4j/tasks/AddStreamToZipTask.java b/src/main/java/net/lingala/zip4j/tasks/AddStreamToZipTask.java index f53a99ff..94bb6717 100644 --- a/src/main/java/net/lingala/zip4j/tasks/AddStreamToZipTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/AddStreamToZipTask.java @@ -11,10 +11,12 @@ import net.lingala.zip4j.model.enums.CompressionMethod; import net.lingala.zip4j.progress.ProgressMonitor; import net.lingala.zip4j.tasks.AddStreamToZipTask.AddStreamToZipTaskParameters; +import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.RemoveEntryFromZipFileTaskParameters; import net.lingala.zip4j.util.Zip4jUtil; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import static net.lingala.zip4j.util.InternalZipConstants.BUFF_SIZE; @@ -35,7 +37,7 @@ protected void executeTask(AddStreamToZipTaskParameters taskParameters, Progress throw new ZipException("fileNameInZip has to be set in zipParameters when adding stream"); } - removeFileIfExists(getZipModel(), taskParameters.zipParameters.getFileNameInZip(), progressMonitor); + removeFileIfExists(getZipModel(), taskParameters.charset, taskParameters.zipParameters.getFileNameInZip(), progressMonitor); // For streams, it is necessary to write extended local file header because of Zip standard encryption. // If we do not write extended local file header, zip standard encryption needs a crc upfront for key, @@ -49,7 +51,7 @@ protected void executeTask(AddStreamToZipTaskParameters taskParameters, Progress } try(SplitOutputStream splitOutputStream = new SplitOutputStream(getZipModel().getZipFile(), getZipModel().getSplitLength()); - ZipOutputStream zipOutputStream = initializeOutputStream(splitOutputStream)) { + ZipOutputStream zipOutputStream = initializeOutputStream(splitOutputStream, taskParameters.charset)) { byte[] readBuff = new byte[BUFF_SIZE]; int readLen = -1; @@ -77,22 +79,23 @@ protected long calculateTotalWork(AddStreamToZipTaskParameters taskParameters) { return 0; } - private void removeFileIfExists(ZipModel zipModel, String fileNameInZip, ProgressMonitor progressMonitor) + private void removeFileIfExists(ZipModel zipModel, Charset charset, String fileNameInZip, ProgressMonitor progressMonitor) throws ZipException { FileHeader fileHeader = HeaderUtil.getFileHeader(zipModel, fileNameInZip); if (fileHeader != null) { RemoveEntryFromZipFileTask removeEntryFromZipFileTask = new RemoveEntryFromZipFileTask(progressMonitor, false, zipModel); - removeEntryFromZipFileTask.execute(fileHeader); + removeEntryFromZipFileTask.execute(new RemoveEntryFromZipFileTaskParameters(fileHeader, charset)); } } - public static class AddStreamToZipTaskParameters { + public static class AddStreamToZipTaskParameters extends AbstractZipTaskParameters { private InputStream inputStream; private ZipParameters zipParameters; - public AddStreamToZipTaskParameters(InputStream inputStream, ZipParameters zipParameters) { + public AddStreamToZipTaskParameters(InputStream inputStream, ZipParameters zipParameters, Charset charset) { + super(charset); this.inputStream = inputStream; this.zipParameters = zipParameters; } diff --git a/src/main/java/net/lingala/zip4j/tasks/ExtractAllFilesTask.java b/src/main/java/net/lingala/zip4j/tasks/ExtractAllFilesTask.java index a6869b32..15b226ca 100644 --- a/src/main/java/net/lingala/zip4j/tasks/ExtractAllFilesTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/ExtractAllFilesTask.java @@ -8,6 +8,7 @@ import net.lingala.zip4j.tasks.ExtractAllFilesTask.ExtractAllFilesTaskParameters; import java.io.IOException; +import java.nio.charset.Charset; public class ExtractAllFilesTask extends AbstractExtractFileTask { @@ -22,7 +23,7 @@ public ExtractAllFilesTask(ProgressMonitor progressMonitor, boolean runInThread, @Override protected void executeTask(ExtractAllFilesTaskParameters taskParameters, ProgressMonitor progressMonitor) throws IOException { - try (ZipInputStream zipInputStream = prepareZipInputStream()) { + try (ZipInputStream zipInputStream = prepareZipInputStream(taskParameters.charset)) { for (FileHeader fileHeader : getZipModel().getCentralDirectory().getFileHeaders()) { if (fileHeader.getFileName().startsWith("__MACOSX")) { progressMonitor.updateWorkCompleted(fileHeader.getUncompressedSize()); @@ -58,7 +59,7 @@ protected long calculateTotalWork(ExtractAllFilesTaskParameters taskParameters) return totalWork; } - private ZipInputStream prepareZipInputStream() throws IOException { + private ZipInputStream prepareZipInputStream(Charset charset) throws IOException { splitInputStream = new SplitInputStream(getZipModel().getZipFile(), getZipModel().isSplitArchive(), getZipModel().getEndOfCentralDirectoryRecord().getNumberOfThisDisk()); @@ -67,7 +68,7 @@ private ZipInputStream prepareZipInputStream() throws IOException { splitInputStream.prepareExtractionForFileHeader(fileHeader); } - return new ZipInputStream(splitInputStream, password); + return new ZipInputStream(splitInputStream, password, charset); } private FileHeader getFirstFileHeader(ZipModel zipModel) { @@ -80,10 +81,11 @@ private FileHeader getFirstFileHeader(ZipModel zipModel) { return zipModel.getCentralDirectory().getFileHeaders().get(0); } - public static class ExtractAllFilesTaskParameters { + public static class ExtractAllFilesTaskParameters extends AbstractZipTaskParameters { private String outputPath; - public ExtractAllFilesTaskParameters(String outputPath) { + public ExtractAllFilesTaskParameters(String outputPath, Charset charset) { + super(charset); this.outputPath = outputPath; } } diff --git a/src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java b/src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java index 7ba3c4ec..2fed6e76 100644 --- a/src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java @@ -8,6 +8,7 @@ import net.lingala.zip4j.tasks.ExtractFileTask.ExtractFileTaskParameters; import java.io.IOException; +import java.nio.charset.Charset; public class ExtractFileTask extends AbstractExtractFileTask { @@ -22,7 +23,7 @@ public ExtractFileTask(ProgressMonitor progressMonitor, boolean runInThread, Zip @Override protected void executeTask(ExtractFileTaskParameters taskParameters, ProgressMonitor progressMonitor) throws IOException { - try(ZipInputStream zipInputStream = createZipInputStream(taskParameters.fileHeader)) { + try(ZipInputStream zipInputStream = createZipInputStream(taskParameters.fileHeader, taskParameters.charset)) { extractFile(zipInputStream, taskParameters.fileHeader, taskParameters.outputPath, taskParameters.newFileName, progressMonitor); } finally { @@ -37,19 +38,20 @@ protected long calculateTotalWork(ExtractFileTaskParameters taskParameters) { return taskParameters.fileHeader.getUncompressedSize(); } - protected ZipInputStream createZipInputStream(FileHeader fileHeader) throws IOException { + protected ZipInputStream createZipInputStream(FileHeader fileHeader, Charset charset) throws IOException { splitInputStream = new SplitInputStream(getZipModel().getZipFile(), getZipModel().isSplitArchive(), getZipModel().getEndOfCentralDirectoryRecord().getNumberOfThisDisk()); splitInputStream.prepareExtractionForFileHeader(fileHeader); - return new ZipInputStream(splitInputStream, password); + return new ZipInputStream(splitInputStream, password, charset); } - public static class ExtractFileTaskParameters { + public static class ExtractFileTaskParameters extends AbstractZipTaskParameters { private String outputPath; private FileHeader fileHeader; private String newFileName; - public ExtractFileTaskParameters(String outputPath, FileHeader fileHeader, String newFileName) { + public ExtractFileTaskParameters(String outputPath, FileHeader fileHeader, String newFileName, Charset charset) { + super(charset); this.outputPath = outputPath; this.fileHeader = fileHeader; this.newFileName = newFileName; diff --git a/src/main/java/net/lingala/zip4j/tasks/MergeSplitZipFileTask.java b/src/main/java/net/lingala/zip4j/tasks/MergeSplitZipFileTask.java index d0531389..fe7bcdb8 100644 --- a/src/main/java/net/lingala/zip4j/tasks/MergeSplitZipFileTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/MergeSplitZipFileTask.java @@ -10,6 +10,7 @@ import net.lingala.zip4j.model.ZipModel; import net.lingala.zip4j.model.enums.RandomAccessFileMode; import net.lingala.zip4j.progress.ProgressMonitor; +import net.lingala.zip4j.tasks.MergeSplitZipFileTask.MergeSplitZipFileTaskParameters; import net.lingala.zip4j.util.RawIO; import java.io.File; @@ -18,11 +19,12 @@ import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; +import java.nio.charset.Charset; import java.util.List; import static net.lingala.zip4j.util.FileUtils.copyFile; -public class MergeSplitZipFileTask extends AsyncZipTask { +public class MergeSplitZipFileTask extends AsyncZipTask { private ZipModel zipModel; private RawIO rawIO = new RawIO(); @@ -33,14 +35,14 @@ public MergeSplitZipFileTask(ProgressMonitor progressMonitor, boolean runInThrea } @Override - protected void executeTask(File outputZipFile, ProgressMonitor progressMonitor) throws IOException { + protected void executeTask(MergeSplitZipFileTaskParameters taskParameters, ProgressMonitor progressMonitor) throws IOException { if (!zipModel.isSplitArchive()) { ZipException e = new ZipException("archive not a split zip file"); progressMonitor.endProgressMonitor(e); throw e; } - try (OutputStream outputStream = new FileOutputStream(outputZipFile)) { + try (OutputStream outputStream = new FileOutputStream(taskParameters.outputZipFile)) { long totalBytesWritten = 0; int totalNumberOfSplitFiles = zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk(); if (totalNumberOfSplitFiles <= 0) { @@ -73,7 +75,7 @@ protected void executeTask(File outputZipFile, ProgressMonitor progressMonitor) verifyIfTaskIsCancelled(); } } - updateHeadersForMergeSplitFileAction(zipModel, totalBytesWritten, outputStream); + updateHeadersForMergeSplitFileAction(zipModel, totalBytesWritten, outputStream, taskParameters.charset); progressMonitor.endProgressMonitor(); } catch (CloneNotSupportedException e) { throw new ZipException(e); @@ -81,7 +83,7 @@ protected void executeTask(File outputZipFile, ProgressMonitor progressMonitor) } @Override - protected long calculateTotalWork(File outputZipFile) { + protected long calculateTotalWork(MergeSplitZipFileTaskParameters taskParameters) { if (!zipModel.isSplitArchive()) { return 0; } @@ -124,7 +126,7 @@ private RandomAccessFile createSplitZipFileStream(ZipModel zipModel, int partNum } private void updateHeadersForMergeSplitFileAction(ZipModel zipModel, long totalBytesWritten, - OutputStream outputStream) + OutputStream outputStream, Charset charset) throws IOException, CloneNotSupportedException { ZipModel newZipModel = (ZipModel) zipModel.clone(); @@ -133,7 +135,7 @@ private void updateHeadersForMergeSplitFileAction(ZipModel zipModel, long totalB updateSplitZipModel(newZipModel, totalBytesWritten); HeaderWriter headerWriter = new HeaderWriter(); - headerWriter.finalizeZipFileWithoutValidations(newZipModel, outputStream); + headerWriter.finalizeZipFileWithoutValidations(newZipModel, outputStream, charset); } private void updateSplitZipModel(ZipModel zipModel, long totalFileSize) { @@ -186,4 +188,13 @@ private void updateSplitZip64EndCentralDirRec(ZipModel zipModel, long totalFileS protected ProgressMonitor.Task getTask() { return ProgressMonitor.Task.MERGE_ZIP_FILES; } + + public static class MergeSplitZipFileTaskParameters extends AbstractZipTaskParameters { + private File outputZipFile; + + public MergeSplitZipFileTaskParameters(File outputZipFile, Charset charset) { + super(charset); + this.outputZipFile = outputZipFile; + } + } } diff --git a/src/main/java/net/lingala/zip4j/tasks/RemoveEntryFromZipFileTask.java b/src/main/java/net/lingala/zip4j/tasks/RemoveEntryFromZipFileTask.java index 942325ac..02d8a85d 100644 --- a/src/main/java/net/lingala/zip4j/tasks/RemoveEntryFromZipFileTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/RemoveEntryFromZipFileTask.java @@ -8,17 +8,19 @@ import net.lingala.zip4j.model.ZipModel; import net.lingala.zip4j.model.enums.RandomAccessFileMode; import net.lingala.zip4j.progress.ProgressMonitor; +import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.RemoveEntryFromZipFileTaskParameters; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.charset.Charset; import java.util.List; import java.util.Random; import static net.lingala.zip4j.headers.HeaderUtil.getIndexOfFileHeader; import static net.lingala.zip4j.util.FileUtils.copyFile; -public class RemoveEntryFromZipFileTask extends AsyncZipTask { +public class RemoveEntryFromZipFileTask extends AsyncZipTask { private ZipModel zipModel; @@ -28,7 +30,7 @@ public RemoveEntryFromZipFileTask(ProgressMonitor progressMonitor, boolean runIn } @Override - protected void executeTask(FileHeader fileHeader, ProgressMonitor progressMonitor) + protected void executeTask(RemoveEntryFromZipFileTaskParameters taskParameters, ProgressMonitor progressMonitor) throws IOException { if (zipModel.isSplitArchive()) { throw new ZipException("This is a split archive. Zip file format does not allow updating split/spanned files"); @@ -41,8 +43,8 @@ protected void executeTask(FileHeader fileHeader, ProgressMonitor progressMonito RandomAccessFile inputStream = new RandomAccessFile(zipModel.getZipFile(), RandomAccessFileMode.READ.getValue())){ - int indexOfFileHeader = getIndexOfFileHeader(zipModel, fileHeader); - long offsetLocalFileHeader = getOffsetLocalFileHeader(fileHeader); + int indexOfFileHeader = getIndexOfFileHeader(zipModel, taskParameters.fileHeader); + long offsetLocalFileHeader = getOffsetLocalFileHeader(taskParameters.fileHeader); long offsetStartOfCentralDirectory = getOffsetOfStartOfCentralDirectory(zipModel); List fileHeaders = zipModel.getCentralDirectory().getFileHeaders(); long offsetEndOfCompressedData = getOffsetEndOfCompressedData(indexOfFileHeader, @@ -64,7 +66,7 @@ protected void executeTask(FileHeader fileHeader, ProgressMonitor progressMonito verifyIfTaskIsCancelled(); - updateHeaders(zipModel, outputStream, indexOfFileHeader, offsetEndOfCompressedData, offsetLocalFileHeader); + updateHeaders(zipModel, outputStream, indexOfFileHeader, offsetEndOfCompressedData, offsetLocalFileHeader, taskParameters.charset); successFlag = true; } finally { cleanupFile(successFlag, zipModel.getZipFile(), temporaryZipFile); @@ -120,7 +122,7 @@ private long getOffsetLocalFileHeader(FileHeader fileHeader) { } private void updateHeaders(ZipModel zipModel, SplitOutputStream splitOutputStream, int indexOfFileHeader, long - offsetEndOfCompressedFile, long offsetLocalFileHeader) throws IOException, ZipException { + offsetEndOfCompressedFile, long offsetLocalFileHeader, Charset charset) throws IOException { updateEndOfCentralDirectoryRecord(zipModel, splitOutputStream); zipModel.getCentralDirectory().getFileHeaders().remove(indexOfFileHeader); @@ -128,7 +130,7 @@ private void updateHeaders(ZipModel zipModel, SplitOutputStream splitOutputStrea offsetLocalFileHeader, indexOfFileHeader); HeaderWriter headerWriter = new HeaderWriter(); - headerWriter.finalizeZipFile(zipModel, splitOutputStream); + headerWriter.finalizeZipFile(zipModel, splitOutputStream, charset); } private void updateEndOfCentralDirectoryRecord(ZipModel zipModel, SplitOutputStream splitOutputStream) @@ -173,12 +175,21 @@ private void restoreFileName(File zipFile, File temporaryZipFile) throws ZipExce } @Override - protected long calculateTotalWork(FileHeader fileHeader) { - return zipModel.getZipFile().length() - fileHeader.getCompressedSize(); + protected long calculateTotalWork(RemoveEntryFromZipFileTaskParameters taskParameters) { + return zipModel.getZipFile().length() - taskParameters.fileHeader.getCompressedSize(); } @Override protected ProgressMonitor.Task getTask() { return ProgressMonitor.Task.REMOVE_ENTRY; } + + public static class RemoveEntryFromZipFileTaskParameters extends AbstractZipTaskParameters { + private FileHeader fileHeader; + + public RemoveEntryFromZipFileTaskParameters(FileHeader fileHeader, Charset charset) { + super(charset); + this.fileHeader = fileHeader; + } + } } diff --git a/src/main/java/net/lingala/zip4j/tasks/SetCommentTask.java b/src/main/java/net/lingala/zip4j/tasks/SetCommentTask.java index 95230df2..367574e5 100644 --- a/src/main/java/net/lingala/zip4j/tasks/SetCommentTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/SetCommentTask.java @@ -6,10 +6,12 @@ import net.lingala.zip4j.model.EndOfCentralDirectoryRecord; import net.lingala.zip4j.model.ZipModel; import net.lingala.zip4j.progress.ProgressMonitor; +import net.lingala.zip4j.tasks.SetCommentTask.SetCommentTaskTaskParameters; import java.io.IOException; +import java.nio.charset.Charset; -public class SetCommentTask extends AsyncZipTask { +public class SetCommentTask extends AsyncZipTask { private ZipModel zipModel; @@ -19,13 +21,13 @@ public SetCommentTask(ProgressMonitor progressMonitor, boolean runInThread, ZipM } @Override - protected void executeTask(String comment, ProgressMonitor progressMonitor) throws IOException { - if (comment == null) { + protected void executeTask(SetCommentTaskTaskParameters taskParameters, ProgressMonitor progressMonitor) throws IOException { + if (taskParameters.comment == null) { throw new ZipException("comment is null, cannot update Zip file with comment"); } EndOfCentralDirectoryRecord endOfCentralDirectoryRecord = zipModel.getEndOfCentralDirectoryRecord(); - endOfCentralDirectoryRecord.setComment(comment); + endOfCentralDirectoryRecord.setComment(taskParameters.comment); try (SplitOutputStream outputStream = new SplitOutputStream(zipModel.getZipFile())) { if (zipModel.isZip64Format()) { @@ -36,12 +38,12 @@ protected void executeTask(String comment, ProgressMonitor progressMonitor) thro } HeaderWriter headerWriter = new HeaderWriter(); - headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream); + headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream, taskParameters.charset); } } @Override - protected long calculateTotalWork(String taskParameters) { + protected long calculateTotalWork(SetCommentTaskTaskParameters taskParameters) { return 0; } @@ -49,4 +51,13 @@ protected long calculateTotalWork(String taskParameters) { protected ProgressMonitor.Task getTask() { return ProgressMonitor.Task.SET_COMMENT; } + + public static class SetCommentTaskTaskParameters extends AbstractZipTaskParameters { + private String comment; + + public SetCommentTaskTaskParameters(String comment, Charset charset) { + super(charset); + this.comment = comment; + } + } } diff --git a/src/test/java/net/lingala/zip4j/AbstractIT.java b/src/test/java/net/lingala/zip4j/AbstractIT.java index fc3e9c3e..ac83206c 100644 --- a/src/test/java/net/lingala/zip4j/AbstractIT.java +++ b/src/test/java/net/lingala/zip4j/AbstractIT.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; @@ -25,6 +26,9 @@ public abstract class AbstractIT { getTestFileFromResources("sample_text_large.txt"), getTestFileFromResources("sample.pdf") ); + protected static final Charset CHARSET_MS_932 = Charset.forName("Ms932"); + protected static final Charset CHARSET_GBK = Charset.forName("GBK"); + protected static final Charset CHARSET_CP_949 = Charset.forName("Cp949"); protected File generatedZipFile; protected File outputFolder; diff --git a/src/test/java/net/lingala/zip4j/AddFilesToZipIT.java b/src/test/java/net/lingala/zip4j/AddFilesToZipIT.java index 5974da5b..39565052 100644 --- a/src/test/java/net/lingala/zip4j/AddFilesToZipIT.java +++ b/src/test/java/net/lingala/zip4j/AddFilesToZipIT.java @@ -69,6 +69,20 @@ public void testAddFileAsStringWithZipParametersStoreAndStandardEncryption() thr EncryptionMethod.ZIP_STANDARD, null); } + @Test + public void testAddFileAsStringWithZipParametersStoreAndStandardEncryptionAndCharsetCp949() throws IOException { + ZipParameters zipParameters = createZipParameters(EncryptionMethod.ZIP_STANDARD, null); + zipParameters.setCompressionMethod(CompressionMethod.STORE); + ZipFile zipFile = new ZipFile(generatedZipFile, PASSWORD); + + zipFile.setCharset(CHARSET_CP_949); + String koreanFileName = "가나다.abc"; + zipFile.addFile(TestUtils.getTestFileFromResources(koreanFileName).getPath(), zipParameters); + + ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, PASSWORD, outputFolder, 1, true, CHARSET_CP_949); + assertThat(zipFile.getFileHeaders().get(0).getFileName()).isEqualTo(koreanFileName); + } + @Test public void testAddFileThrowsExceptionWhenFileDoesNotExist() throws ZipException { expectedException.expectMessage("File does not exist: somefile.txt"); @@ -656,6 +670,43 @@ public void testAddStreamToWithStoreCompressionAndZipStandardEncryption() throws assertThat(BitUtils.isBitSet(generalPurposeBytes[0], 3)).isTrue(); } + @Test + public void testAddStreamWithStoreCompressionAndCharset() throws IOException { + String koreanFileName = "가나다.abc"; + File fileToAdd = TestUtils.getTestFileFromResources(koreanFileName); + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setCompressionMethod(CompressionMethod.STORE); + zipParameters.setFileNameInZip(fileToAdd.getName()); + ZipFile zipFile = new ZipFile(generatedZipFile); + InputStream inputStream = new FileInputStream(fileToAdd); + + zipFile.setCharset(CHARSET_CP_949); + zipFile.addStream(inputStream, zipParameters); + + byte[] generalPurposeBytes = zipFile.getFileHeaders().get(0).getGeneralPurposeFlag(); + // assert that extra data record is not present + assertThat(BitUtils.isBitSet(generalPurposeBytes[1], 3)).isFalse(); + assertThat(zipFile.getFileHeaders().get(0).getFileName()).isEqualTo(koreanFileName); + } + + @Test + public void testAddStreamWithStoreCompressionAndDefaultCharset() throws IOException { + String koreanFileName = "가나다.abc"; + File fileToAdd = TestUtils.getTestFileFromResources(koreanFileName); + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setCompressionMethod(CompressionMethod.STORE); + zipParameters.setFileNameInZip(fileToAdd.getName()); + ZipFile zipFile = new ZipFile(generatedZipFile); + InputStream inputStream = new FileInputStream(fileToAdd); + + zipFile.addStream(inputStream, zipParameters); + + byte[] generalPurposeBytes = zipFile.getFileHeaders().get(0).getGeneralPurposeFlag(); + // assert that extra data record is not present + assertThat(BitUtils.isBitSet(generalPurposeBytes[1], 3)).isTrue(); + assertThat(zipFile.getFileHeaders().get(0).getFileName()).isEqualTo(koreanFileName); + } + @Test public void testAddStreamToZipWithAesEncryptionForNewZipAddsSuccessfully() throws IOException { File fileToAdd = TestUtils.getTestFileFromResources("бореиская.txt"); @@ -719,6 +770,22 @@ public void testAddStreamToZipWithAesEncryptionV1ForExistingZipAddsSuccessfully( AesKeyStrength.KEY_STRENGTH_256, AesVersion.ONE); } + @Test + public void testAddStreamToZipWithCharsetCp949() throws IOException { + String koreanFileName = "가나다.abc"; + ZipFile zipFile = new ZipFile(generatedZipFile); + File fileToAdd = TestUtils.getTestFileFromResources(koreanFileName); + InputStream inputStream = new FileInputStream(fileToAdd); + ZipParameters zipParameters = new ZipParameters(); + + zipParameters.setFileNameInZip(fileToAdd.getName()); + zipFile.setCharset(CHARSET_CP_949); + zipFile.addStream(inputStream, zipParameters); + + ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, null, outputFolder, 1, true, CHARSET_CP_949); + assertThat(zipFile.getFileHeaders().get(0).getFileName()).isEqualTo(koreanFileName); + } + @Test public void testAddStreamToZipWithSameEntryNameRemovesOldEntry() throws IOException { File fileToAdd = TestUtils.getTestFileFromResources("sample.pdf"); diff --git a/src/test/java/net/lingala/zip4j/CreateZipFileIT.java b/src/test/java/net/lingala/zip4j/CreateZipFileIT.java index 63b001ae..35429f95 100644 --- a/src/test/java/net/lingala/zip4j/CreateZipFileIT.java +++ b/src/test/java/net/lingala/zip4j/CreateZipFileIT.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; +import static net.lingala.zip4j.testutils.TestUtils.getTestFileFromResources; import static net.lingala.zip4j.testutils.ZipFileVerifier.verifyZipFileByExtractingAllFiles; import static org.assertj.core.api.Assertions.assertThat; @@ -41,6 +42,20 @@ public void testCreateSplitZipFileNotSplitArchiveWithZipNameAsString() throws IO ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, outputFolder, FILES_TO_ADD.size()); } + @Test + public void testCreateSplitZipFileNotSplitArchiveWithZipNameAsStringAndCharsetCp949() throws IOException { + String koreanFileName = "가나다.abc"; + ZipFile zipFile = new ZipFile(generatedZipFile.getPath()); + List filesToAdd = new ArrayList<>(); + filesToAdd.add(getTestFileFromResources(koreanFileName)); + + zipFile.setCharset(CHARSET_CP_949); + zipFile.createSplitZipFile(filesToAdd, new ZipParameters(), false, -1); + + ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, null, outputFolder, filesToAdd.size(), true, CHARSET_CP_949); + assertThat(zipFile.getFileHeaders().get(0).getFileName()).isEqualTo(koreanFileName); + } + @Test public void testCreateSplitZipFileNotSplitArchiveWithZipNameAsStringWithAESEncryption256() throws IOException { ZipParameters zipParameters = createZipParameters(EncryptionMethod.AES, diff --git a/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java b/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java index 8e7f8af4..a98f69ee 100644 --- a/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java +++ b/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java @@ -16,8 +16,10 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -301,6 +303,20 @@ public void testExtractZipFileWithZip64ExtraDataRecordWithOnlyFileSizesInIt() th assertThat(Files.walk(outputFolder.toPath()).filter(Files::isRegularFile)).hasSize(1); } + @Test + public void testExtractZipFileWithChineseCharsetGBK() throws IOException { + String expactedFileName = "fff - 副本.txt"; + ZipFile zipFile = new ZipFile(getTestArchiveFromResources("testfile_with_chinese_filename_by_7zip.zip")); + + zipFile.setCharset(CHARSET_GBK); + zipFile.extractAll(outputFolder.getPath()); + + assertThat(zipFile.getFileHeaders()).hasSize(2); + Set filenameSet = new HashSet<>(); + Files.walk(outputFolder.toPath()).forEach(file -> filenameSet.add(file.getFileName().toString())); + assertThat(filenameSet.contains(expactedFileName)).isTrue(); + } + private void verifyNumberOfFilesInOutputFolder(File outputFolder, int numberOfExpectedFiles) { assertThat(outputFolder.listFiles()).hasSize(numberOfExpectedFiles); } diff --git a/src/test/java/net/lingala/zip4j/RemoveFilesFromZipIT.java b/src/test/java/net/lingala/zip4j/RemoveFilesFromZipIT.java index 2ddd7bdc..005fe3e0 100644 --- a/src/test/java/net/lingala/zip4j/RemoveFilesFromZipIT.java +++ b/src/test/java/net/lingala/zip4j/RemoveFilesFromZipIT.java @@ -70,6 +70,21 @@ public void testRemoveFileAsFileNameRemovesSuccessfully() throws IOException { verifyZipFileDoesNotContainFile(generatedZipFile, "sample_text1.txt"); } + @Test + public void testRemoveFileAsFileNameWithCharsetCp949RemovesSuccessfully() throws IOException { + ZipFile zipFile = new ZipFile(generatedZipFile); + List filesToAdd = new ArrayList<>(); + filesToAdd.add(TestUtils.getTestFileFromResources("가나다.abc")); + filesToAdd.add(TestUtils.getTestFileFromResources("sample_text1.txt")); + + zipFile.setCharset(CHARSET_CP_949); + zipFile.addFiles(filesToAdd); + zipFile.removeFile("sample_text1.txt"); + + ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, null, outputFolder, 1, true, CHARSET_CP_949); + verifyZipFileDoesNotContainFile(generatedZipFile, "sample_text1.txt"); + } + @Test public void testRemoveFileAsFileNameRemovesSuccessfullyWithFolderNameInPath() throws IOException { ZipParameters zipParameters = createZipParameters(EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256); diff --git a/src/test/java/net/lingala/zip4j/ZipFileZip64IT.java b/src/test/java/net/lingala/zip4j/ZipFileZip64IT.java index 9d724802..a64c1f98 100644 --- a/src/test/java/net/lingala/zip4j/ZipFileZip64IT.java +++ b/src/test/java/net/lingala/zip4j/ZipFileZip64IT.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat; @@ -69,7 +70,7 @@ public void testZip64WithNumberOfEntriesGreaterThan70k() throws IOException { private void verifyZip64HeadersPresent() throws IOException { HeaderReader headerReader = new HeaderReader(); ZipModel zipModel = headerReader.readAllHeaders(new RandomAccessFile(generatedZipFile, - RandomAccessFileMode.READ.getValue())); + RandomAccessFileMode.READ.getValue()), StandardCharsets.UTF_8); assertThat(zipModel.getZip64EndOfCentralDirectoryLocator()).isNotNull(); assertThat(zipModel.getZip64EndOfCentralDirectoryRecord()).isNotNull(); assertThat(zipModel.isZip64Format()).isTrue(); diff --git a/src/test/java/net/lingala/zip4j/headers/FileHeaderFactoryTest.java b/src/test/java/net/lingala/zip4j/headers/FileHeaderFactoryTest.java index f14991ad..fad6adf2 100644 --- a/src/test/java/net/lingala/zip4j/headers/FileHeaderFactoryTest.java +++ b/src/test/java/net/lingala/zip4j/headers/FileHeaderFactoryTest.java @@ -14,6 +14,9 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + import static net.lingala.zip4j.util.BitUtils.isBitSet; import static net.lingala.zip4j.util.Zip4jUtil.javaToDosTime; import static org.assertj.core.api.Assertions.assertThat; @@ -33,12 +36,12 @@ public void testGenerateFileHeaderWithoutFileNameThrowsException() throws ZipExc expectedException.expect(ZipException.class); expectedException.expectMessage("fileNameInZip is null or empty"); - fileHeaderFactory.generateFileHeader(new ZipParameters(), false, 0); + fileHeaderFactory.generateFileHeader(new ZipParameters(), false, 0, StandardCharsets.UTF_8); } @Test public void testGenerateFileHeaderDefaults() throws ZipException { - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, StandardCharsets.UTF_8); assertThat(fileHeader).isNotNull(); assertThat(fileHeader.getCompressionMethod()).isEqualTo(CompressionMethod.DEFLATE); @@ -57,7 +60,7 @@ public void testGenerateFileHeaderForStoreWithoutEncryption() throws ZipExceptio ZipParameters zipParameters = generateZipParameters(); zipParameters.setCompressionMethod(CompressionMethod.STORE); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyFileHeader(fileHeader, zipParameters, false, 0, false); } @@ -69,7 +72,7 @@ public void testGenerateFileHeaderWhenEncryptingWithoutMethodThrowsException() t ZipParameters zipParameters = generateZipParameters(); zipParameters.setEncryptFiles(true); - fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); } @Test @@ -78,7 +81,7 @@ public void testGenerateFileHeaderWithStandardEncryption() throws ZipException { zipParameters.setEncryptFiles(true); zipParameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyFileHeader(fileHeader, zipParameters, false, 0, false); } @@ -92,7 +95,7 @@ public void testGenerateFileHeaderWithAesEncryptionWithNullKeyStrengthThrowsExce zipParameters.setEncryptionMethod(EncryptionMethod.AES); zipParameters.setAesKeyStrength(null); - fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); } @Test @@ -101,7 +104,7 @@ public void testGenerateFileHeaderWithAesEncryptionWithoutKeyStrengthUsesDefault zipParameters.setEncryptFiles(true); zipParameters.setEncryptionMethod(EncryptionMethod.AES); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyFileHeader(fileHeader, zipParameters, false, 0, true); verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_256, CompressionMethod.DEFLATE, AesVersion.TWO); @@ -114,7 +117,7 @@ public void testGenerateFileHeaderWithAesEncryptionWithKeyStrength128() throws Z zipParameters.setEncryptionMethod(EncryptionMethod.AES); zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_128); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyFileHeader(fileHeader, zipParameters, false, 0, true); verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_128, CompressionMethod.DEFLATE, AesVersion.TWO); @@ -127,7 +130,7 @@ public void testGenerateFileHeaderWithAesEncryptionWithKeyStrength192() throws Z zipParameters.setEncryptionMethod(EncryptionMethod.AES); zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_192); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyFileHeader(fileHeader, zipParameters, false, 0, true); verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_192, CompressionMethod.DEFLATE, AesVersion.TWO); @@ -140,7 +143,7 @@ public void testGenerateFileHeaderWithAesEncryptionWithKeyStrength256() throws Z zipParameters.setEncryptionMethod(EncryptionMethod.AES); zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyFileHeader(fileHeader, zipParameters, false, 0, true); verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_256, CompressionMethod.DEFLATE, AesVersion.TWO); @@ -154,7 +157,7 @@ public void testGenerateFileHeaderWithAesEncryptionVersionV1() throws ZipExcepti zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256); zipParameters.setAesVersion(AesVersion.ONE); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyFileHeader(fileHeader, zipParameters, false, 0, true); verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_256, CompressionMethod.DEFLATE, AesVersion.ONE); @@ -168,7 +171,7 @@ public void testGenerateFileHeaderWithAesEncryptionWithNullVersionUsesV2() throw zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256); zipParameters.setAesVersion(null); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyFileHeader(fileHeader, zipParameters, false, 0, true); verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_256, CompressionMethod.DEFLATE, AesVersion.TWO); @@ -180,7 +183,7 @@ public void testGenerateFileHeaderWithLastModifiedFileTime() throws ZipException ZipParameters zipParameters = generateZipParameters(); zipParameters.setLastModifiedFileTime(lastModifiedFileTime); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); assertThat(fileHeader.getLastModifiedTime()).isEqualTo(javaToDosTime(zipParameters.getLastModifiedFileTime())); } @@ -190,7 +193,7 @@ public void testGenerateFileHeaderWithCompressionLevelMaximum() throws ZipExcept ZipParameters zipParameters = generateZipParameters(); zipParameters.setCompressionLevel(CompressionLevel.MAXIMUM); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyCompressionLevelGridForDeflate(CompressionLevel.MAXIMUM, fileHeader.getGeneralPurposeFlag()[0]); } @@ -200,7 +203,7 @@ public void testGenerateFileHeaderWithCompressionLevelFast() throws ZipException ZipParameters zipParameters = generateZipParameters(); zipParameters.setCompressionLevel(CompressionLevel.FAST); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyCompressionLevelGridForDeflate(CompressionLevel.FAST, fileHeader.getGeneralPurposeFlag()[0]); } @@ -210,11 +213,23 @@ public void testGenerateFileHeaderWithCompressionLevelFastest() throws ZipExcept ZipParameters zipParameters = generateZipParameters(); zipParameters.setCompressionLevel(CompressionLevel.FASTEST); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); verifyCompressionLevelGridForDeflate(CompressionLevel.FASTEST, fileHeader.getGeneralPurposeFlag()[0]); } + @Test + public void testGenerateFileHeaderWithCorrectCharset() throws ZipException { + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, Charset.forName("Cp949")); + assertThat(isBitSet(fileHeader.getGeneralPurposeFlag()[1], 3)).isFalse(); + } + + @Test + public void testGenerateFileHeaderWithUTF8Charset() throws ZipException { + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, StandardCharsets.UTF_8); + assertThat(isBitSet(fileHeader.getGeneralPurposeFlag()[1], 3)).isTrue(); + } + @Test public void testGenerateLocalFileHeader() { long lastModifiedFileTime = javaToDosTime(System.currentTimeMillis()); diff --git a/src/test/java/net/lingala/zip4j/headers/HeaderReaderIT.java b/src/test/java/net/lingala/zip4j/headers/HeaderReaderIT.java index eab8a976..51a17689 100644 --- a/src/test/java/net/lingala/zip4j/headers/HeaderReaderIT.java +++ b/src/test/java/net/lingala/zip4j/headers/HeaderReaderIT.java @@ -45,7 +45,7 @@ public void testReadAllHeadersWith10Entries() throws IOException, ZipException { ZipModel actualZipModel = generateZipHeadersFile(numberOfEntries, EncryptionMethod.NONE); try(RandomAccessFile randomAccessFile = initializeRandomAccessFile(actualZipModel.getZipFile())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); verifyZipModel(readZipModel, 10, false); assertThat(readZipModel.getEndOfCentralDirectoryRecord().getComment()).isEmpty(); } @@ -59,7 +59,7 @@ public void testReadAllHeadersWithEndOfCentralDirectoryComment() throws IOExcept actualZipModel.setZipFile(headersFile); try(RandomAccessFile randomAccessFile = initializeRandomAccessFile(actualZipModel.getZipFile())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); verifyZipModel(readZipModel, 1, false); EndOfCentralDirectoryRecord endOfCentralDirectoryRecord = readZipModel.getEndOfCentralDirectoryRecord(); @@ -75,7 +75,7 @@ public void testReadAllWithoutEndOfCentralDirectoryRecordThrowsException() throw randomAccessFile.seek(4000); randomAccessFile.write(1); - headerReader.readAllHeaders(randomAccessFile); + headerReader.readAllHeaders(randomAccessFile, null); fail("Should throw an exception"); } catch (ZipException e) { assertThat(e.getMessage()).isEqualTo("Zip headers not found. Probably not a zip file or a corrupted zip file"); @@ -90,7 +90,7 @@ public void testReadAllWithoutEnoughHeaderDataThrowsException() throws IOExcepti randomAccessFile.seek(1000); randomAccessFile.write(1); - headerReader.readAllHeaders(randomAccessFile); + headerReader.readAllHeaders(randomAccessFile, null); fail("Should throw an exception"); } catch (ZipException e) { assertThat(e.getMessage()).isEqualTo("Zip headers not found. Probably not a zip file or a corrupted zip file"); @@ -107,7 +107,7 @@ public void testReadAllWithoutFileHeaderSignatureThrowsException() throws IOExce try(RandomAccessFile randomAccessFile = new RandomAccessFile(actualZipModel.getZipFile(), RandomAccessFileMode.READ.getValue())) { - headerReader.readAllHeaders(randomAccessFile); + headerReader.readAllHeaders(randomAccessFile, null); fail("Should throw an exception"); } catch (ZipException e) { assertThat(e.getMessage()).isEqualTo("Expected central directory entry not found (#2)"); @@ -124,7 +124,7 @@ public void testReadAllWithFileNameContainsWindowsDriveExcludesIt() throws IOExc try(RandomAccessFile randomAccessFile = new RandomAccessFile(actualZipModel.getZipFile(), RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); FileHeader fileHeader = readZipModel.getCentralDirectory().getFileHeaders().get(0); assertThat(fileHeader.getFileName()).isEqualTo("test.txt"); } @@ -139,7 +139,7 @@ public void testReadAllWithoutFileNameWritesNull() throws IOException, ZipExcept try(RandomAccessFile randomAccessFile = new RandomAccessFile(actualZipModel.getZipFile(), RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); FileHeader fileHeader = readZipModel.getCentralDirectory().getFileHeaders().get(0); assertThat(fileHeader.getFileName()).isNull(); } @@ -168,7 +168,7 @@ public void testReadAllWithAesEncryption() throws ZipException, IOException { try(RandomAccessFile randomAccessFile = new RandomAccessFile(actualZipModel.getZipFile(), RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); for (FileHeader fileHeader : readZipModel.getCentralDirectory().getFileHeaders()) { assertThat(fileHeader.getAesExtraDataRecord()).isNotNull(); assertThat(fileHeader.getAesExtraDataRecord().getAesKeyStrength()).isEqualTo(AesKeyStrength.KEY_STRENGTH_256); @@ -182,7 +182,7 @@ public void testReadAllWithStandardZipEncryption() throws ZipException, IOExcept try(RandomAccessFile randomAccessFile = new RandomAccessFile(actualZipModel.getZipFile(), RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); for (FileHeader fileHeader : readZipModel.getCentralDirectory().getFileHeaders()) { assertThat(fileHeader.isEncrypted()).isTrue(); assertThat(fileHeader.getEncryptionMethod()).isEqualTo(EncryptionMethod.ZIP_STANDARD); @@ -202,7 +202,7 @@ public void testReadAllZip64Format() throws IOException, ZipException { try(RandomAccessFile randomAccessFile = new RandomAccessFile(actualZipModel.getZipFile(), RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); assertThat(readZipModel.isZip64Format()).isTrue(); assertThat(readZipModel.getZip64EndOfCentralDirectoryRecord()).isNotNull(); assertThat(readZipModel.getZip64EndOfCentralDirectoryLocator()).isNotNull(); @@ -218,7 +218,7 @@ public void testReadLocalFileHeader() throws ZipException, IOException { File headerFile = generateAndWriteLocalFileHeader(entrySize, EncryptionMethod.NONE); try(InputStream inputStream = new FileInputStream(headerFile)) { - LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream); + LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream, StandardCharsets.UTF_8); assertThat(readLocalFileHeader).isNotNull(); assertThat(readLocalFileHeader.getCompressedSize()).isEqualTo(entrySize); assertThat(readLocalFileHeader.getUncompressedSize()).isEqualTo(entrySize); @@ -231,7 +231,7 @@ public void testReadLocalFileHeaderWithAesEncryption() throws ZipException, IOEx File headerFile = generateAndWriteLocalFileHeader(entrySize, EncryptionMethod.AES); try(InputStream inputStream = new FileInputStream(headerFile)) { - LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream); + LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream, StandardCharsets.UTF_8); assertThat(readLocalFileHeader).isNotNull(); assertThat(readLocalFileHeader.getCompressedSize()).isEqualTo(entrySize); assertThat(readLocalFileHeader.getUncompressedSize()).isEqualTo(entrySize); @@ -265,7 +265,7 @@ private void testWithoutUtf8FileName(String fileName, String entryComment, boole try(RandomAccessFile randomAccessFile = new RandomAccessFile(actualZipModel.getZipFile(), RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); FileHeader fileHeader = readZipModel.getCentralDirectory().getFileHeaders().get(1); if (shouldFileNamesMatch) { assertThat(fileHeader.getFileName()).isEqualTo(fileName); @@ -362,7 +362,7 @@ private List generateFileHeaders(ZipParameters zipParameters, int nu List fileHeaders = new ArrayList<>(); for (int i = 0; i < numberOfEntries; i++) { zipParameters.setFileNameInZip(FILE_NAME_PREFIX + i); - FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0); + FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, StandardCharsets.UTF_8); fileHeaders.add(fileHeader); } return fileHeaders; @@ -390,7 +390,7 @@ private File generateAndWriteLocalFileHeader(long entrySize, EncryptionMethod en File headerFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headerFile)) { - headerWriter.writeLocalFileHeader(zipModel, localFileHeader, outputStream); + headerWriter.writeLocalFileHeader(zipModel, localFileHeader, outputStream, StandardCharsets.UTF_8); } return headerFile; @@ -399,7 +399,7 @@ private File generateAndWriteLocalFileHeader(long entrySize, EncryptionMethod en private File writeZipHeaders(ZipModel zipModel) throws IOException, ZipException { File headersFile = temporaryFolder.newFile(); try(SplitOutputStream splitOutputStream = new SplitOutputStream(headersFile)) { - headerWriter.finalizeZipFile(zipModel, splitOutputStream); + headerWriter.finalizeZipFile(zipModel, splitOutputStream, StandardCharsets.UTF_8); return headersFile; } } diff --git a/src/test/java/net/lingala/zip4j/headers/HeaderUtilTest.java b/src/test/java/net/lingala/zip4j/headers/HeaderUtilTest.java index b7db6ef5..0184ff8b 100644 --- a/src/test/java/net/lingala/zip4j/headers/HeaderUtilTest.java +++ b/src/test/java/net/lingala/zip4j/headers/HeaderUtilTest.java @@ -8,6 +8,8 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -217,7 +219,7 @@ public void testDecodeStringWithCharsetForUtf8() { String utf8StringToEncode = "asdäüöö"; byte[] utf8EncodedBytes = utf8StringToEncode.getBytes(StandardCharsets.UTF_8); - assertThat(HeaderUtil.decodeStringWithCharset(utf8EncodedBytes, true)).isEqualTo(utf8StringToEncode); + assertThat(HeaderUtil.decodeStringWithCharset(utf8EncodedBytes, true, null)).isEqualTo(utf8StringToEncode); } @@ -226,7 +228,7 @@ public void testDecodeStringWithCharsetWithoutUtf8ForUtf8String() { String utf8StringToEncode = "asdäüöö"; byte[] utf8EncodedBytes = utf8StringToEncode.getBytes(StandardCharsets.UTF_8); - assertThat(HeaderUtil.decodeStringWithCharset(utf8EncodedBytes, false)).isNotEqualTo(utf8StringToEncode); + assertThat(HeaderUtil.decodeStringWithCharset(utf8EncodedBytes, false, null)).isNotEqualTo(utf8StringToEncode); } @@ -235,10 +237,34 @@ public void testDecodeStringWithCharsetWithoutUtf8AndWithEnglishChars() { String plainString = "asdasda234234"; byte[] plainEncodedBytes = plainString.getBytes(); - assertThat(HeaderUtil.decodeStringWithCharset(plainEncodedBytes, false)).isEqualTo(plainString); + assertThat(HeaderUtil.decodeStringWithCharset(plainEncodedBytes, false, null)).isEqualTo(plainString); } + @Test + public void testDecodeStringWithCharsetWithISO8859AndFinnishChars() throws UnsupportedEncodingException { + String finnishString = "asdäüöö"; + byte[] plainEncodedBytes = finnishString.getBytes("ISO-8859-1"); + + assertThat(HeaderUtil.decodeStringWithCharset(plainEncodedBytes, false, Charset.forName("ISO-8859-1"))).isEqualTo(finnishString); + } + + @Test + public void testDecodeStringWithCharsetWithUTF8CharsetAndKoreanChars() { + String koreanString = "가나다"; + byte[] plainEncodedBytes = koreanString.getBytes(StandardCharsets.UTF_8); + + assertThat(HeaderUtil.decodeStringWithCharset(plainEncodedBytes, true, null)).isEqualTo(koreanString); + } + + @Test + public void testDecodeStringWithCharsetWithNullCharsetAndEnglishChars() { + String englishString = "asdasda234234"; + byte[] plainEncodedBytes = englishString.getBytes(); + + assertThat(HeaderUtil.decodeStringWithCharset(plainEncodedBytes, false, null)).isEqualTo(englishString); + } + private List generateFileHeaderWithFileNames(String fileNamePrefix, int numberOfEntriesToAdd) { List fileHeaders = new ArrayList<>(); for (int i = 0; i < numberOfEntriesToAdd; i++) { diff --git a/src/test/java/net/lingala/zip4j/headers/HeaderWriterIT.java b/src/test/java/net/lingala/zip4j/headers/HeaderWriterIT.java index 10ea59d2..7607e125 100644 --- a/src/test/java/net/lingala/zip4j/headers/HeaderWriterIT.java +++ b/src/test/java/net/lingala/zip4j/headers/HeaderWriterIT.java @@ -30,6 +30,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -63,11 +65,11 @@ public void testWriteLocalFileHeaderSimpleLocalFileHeaderSuccessScenario() throw File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream); + headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream, StandardCharsets.UTF_8); } try(InputStream inputStream = new FileInputStream(headersFile)) { - LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream); + LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream, StandardCharsets.UTF_8); verifyLocalFileHeader(readLocalFileHeader, FILE_NAME_PREFIX + "LFH", COMPRESSED_SIZE, UNCOMPRESSED_SIZE); } } @@ -80,11 +82,11 @@ public void testWriteLocalFileHeaderForZip64Format() throws IOException { File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream); + headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream, StandardCharsets.UTF_8); } try(InputStream inputStream = new FileInputStream(headersFile)) { - LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream); + LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream, StandardCharsets.UTF_8); verifyLocalFileHeader(readLocalFileHeader, FILE_NAME_PREFIX + "LFH", COMPRESSED_SIZE_ZIP64, UNCOMPRESSED_SIZE_ZIP64); verifyZip64ExtendedInfo(readLocalFileHeader.getZip64ExtendedInfo(), COMPRESSED_SIZE_ZIP64, @@ -107,9 +109,27 @@ public void testWriteLocalFileHeaderEnglishCharactersInFileNameWithoutUtf8Should } @Test - public void testWriteLocalFileHeaderJapaneseCharactersInFileNameWithoutUtf8ShouldNotMatch() + public void testWriteLocalFileHeaderJapaneseCharactersInFileNameWithoutUtf8ShouldMatch() throws IOException { - testWriteLocalFileHeaderWithFileName("公ゃ的年社", false, false); + testWriteLocalFileHeaderWithFileName("公ゃ的年社", false, true); + } + + @Test + public void testWriteLocalFileHeaderJapaneseCharactersInFileNameWithCharsetMs932ShouldMatch() + throws IOException { + testWriteLocalFileHeaderWithFileNameAndCharset("公ゃ的年社", false, true, CHARSET_MS_932); + } + + @Test + public void testWriteLocalFileHeaderJapaneseCharactersInFileNameWithUTF8CharsetWithUtf8ShouldMatch() + throws IOException { + testWriteLocalFileHeaderWithFileNameAndCharset("公ゃ的年社", true, true, StandardCharsets.UTF_8); + } + + @Test + public void testWriteLocalFileHeaderJapaneseCharactersInFileNameWithUTF8CharsetWithoutUtf8ShouldMatch() + throws IOException { + testWriteLocalFileHeaderWithFileNameAndCharset("公ゃ的年社", false, true, StandardCharsets.UTF_8); } @Test @@ -171,7 +191,7 @@ public void testFinalizeZipFileWhenZipModelNullThrowsException() throws IOExcept expectedException.expect(ZipException.class); expectedException.expectMessage("input parameters is null, cannot finalize zip file"); - headerWriter.finalizeZipFile(null, new FileOutputStream(temporaryFolder.newFile())); + headerWriter.finalizeZipFile(null, new FileOutputStream(temporaryFolder.newFile()), StandardCharsets.UTF_8); } @Test @@ -179,7 +199,7 @@ public void testFinalizeZipFileWhenOutputStreamIsNullThrowsException() throws IO expectedException.expect(ZipException.class); expectedException.expectMessage("input parameters is null, cannot finalize zip file"); - headerWriter.finalizeZipFile(new ZipModel(), null); + headerWriter.finalizeZipFile(new ZipModel(), null, StandardCharsets.UTF_8); } @Test @@ -188,11 +208,11 @@ public void testFinalizeZipFileForNonZip64Format() throws IOException { File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.finalizeZipFile(zipModel, outputStream); + headerWriter.finalizeZipFile(zipModel, outputStream, StandardCharsets.UTF_8); } try(RandomAccessFile randomAccessFile = new RandomAccessFile(headersFile, RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); verifyZipModel(readZipModel, 10); for (FileHeader fileHeader : readZipModel.getCentralDirectory().getFileHeaders()) { @@ -209,11 +229,11 @@ public void testFinalizeZipFileForZip64Format() throws IOException { File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.finalizeZipFile(zipModel, outputStream); + headerWriter.finalizeZipFile(zipModel, outputStream, StandardCharsets.UTF_8); } try(RandomAccessFile randomAccessFile = new RandomAccessFile(headersFile, RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); verifyZipModel(readZipModel, 10, COMPRESSED_SIZE_ZIP64, UNCOMPRESSED_SIZE_ZIP64, true); List fileHeaders = readZipModel.getCentralDirectory().getFileHeaders(); @@ -232,11 +252,11 @@ public void testFinalizeZipFileForAes() throws IOException { File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.finalizeZipFile(zipModel, outputStream); + headerWriter.finalizeZipFile(zipModel, outputStream, StandardCharsets.UTF_8); } try(RandomAccessFile randomAccessFile = new RandomAccessFile(headersFile, RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); verifyZipModel(readZipModel, 10); for (FileHeader fileHeader : readZipModel.getCentralDirectory().getFileHeaders()) { @@ -254,11 +274,11 @@ public void testFinalizeZipFileForZip64FormatForSplitFileWithSplitOutputStream() File headersFile = temporaryFolder.newFile(); try(SplitOutputStream outputStream = new SplitOutputStream(headersFile, 65536)) { - headerWriter.finalizeZipFile(zipModel, outputStream); + headerWriter.finalizeZipFile(zipModel, outputStream, StandardCharsets.UTF_8); } try(RandomAccessFile randomAccessFile = new RandomAccessFile(headersFile, RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); verifyZipModel(readZipModel, 10, COMPRESSED_SIZE_ZIP64, UNCOMPRESSED_SIZE_ZIP64, true); List fileHeaders = readZipModel.getCentralDirectory().getFileHeaders(); @@ -276,11 +296,11 @@ public void testFinalizeZipFileForZip64FormatForSplitFileWithCountingOutputStrea File headersFile = temporaryFolder.newFile(); try(CountingOutputStream outputStream = new CountingOutputStream(new SplitOutputStream(headersFile, 65536))) { - headerWriter.finalizeZipFile(zipModel, outputStream); + headerWriter.finalizeZipFile(zipModel, outputStream, StandardCharsets.UTF_8); } try(RandomAccessFile randomAccessFile = new RandomAccessFile(headersFile, RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, null); verifyZipModel(readZipModel, 10, COMPRESSED_SIZE_ZIP64, UNCOMPRESSED_SIZE_ZIP64, true); List fileHeaders = readZipModel.getCentralDirectory().getFileHeaders(); @@ -297,7 +317,7 @@ public void testFinalizeZipFileWithoutValidationsWhenZipModelNullThrowsException expectedException.expect(ZipException.class); expectedException.expectMessage("input parameters is null, cannot finalize zip file"); - headerWriter.finalizeZipFileWithoutValidations(null, new FileOutputStream(temporaryFolder.newFile())); + headerWriter.finalizeZipFileWithoutValidations(null, new FileOutputStream(temporaryFolder.newFile()), null); } @Test @@ -306,7 +326,7 @@ public void testFinalizeZipFileWithoutValidationsWhenOutputStreamIsNullThrowsExc expectedException.expect(ZipException.class); expectedException.expectMessage("input parameters is null, cannot finalize zip file"); - headerWriter.finalizeZipFileWithoutValidations(new ZipModel(), null); + headerWriter.finalizeZipFileWithoutValidations(new ZipModel(), null, null); } @Test @@ -315,11 +335,11 @@ public void testFinalizeZipFileWithoutValidationsForNonZip64Format() throws IOEx File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream); + headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream, StandardCharsets.UTF_8); } try(RandomAccessFile randomAccessFile = new RandomAccessFile(headersFile, RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, StandardCharsets.UTF_8); verifyZipModel(readZipModel, 10); for (FileHeader fileHeader : readZipModel.getCentralDirectory().getFileHeaders()) { @@ -336,11 +356,11 @@ public void testFinalizeZipFileWithoutValidationsForZip64Format() throws IOExcep File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream); + headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream, StandardCharsets.UTF_8); } try(RandomAccessFile randomAccessFile = new RandomAccessFile(headersFile, RandomAccessFileMode.READ.getValue())) { - ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile); + ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, StandardCharsets.UTF_8); verifyZipModel(readZipModel, 10, COMPRESSED_SIZE_ZIP64, UNCOMPRESSED_SIZE_ZIP64, true); List fileHeaders = readZipModel.getCentralDirectory().getFileHeaders(); @@ -374,7 +394,7 @@ public void testUpdateLocalFileHeaderForNonZip64() throws IOException { createAndUpdateLocalFileHeader(headersFile, COMPRESSED_SIZE, UNCOMPRESSED_SIZE, 23423); try (InputStream inputStream = new FileInputStream(headersFile)) { - LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream); + LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream, StandardCharsets.UTF_8); assertThat(readLocalFileHeader.getCompressedSize()).isEqualTo(COMPRESSED_SIZE + 100); assertThat(readLocalFileHeader.getUncompressedSize()).isEqualTo(UNCOMPRESSED_SIZE + 100); assertThat(readLocalFileHeader.getCrc()).isEqualTo(23423); @@ -388,7 +408,7 @@ public void testUpdateLocalFileHeaderForZip64() throws IOException { createAndUpdateLocalFileHeader(headersFile, COMPRESSED_SIZE_ZIP64, UNCOMPRESSED_SIZE_ZIP64, 546423); try (InputStream inputStream = new FileInputStream(headersFile)) { - LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream); + LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream, StandardCharsets.UTF_8); assertThat(readLocalFileHeader.getCompressedSize()).isEqualTo(COMPRESSED_SIZE_ZIP64 + 100); assertThat(readLocalFileHeader.getUncompressedSize()).isEqualTo(UNCOMPRESSED_SIZE_ZIP64 + 100); assertThat(readLocalFileHeader.getCrc()).isEqualTo(546423); @@ -408,7 +428,7 @@ public void createAndUpdateLocalFileHeader(File headersFile, long compressedSize localFileHeaderToWrite.setCrc(10); try (OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream); + headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream, StandardCharsets.UTF_8); } try (SplitOutputStream splitOutputStream = new SplitOutputStream(headersFile)) { @@ -488,11 +508,11 @@ private void testWriteLocalFileHeaderWithAes(AesKeyStrength aesKeyStrength, AesV File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream); + headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream, StandardCharsets.UTF_8); } try(InputStream inputStream = new FileInputStream(headersFile)) { - LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream); + LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream, StandardCharsets.UTF_8); assertThat(readLocalFileHeader.getEncryptionMethod()).isEqualTo(EncryptionMethod.AES); verifyAesExtraDataRecord(readLocalFileHeader.getAesExtraDataRecord(), aesKeyStrength, aesVersion); } @@ -520,17 +540,22 @@ private void verifyAesExtraDataRecord(AESExtraDataRecord aesExtraDataRecord, Aes private void testWriteLocalFileHeaderWithFileName(String fileNameSuffix, boolean useUtf8, boolean expectFileNamesToMatch) throws IOException { + testWriteLocalFileHeaderWithFileNameAndCharset(fileNameSuffix, useUtf8, expectFileNamesToMatch, StandardCharsets.UTF_8); + } + + private void testWriteLocalFileHeaderWithFileNameAndCharset(String fileNameSuffix, boolean useUtf8, + boolean expectFileNamesToMatch, Charset charset) throws IOException { ZipModel zipModel = createZipModel(10); LocalFileHeader localFileHeaderToWrite = createLocalFileHeader(fileNameSuffix, COMPRESSED_SIZE, UNCOMPRESSED_SIZE, useUtf8); File headersFile = temporaryFolder.newFile(); try(OutputStream outputStream = new FileOutputStream(headersFile)) { - headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream); + headerWriter.writeLocalFileHeader(zipModel, localFileHeaderToWrite, outputStream, charset); } try(InputStream inputStream = new FileInputStream(headersFile)) { - LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream); + LocalFileHeader readLocalFileHeader = headerReader.readLocalFileHeader(inputStream, charset); if (expectFileNamesToMatch) { assertThat(readLocalFileHeader.getFileName()).isEqualTo(FILE_NAME_PREFIX + fileNameSuffix); } else { diff --git a/src/test/java/net/lingala/zip4j/io/inputstream/ZipInputStreamIT.java b/src/test/java/net/lingala/zip4j/io/inputstream/ZipInputStreamIT.java index 3f79888c..7bdd28b3 100644 --- a/src/test/java/net/lingala/zip4j/io/inputstream/ZipInputStreamIT.java +++ b/src/test/java/net/lingala/zip4j/io/inputstream/ZipInputStreamIT.java @@ -17,7 +17,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.HashSet; import java.util.Random; +import java.util.Set; import static net.lingala.zip4j.testutils.TestUtils.getTestFileFromResources; import static net.lingala.zip4j.testutils.ZipFileVerifier.verifyFileContent; @@ -172,6 +174,20 @@ public void testGetNextEntryReturnsNextEntryEvenIfEntryNotCompletelyRead() throw assertThat(numberOfEntries).isEqualTo(FILES_TO_ADD.size()); } + @Test + public void testGetFileNamesWithChineseCharset() throws IOException { + InputStream inputStream = new FileInputStream(getTestArchiveFromResources("testfile_with_chinese_filename_by_7zip.zip")); + ZipInputStream zipInputStream = new ZipInputStream(inputStream, CHARSET_GBK); + LocalFileHeader localFileHeader; + String expactedFileName = "fff - 副本.txt"; + Set filenameSet = new HashSet<>(); + + while ((localFileHeader = zipInputStream.getNextEntry()) != null) { + filenameSet.add(localFileHeader.getFileName()); + } + assertThat(filenameSet.contains(expactedFileName)).isTrue(); + } + private void extractZipFileWithInputStreams(File zipFile, char[] password) throws IOException { extractZipFileWithInputStreams(zipFile, password, 4096, AesVersion.TWO); } diff --git a/src/test/java/net/lingala/zip4j/io/outputstream/ZipOutputStreamIT.java b/src/test/java/net/lingala/zip4j/io/outputstream/ZipOutputStreamIT.java index 0fe8492b..42e4d85b 100644 --- a/src/test/java/net/lingala/zip4j/io/outputstream/ZipOutputStreamIT.java +++ b/src/test/java/net/lingala/zip4j/io/outputstream/ZipOutputStreamIT.java @@ -19,7 +19,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import static net.lingala.zip4j.testutils.TestUtils.getTestFileFromResources; import static net.lingala.zip4j.testutils.ZipFileVerifier.verifyZipFileByExtractingAllFiles; import static org.assertj.core.api.Assertions.assertThat; @@ -58,6 +63,15 @@ public void testZipOutputStreamDeflateWithoutEncryption() throws IOException { testZipOutputStream(CompressionMethod.DEFLATE, false, null, null, null); } + @Test + public void testZipOutputStreamDeflateWithoutEncryptionAndKoreanFilename() throws IOException { + List filesToAdd = new ArrayList<>(); + filesToAdd.add(getTestFileFromResources("가나다.abc")); + + testZipOutputStream(CompressionMethod.DEFLATE, false, null, null, null, true, + filesToAdd, CHARSET_CP_949); + } + @Test public void testZipOutputStreamDeflateWithStandardEncryption() throws IOException { testZipOutputStream(CompressionMethod.DEFLATE, true, EncryptionMethod.ZIP_STANDARD, null, null); @@ -97,7 +111,7 @@ public void testZipOutputStreamThrowsExceptionWhenEntrySizeNotSetForStoreCompres expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("uncompressed size should be set for zip entries of compression type store"); - try(ZipOutputStream zos = initializeZipOutputStream(false)) { + try(ZipOutputStream zos = initializeZipOutputStream(false, StandardCharsets.UTF_8)) { for (File fileToAdd : FILES_TO_ADD) { zipParameters.setLastModifiedFileTime(fileToAdd.lastModified()); zipParameters.setFileNameInZip(fileToAdd.getName()); @@ -116,6 +130,13 @@ private void testZipOutputStream(CompressionMethod compressionMethod, boolean en private void testZipOutputStream(CompressionMethod compressionMethod, boolean encrypt, EncryptionMethod encryptionMethod, AesKeyStrength aesKeyStrength, AesVersion aesVersion, boolean setLastModifiedTime) + throws IOException { + testZipOutputStream(compressionMethod, encrypt, encryptionMethod, aesKeyStrength, aesVersion, true, FILES_TO_ADD, StandardCharsets.UTF_8); + } + + private void testZipOutputStream(CompressionMethod compressionMethod, boolean encrypt, + EncryptionMethod encryptionMethod, AesKeyStrength aesKeyStrength, + AesVersion aesVersion, boolean setLastModifiedTime, List filesToAdd, Charset charset) throws IOException { ZipParameters zipParameters = buildZipParameters(compressionMethod, encrypt, encryptionMethod, aesKeyStrength); @@ -124,8 +145,8 @@ private void testZipOutputStream(CompressionMethod compressionMethod, boolean en byte[] buff = new byte[4096]; int readLen; - try(ZipOutputStream zos = initializeZipOutputStream(encrypt)) { - for (File fileToAdd : FILES_TO_ADD) { + try(ZipOutputStream zos = initializeZipOutputStream(encrypt, charset)) { + for (File fileToAdd : filesToAdd) { if (zipParameters.getCompressionMethod() == CompressionMethod.STORE) { zipParameters.setEntrySize(fileToAdd.length()); @@ -145,7 +166,7 @@ private void testZipOutputStream(CompressionMethod compressionMethod, boolean en zos.closeEntry(); } } - verifyZipFileByExtractingAllFiles(generatedZipFile, PASSWORD, outputFolder, FILES_TO_ADD.size()); + verifyZipFileByExtractingAllFiles(generatedZipFile, PASSWORD, outputFolder, filesToAdd.size(), true, charset); verifyEntries(); } @@ -167,14 +188,14 @@ private void verifyEntries() throws ZipException { } } - private ZipOutputStream initializeZipOutputStream(boolean encrypt) throws IOException { + private ZipOutputStream initializeZipOutputStream(boolean encrypt, Charset charset) throws IOException { FileOutputStream fos = new FileOutputStream(generatedZipFile); if (encrypt) { - return new ZipOutputStream(fos, PASSWORD); + return new ZipOutputStream(fos, PASSWORD, charset); } - return new ZipOutputStream(fos); + return new ZipOutputStream(fos, null, charset); } private ZipParameters buildZipParameters(CompressionMethod compressionMethod, boolean encrypt, diff --git a/src/test/java/net/lingala/zip4j/testutils/ZipFileVerifier.java b/src/test/java/net/lingala/zip4j/testutils/ZipFileVerifier.java index 1bf40e60..19f2bf84 100644 --- a/src/test/java/net/lingala/zip4j/testutils/ZipFileVerifier.java +++ b/src/test/java/net/lingala/zip4j/testutils/ZipFileVerifier.java @@ -7,6 +7,8 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -25,12 +27,19 @@ public static void verifyZipFileByExtractingAllFiles(File zipFileToExtract, char public static void verifyZipFileByExtractingAllFiles(File zipFileToExtract, char[] password, File outputFolder, int expectedNumberOfEntries, boolean verifyFileContents) + throws IOException { + verifyZipFileByExtractingAllFiles(zipFileToExtract, password, outputFolder, expectedNumberOfEntries, verifyFileContents, StandardCharsets.UTF_8); + } + + public static void verifyZipFileByExtractingAllFiles(File zipFileToExtract, char[] password, File outputFolder, + int expectedNumberOfEntries, boolean verifyFileContents, Charset charset) throws IOException { assertThat(zipFileToExtract).isNotNull(); assertThat(zipFileToExtract).exists(); ZipFile zipFile = new ZipFile(zipFileToExtract, password); + zipFile.setCharset(charset); zipFile.extractAll(outputFolder.getPath()); assertThat(zipFile.getFileHeaders().size()).as("Number of file headers").isEqualTo(expectedNumberOfEntries); diff --git a/src/test/resources/test-archives/testfile_with_chinese_filename_by_7zip.zip b/src/test/resources/test-archives/testfile_with_chinese_filename_by_7zip.zip new file mode 100644 index 0000000000000000000000000000000000000000..8d8c8c95f6ec5fdfd1c983582b459eeb8ac96b85 GIT binary patch literal 313 zcmWIWW@h1H0D(Qp{Qj~1?UlknHVE?paavlMg08}jjT`srl~j}@CMNQ7?b>zy+NMXR zuJCdt13>^>`*xuA8%x_>u>rM#Fgrv$R9{k3Qh+xjlRX12yHtS6K|ldaqT9>`6=!6S zU}!B^A*K|3l~G}SAUiURY(A!Akj=LT+5z+#$Z5z1SFx%Jf(#6?p2C7m2Y9oBJjB2R Mggb$B7Kp