_list = new ArrayList<>();
+ byte[] _byte = new byte[1024 * 1024];
+ try {
+ File descDirPath = new File(descDir);
+ if (!descDirPath.exists()) {
+ descDirPath.mkdirs();
+ }
+
+ System.out.println("文件:" + zipFile.exists());
+ try {
+ final ZipFile _zipFile = new ZipFile(zipFile);
+ _zipFile.setFileNameCharset("GBK"); //è®¾ç½®ç¼–ç æ ¼å¼ï¼ˆæ”¯æŒä¸æ–‡ï¼‰
+ if (!_zipFile.isValidZipFile()) { //检查输入的zipæ–‡ä»¶æ˜¯å¦æ˜¯æœ‰æ•ˆçš„zip文件
+ throw new ZipException("压缩文件ä¸åˆæ³•,å¯èƒ½è¢«æŸå.");
+ }
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (true) {
+ final int done = _zipFile.getProgressMonitor().getPercentDone();
+ Utils.handler.post(new Runnable() {
+ @Override
+ public void run() {
+ hud.setLabel(done + "%");
+ }
+ });
+ if(done>=100){
+ break;
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }).start();
+ _zipFile.extractAll(descDir); //解压
+ } catch (ZipException e) {
+ e.printStackTrace();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ Utils.handler.post(new Runnable() {
+ @Override
+ public void run() {
+ hud.dismiss();
+ }
+ });
+ return _list;
+ }
+}
diff --git a/harmony/src/main/java/com/yutou/jianr_mg/Interfaces/ModInterface.java b/harmony/src/main/java/com/yutou/jianr_mg/Interfaces/ModInterface.java
new file mode 100644
index 0000000..8ae1a65
--- /dev/null
+++ b/harmony/src/main/java/com/yutou/jianr_mg/Interfaces/ModInterface.java
@@ -0,0 +1,5 @@
+package com.yutou.jianr_mg.Interfaces;
+
+public interface ModInterface {
+ void onAction(boolean flag, int type);
+}
diff --git a/harmony/src/main/java/com/yutou/jianr_mg/plugins/Data.java b/harmony/src/main/java/com/yutou/jianr_mg/plugins/Data.java
new file mode 100644
index 0000000..75e40b8
--- /dev/null
+++ b/harmony/src/main/java/com/yutou/jianr_mg/plugins/Data.java
@@ -0,0 +1,12 @@
+package com.yutou.jianr_mg.plugins;
+
+public class Data {
+ public static String name="harmony";
+ public static String title="专用客户端解å¡/åå’Œè°";
+ public static String appId="48B9C440405E442EBE6F5512DE585DBB";
+ public static String url="http://aaa/harmony.jar";
+ public static String image="#";
+ public static String mainFunction="com.jianrmod.plugins.harmony.Activitys.MainActivity#localActivity";
+ public static Integer permission=0;
+ public static String version="1.0";
+}
diff --git a/harmony/src/main/java/com/zip4j/core/HeaderReader.java b/harmony/src/main/java/com/zip4j/core/HeaderReader.java
new file mode 100644
index 0000000..41dfce9
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/core/HeaderReader.java
@@ -0,0 +1,1120 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.core;
+
+import com.zip4j.model.AESExtraDataRecord;
+import com.zip4j.model.CentralDirectory;
+import com.zip4j.model.DigitalSignature;
+import com.zip4j.model.EndCentralDirRecord;
+import com.zip4j.model.ExtraDataRecord;
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.LocalFileHeader;
+import com.zip4j.model.Zip64EndCentralDirLocator;
+import com.zip4j.model.Zip64EndCentralDirRecord;
+import com.zip4j.model.Zip64ExtendedInfo;
+import com.zip4j.model.ZipModel;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.exception.ZipExceptionConstants;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Raw;
+import com.zip4j.util.Zip4jConstants;
+import com.zip4j.util.Zip4jUtil;
+
+/**
+ * Helper class to read header information for the zip file
+ *
+ */
+public class HeaderReader {
+
+ private RandomAccessFile zip4jRaf = null;
+ private ZipModel zipModel;
+
+ /**
+ * Creates a new HeaderReader object with the given input stream
+ * @param zip4jRaf
+ */
+ public HeaderReader(RandomAccessFile zip4jRaf) {
+ this.zip4jRaf = zip4jRaf;
+ }
+
+ /**
+ * Reads all the header information for the zip file.
+ *
Note: This method does not read local file header information
+ * @return {@link ZipModel}
+ * @throws ZipException
+ */
+ public ZipModel readAllHeaders() throws ZipException {
+ return readAllHeaders(null);
+ }
+
+ /**
+ * Reads all the header information for the zip file. File names are read with
+ * input charset name. If this parameter is null, default system charset is used.
+ *
Note: This method does not read local file header information
+ * @return {@link ZipModel}
+ * @throws ZipException
+ */
+ public ZipModel readAllHeaders(String fileNameCharset) throws ZipException {
+ zipModel = new ZipModel();
+ zipModel.setFileNameCharset(fileNameCharset);
+ zipModel.setEndCentralDirRecord(readEndOfCentralDirectoryRecord());
+
+ // If file is Zip64 format, then Zip64 headers have to be read before
+ // reading central directory
+ zipModel.setZip64EndCentralDirLocator(readZip64EndCentralDirLocator());
+
+ if (zipModel.isZip64Format()) {
+ zipModel.setZip64EndCentralDirRecord(readZip64EndCentralDirRec());
+ if(zipModel.getZip64EndCentralDirRecord() != null &&
+ zipModel.getZip64EndCentralDirRecord().getNoOfThisDisk() > 0){
+ zipModel.setSplitArchive(true);
+ } else {
+ zipModel.setSplitArchive(false);
+ }
+ }
+
+ zipModel.setCentralDirectory(readCentralDirectory());
+ //zipModel.setLocalFileHeaderList(readLocalFileHeaders()); //Donot read local headers now.
+ return zipModel;
+ }
+
+ /**
+ * Reads end of central directory record
+ * @return {@link EndCentralDirRecord}
+ * @throws ZipException
+ */
+ private EndCentralDirRecord readEndOfCentralDirectoryRecord() throws ZipException {
+
+ if (zip4jRaf == null) {
+ throw new ZipException("random access file was null", ZipExceptionConstants.randomAccessFileNull);
+ }
+
+ try {
+ byte[] ebs = new byte[4];
+ long pos = zip4jRaf.length() - InternalZipConstants.ENDHDR;
+
+ EndCentralDirRecord endCentralDirRecord = new EndCentralDirRecord();
+ int counter = 0;
+ do {
+ zip4jRaf.seek(pos--);
+ counter++;
+ } while ((Raw.readLeInt(zip4jRaf, ebs) != InternalZipConstants.ENDSIG) && counter <= 3000);
+
+ if ((Raw.readIntLittleEndian(ebs, 0) != InternalZipConstants.ENDSIG)) {
+ throw new ZipException("zip headers not found. probably not a zip file");
+ }
+ byte[] intBuff = new byte[4];
+ byte[] shortBuff = new byte[2];
+
+ //End of central record signature
+ endCentralDirRecord.setSignature(InternalZipConstants.ENDSIG);
+
+ //number of this disk
+ readIntoBuff(zip4jRaf, shortBuff);
+ endCentralDirRecord.setNoOfThisDisk(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //number of the disk with the start of the central directory
+ readIntoBuff(zip4jRaf, shortBuff);
+ endCentralDirRecord.setNoOfThisDiskStartOfCentralDir(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //total number of entries in the central directory on this disk
+ readIntoBuff(zip4jRaf, shortBuff);
+ endCentralDirRecord.setTotNoOfEntriesInCentralDirOnThisDisk(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //total number of entries in the central directory
+ readIntoBuff(zip4jRaf, shortBuff);
+ endCentralDirRecord.setTotNoOfEntriesInCentralDir(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //size of the central directory
+ readIntoBuff(zip4jRaf, intBuff);
+ endCentralDirRecord.setSizeOfCentralDir(Raw.readIntLittleEndian(intBuff, 0));
+
+ //offset of start of central directory with respect to the starting disk number
+ readIntoBuff(zip4jRaf, intBuff);
+ byte[] longBuff = getLongByteFromIntByte(intBuff);
+ endCentralDirRecord.setOffsetOfStartOfCentralDir(Raw.readLongLittleEndian(longBuff, 0));
+
+ //.ZIP file comment length
+ readIntoBuff(zip4jRaf, shortBuff);
+ int commentLength = Raw.readShortLittleEndian(shortBuff, 0);
+ endCentralDirRecord.setCommentLength(commentLength);
+
+ //.ZIP file comment
+ if (commentLength > 0) {
+ byte[] commentBuf = new byte[commentLength];
+ readIntoBuff(zip4jRaf, commentBuf);
+ endCentralDirRecord.setComment(new String(commentBuf));
+ endCentralDirRecord.setCommentBytes(commentBuf);
+ } else {
+ endCentralDirRecord.setComment(null);
+ }
+
+ int diskNumber = endCentralDirRecord.getNoOfThisDisk();
+ if (diskNumber > 0) {
+ zipModel.setSplitArchive(true);
+ } else {
+ zipModel.setSplitArchive(false);
+ }
+
+ return endCentralDirRecord;
+ } catch (IOException e) {
+ throw new ZipException("Probably not a zip file or a corrupted zip file", e, ZipExceptionConstants.notZipFile);
+ }
+ }
+
+ /**
+ * Reads central directory information for the zip file
+ * @return {@link CentralDirectory}
+ * @throws ZipException
+ */
+ private CentralDirectory readCentralDirectory() throws ZipException {
+
+ if (zip4jRaf == null) {
+ throw new ZipException("random access file was null", ZipExceptionConstants.randomAccessFileNull);
+ }
+
+ if (zipModel.getEndCentralDirRecord() == null) {
+ throw new ZipException("EndCentralRecord was null, maybe a corrupt zip file");
+ }
+
+ try {
+ CentralDirectory centralDirectory = new CentralDirectory();
+ ArrayList fileHeaderList = new ArrayList();
+
+ EndCentralDirRecord endCentralDirRecord = zipModel.getEndCentralDirRecord();
+ long offSetStartCentralDir = endCentralDirRecord.getOffsetOfStartOfCentralDir();
+ int centralDirEntryCount = endCentralDirRecord.getTotNoOfEntriesInCentralDir();
+
+ if (zipModel.isZip64Format()) {
+ offSetStartCentralDir = zipModel.getZip64EndCentralDirRecord().getOffsetStartCenDirWRTStartDiskNo();
+ centralDirEntryCount = (int)zipModel.getZip64EndCentralDirRecord().getTotNoOfEntriesInCentralDir();
+ }
+
+ zip4jRaf.seek(offSetStartCentralDir);
+
+ byte[] intBuff = new byte[4];
+ byte[] shortBuff = new byte[2];
+ byte[] longBuff = new byte[8];
+
+ for (int i = 0; i < centralDirEntryCount; i++) {
+ FileHeader fileHeader = new FileHeader();
+
+ //FileHeader Signature
+ readIntoBuff(zip4jRaf, intBuff);
+ int signature = Raw.readIntLittleEndian(intBuff, 0);
+ if (signature != InternalZipConstants.CENSIG) {
+ throw new ZipException("Expected central directory entry not found (#" + (i + 1) + ")");
+ }
+ fileHeader.setSignature(signature);
+
+ //version made by
+ readIntoBuff(zip4jRaf, shortBuff);
+ fileHeader.setVersionMadeBy(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //version needed to extract
+ readIntoBuff(zip4jRaf, shortBuff);
+ fileHeader.setVersionNeededToExtract(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //general purpose bit flag
+ readIntoBuff(zip4jRaf, shortBuff);
+ fileHeader.setFileNameUTF8Encoded((Raw.readShortLittleEndian(shortBuff, 0) & InternalZipConstants.UFT8_NAMES_FLAG) != 0);
+ int firstByte = shortBuff[0];
+ int result = firstByte & 1;
+ if (result != 0) {
+ fileHeader.setEncrypted(true);
+ }
+ fileHeader.setGeneralPurposeFlag((byte[])shortBuff.clone());
+
+ //Check if data descriptor exists for local file header
+ fileHeader.setDataDescriptorExists(firstByte>>3 == 1);
+
+ //compression method
+ readIntoBuff(zip4jRaf, shortBuff);
+ fileHeader.setCompressionMethod(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //last mod file time
+ readIntoBuff(zip4jRaf, intBuff);
+ fileHeader.setLastModFileTime(Raw.readIntLittleEndian(intBuff, 0));
+
+ //crc-32
+ readIntoBuff(zip4jRaf, intBuff);
+ fileHeader.setCrc32(Raw.readIntLittleEndian(intBuff, 0));
+ fileHeader.setCrcBuff((byte[])intBuff.clone());
+
+ //compressed size
+ readIntoBuff(zip4jRaf, intBuff);
+ longBuff = getLongByteFromIntByte(intBuff);
+ fileHeader.setCompressedSize(Raw.readLongLittleEndian(longBuff, 0));
+
+ //uncompressed size
+ readIntoBuff(zip4jRaf, intBuff);
+ longBuff = getLongByteFromIntByte(intBuff);
+ fileHeader.setUncompressedSize(Raw.readLongLittleEndian(longBuff, 0));
+
+ //file name length
+ readIntoBuff(zip4jRaf, shortBuff);
+ int fileNameLength = Raw.readShortLittleEndian(shortBuff, 0);
+ fileHeader.setFileNameLength(fileNameLength);
+
+ //extra field length
+ readIntoBuff(zip4jRaf, shortBuff);
+ int extraFieldLength = Raw.readShortLittleEndian(shortBuff, 0);
+ fileHeader.setExtraFieldLength(extraFieldLength);
+
+ //file comment length
+ readIntoBuff(zip4jRaf, shortBuff);
+ int fileCommentLength = Raw.readShortLittleEndian(shortBuff, 0);
+ fileHeader.setFileComment(new String(shortBuff));
+
+ //disk number start
+ readIntoBuff(zip4jRaf, shortBuff);
+ fileHeader.setDiskNumberStart(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //internal file attributes
+ readIntoBuff(zip4jRaf, shortBuff);
+ fileHeader.setInternalFileAttr((byte[])shortBuff.clone());
+
+ //external file attributes
+ readIntoBuff(zip4jRaf, intBuff);
+ fileHeader.setExternalFileAttr((byte[])intBuff.clone());
+
+ //relative offset of local header
+ readIntoBuff(zip4jRaf, intBuff);
+ //Commented on 26.08.2010. Revert back if any issues
+ //fileHeader.setOffsetLocalHeader((Raw.readIntLittleEndian(intBuff, 0) & 0xFFFFFFFFL) + zip4jRaf.getStart());
+ longBuff = getLongByteFromIntByte(intBuff);
+ fileHeader.setOffsetLocalHeader((Raw.readLongLittleEndian(longBuff, 0) & 0xFFFFFFFFL));
+
+ if (fileNameLength > 0) {
+ byte[] fileNameBuf = new byte[fileNameLength];
+ readIntoBuff(zip4jRaf, fileNameBuf);
+ // Modified after user reported an issue http://www.lingala.net/zip4j/forum/index.php?topic=2.0
+// String fileName = new String(fileNameBuf, "Cp850");
+ // Modified as per http://www.lingala.net/zip4j/forum/index.php?topic=41.0
+// String fileName = Zip4jUtil.getCp850EncodedString(fileNameBuf);
+
+ String fileName = null;
+
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getFileNameCharset())) {
+ fileName = new String(fileNameBuf, zipModel.getFileNameCharset());
+ } else {
+ fileName = Zip4jUtil.decodeFileName(fileNameBuf, fileHeader.isFileNameUTF8Encoded());
+ }
+
+ if (fileName == null) {
+ throw new ZipException("fileName is null when reading central directory");
+ }
+
+ if (fileName.indexOf(":" + System.getProperty("file.separator")) >= 0) {
+ fileName = fileName.substring(fileName.indexOf(":" + System.getProperty("file.separator")) + 2);
+ }
+
+ fileHeader.setFileName(fileName);
+ fileHeader.setDirectory(fileName.endsWith("/") || fileName.endsWith("\\"));
+
+ } else {
+ fileHeader.setFileName(null);
+ }
+
+ //Extra field
+ readAndSaveExtraDataRecord(fileHeader);
+
+ //Read Zip64 Extra data records if exists
+ readAndSaveZip64ExtendedInfo(fileHeader);
+
+ //Read AES Extra Data record if exists
+ readAndSaveAESExtraDataRecord(fileHeader);
+
+// if (fileHeader.isEncrypted()) {
+//
+// if (fileHeader.getEncryptionMethod() == ZipConstants.ENC_METHOD_AES) {
+// //Do nothing
+// } else {
+// if ((firstByte & 64) == 64) {
+// //hardcoded for now
+// fileHeader.setEncryptionMethod(1);
+// } else {
+// fileHeader.setEncryptionMethod(ZipConstants.ENC_METHOD_STANDARD);
+// fileHeader.setCompressedSize(fileHeader.getCompressedSize()
+// - ZipConstants.STD_DEC_HDR_SIZE);
+// }
+// }
+//
+// }
+
+ if (fileCommentLength > 0) {
+ byte[] fileCommentBuf = new byte[fileCommentLength];
+ readIntoBuff(zip4jRaf, fileCommentBuf);
+ fileHeader.setFileComment(new String(fileCommentBuf));
+ }
+
+ fileHeaderList.add(fileHeader);
+ }
+ centralDirectory.setFileHeaders(fileHeaderList);
+
+ //Digital Signature
+ DigitalSignature digitalSignature = new DigitalSignature();
+ readIntoBuff(zip4jRaf, intBuff);
+ int signature = Raw.readIntLittleEndian(intBuff, 0);
+ if (signature != InternalZipConstants.DIGSIG) {
+ return centralDirectory;
+ }
+
+ digitalSignature.setHeaderSignature(signature);
+
+ //size of data
+ readIntoBuff(zip4jRaf, shortBuff);
+ int sizeOfData = Raw.readShortLittleEndian(shortBuff, 0);
+ digitalSignature.setSizeOfData(sizeOfData);
+
+ if (sizeOfData > 0) {
+ byte[] sigDataBuf = new byte[sizeOfData];
+ readIntoBuff(zip4jRaf, sigDataBuf);
+ digitalSignature.setSignatureData(new String(sigDataBuf));
+ }
+
+ return centralDirectory;
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Reads extra data record and saves it in the {@link FileHeader}
+ * @param fileHeader
+ * @throws ZipException
+ */
+ private void readAndSaveExtraDataRecord(FileHeader fileHeader) throws ZipException {
+
+ if (zip4jRaf == null) {
+ throw new ZipException("invalid file handler when trying to read extra data record");
+ }
+
+ if (fileHeader == null) {
+ throw new ZipException("file header is null");
+ }
+
+ int extraFieldLength = fileHeader.getExtraFieldLength();
+ if (extraFieldLength <= 0) {
+ return;
+ }
+
+ fileHeader.setExtraDataRecords(readExtraDataRecords(extraFieldLength));
+
+ }
+
+ /**
+ * Reads extra data record and saves it in the {@link LocalFileHeader}
+ * @param localFileHeader
+ * @throws ZipException
+ */
+ private void readAndSaveExtraDataRecord(LocalFileHeader localFileHeader) throws ZipException {
+
+ if (zip4jRaf == null) {
+ throw new ZipException("invalid file handler when trying to read extra data record");
+ }
+
+ if (localFileHeader == null) {
+ throw new ZipException("file header is null");
+ }
+
+ int extraFieldLength = localFileHeader.getExtraFieldLength();
+ if (extraFieldLength <= 0) {
+ return;
+ }
+
+ localFileHeader.setExtraDataRecords(readExtraDataRecords(extraFieldLength));
+
+ }
+
+ /**
+ * Reads extra data records
+ * @param extraFieldLength
+ * @return ArrayList of {@link ExtraDataRecord}
+ * @throws ZipException
+ */
+ private ArrayList readExtraDataRecords(int extraFieldLength) throws ZipException {
+
+ if (extraFieldLength <= 0) {
+ return null;
+ }
+
+ try {
+ byte[] extraFieldBuf = new byte[extraFieldLength];
+ zip4jRaf.read(extraFieldBuf);
+
+ int counter = 0;
+ ArrayList extraDataList = new ArrayList();
+ while(counter < extraFieldLength) {
+ ExtraDataRecord extraDataRecord = new ExtraDataRecord();
+ int header = Raw.readShortLittleEndian(extraFieldBuf, counter);
+ extraDataRecord.setHeader(header);
+ counter = counter + 2;
+ int sizeOfRec = Raw.readShortLittleEndian(extraFieldBuf, counter);
+
+ if ((2 + sizeOfRec) > extraFieldLength) {
+ sizeOfRec = Raw.readShortBigEndian(extraFieldBuf, counter);
+ if ((2 + sizeOfRec) > extraFieldLength) {
+ //If this is the case, then extra data record is corrupt
+ //skip reading any further extra data records
+ break;
+ }
+ }
+
+ extraDataRecord.setSizeOfData(sizeOfRec);
+ counter = counter + 2;
+
+ if (sizeOfRec > 0) {
+ byte[] data = new byte[sizeOfRec];
+ System.arraycopy(extraFieldBuf, counter, data, 0, sizeOfRec);
+ extraDataRecord.setData(data);
+ }
+ counter = counter + sizeOfRec;
+ extraDataList.add(extraDataRecord);
+ }
+ if (extraDataList.size() > 0) {
+ return extraDataList;
+ } else {
+ return null;
+ }
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Reads Zip64 End Of Central Directory Locator
+ * @return {@link Zip64EndCentralDirLocator}
+ * @throws ZipException
+ */
+ private Zip64EndCentralDirLocator readZip64EndCentralDirLocator() throws ZipException {
+
+ if (zip4jRaf == null) {
+ throw new ZipException("invalid file handler when trying to read Zip64EndCentralDirLocator");
+ }
+
+ try {
+ Zip64EndCentralDirLocator zip64EndCentralDirLocator = new Zip64EndCentralDirLocator();
+
+ setFilePointerToReadZip64EndCentralDirLoc();
+
+ byte[] intBuff = new byte[4];
+ byte[] longBuff = new byte[8];
+
+ readIntoBuff(zip4jRaf, intBuff);
+ int signature = Raw.readIntLittleEndian(intBuff, 0);
+ if (signature == InternalZipConstants.ZIP64ENDCENDIRLOC) {
+ zipModel.setZip64Format(true);
+ zip64EndCentralDirLocator.setSignature(signature);
+ } else {
+ zipModel.setZip64Format(false);
+ return null;
+ }
+
+ readIntoBuff(zip4jRaf, intBuff);
+ zip64EndCentralDirLocator.setNoOfDiskStartOfZip64EndOfCentralDirRec(
+ Raw.readIntLittleEndian(intBuff, 0));
+
+ readIntoBuff(zip4jRaf, longBuff);
+ zip64EndCentralDirLocator.setOffsetZip64EndOfCentralDirRec(
+ Raw.readLongLittleEndian(longBuff, 0));
+
+ readIntoBuff(zip4jRaf, intBuff);
+ zip64EndCentralDirLocator.setTotNumberOfDiscs(Raw.readIntLittleEndian(intBuff, 0));
+
+ return zip64EndCentralDirLocator;
+
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+
+ }
+
+ /**
+ * Reads Zip64 End of Central Directory Record
+ * @return {@link Zip64EndCentralDirRecord}
+ * @throws ZipException
+ */
+ private Zip64EndCentralDirRecord readZip64EndCentralDirRec() throws ZipException {
+
+ if (zipModel.getZip64EndCentralDirLocator() == null) {
+ throw new ZipException("invalid zip64 end of central directory locator");
+ }
+
+ long offSetStartOfZip64CentralDir =
+ zipModel.getZip64EndCentralDirLocator().getOffsetZip64EndOfCentralDirRec();
+
+ if (offSetStartOfZip64CentralDir < 0) {
+ throw new ZipException("invalid offset for start of end of central directory record");
+ }
+
+ try {
+ zip4jRaf.seek(offSetStartOfZip64CentralDir);
+
+ Zip64EndCentralDirRecord zip64EndCentralDirRecord = new Zip64EndCentralDirRecord();
+
+ byte[] shortBuff = new byte[2];
+ byte[] intBuff = new byte[4];
+ byte[] longBuff = new byte[8];
+
+ //signature
+ readIntoBuff(zip4jRaf, intBuff);
+ int signature = Raw.readIntLittleEndian(intBuff, 0);
+ if (signature != InternalZipConstants.ZIP64ENDCENDIRREC) {
+ throw new ZipException("invalid signature for zip64 end of central directory record");
+ }
+ zip64EndCentralDirRecord.setSignature(signature);
+
+ //size of zip64 end of central directory record
+ readIntoBuff(zip4jRaf, longBuff);
+ zip64EndCentralDirRecord.setSizeOfZip64EndCentralDirRec(
+ Raw.readLongLittleEndian(longBuff, 0));
+
+ //version made by
+ readIntoBuff(zip4jRaf, shortBuff);
+ zip64EndCentralDirRecord.setVersionMadeBy(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //version needed to extract
+ readIntoBuff(zip4jRaf, shortBuff);
+ zip64EndCentralDirRecord.setVersionNeededToExtract(Raw.readShortLittleEndian(shortBuff, 0));
+
+ //number of this disk
+ readIntoBuff(zip4jRaf, intBuff);
+ zip64EndCentralDirRecord.setNoOfThisDisk(Raw.readIntLittleEndian(intBuff, 0));
+
+ //number of the disk with the start of the central directory
+ readIntoBuff(zip4jRaf, intBuff);
+ zip64EndCentralDirRecord.setNoOfThisDiskStartOfCentralDir(
+ Raw.readIntLittleEndian(intBuff, 0));
+
+ //total number of entries in the central directory on this disk
+ readIntoBuff(zip4jRaf, longBuff);
+ zip64EndCentralDirRecord.setTotNoOfEntriesInCentralDirOnThisDisk(
+ Raw.readLongLittleEndian(longBuff, 0));
+
+ //total number of entries in the central directory
+ readIntoBuff(zip4jRaf, longBuff);
+ zip64EndCentralDirRecord.setTotNoOfEntriesInCentralDir(
+ Raw.readLongLittleEndian(longBuff, 0));
+
+ //size of the central directory
+ readIntoBuff(zip4jRaf, longBuff);
+ zip64EndCentralDirRecord.setSizeOfCentralDir(Raw.readLongLittleEndian(longBuff, 0));
+
+ //offset of start of central directory with respect to the starting disk number
+ readIntoBuff(zip4jRaf, longBuff);
+ zip64EndCentralDirRecord.setOffsetStartCenDirWRTStartDiskNo(
+ Raw.readLongLittleEndian(longBuff, 0));
+
+ //zip64 extensible data sector
+ //44 is the size of fixed variables in this record
+ long extDataSecSize = zip64EndCentralDirRecord.getSizeOfZip64EndCentralDirRec() - 44;
+ if (extDataSecSize > 0) {
+ byte[] extDataSecRecBuf = new byte[(int)extDataSecSize];
+ readIntoBuff(zip4jRaf, extDataSecRecBuf);
+ zip64EndCentralDirRecord.setExtensibleDataSector(extDataSecRecBuf);
+ }
+
+ return zip64EndCentralDirRecord;
+
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+
+ }
+
+ /**
+ * Reads Zip64 Extended info and saves it in the {@link FileHeader}
+ * @param fileHeader
+ * @throws ZipException
+ */
+ private void readAndSaveZip64ExtendedInfo(FileHeader fileHeader) throws ZipException {
+ if (fileHeader == null) {
+ throw new ZipException("file header is null in reading Zip64 Extended Info");
+ }
+
+ if (fileHeader.getExtraDataRecords() == null || fileHeader.getExtraDataRecords().size() <= 0) {
+ return;
+ }
+
+ Zip64ExtendedInfo zip64ExtendedInfo = readZip64ExtendedInfo(
+ fileHeader.getExtraDataRecords(),
+ fileHeader.getUncompressedSize(),
+ fileHeader.getCompressedSize(),
+ fileHeader.getOffsetLocalHeader(),
+ fileHeader.getDiskNumberStart());
+
+ if (zip64ExtendedInfo != null) {
+ fileHeader.setZip64ExtendedInfo(zip64ExtendedInfo);
+ if (zip64ExtendedInfo.getUnCompressedSize() != -1)
+ fileHeader.setUncompressedSize(zip64ExtendedInfo.getUnCompressedSize());
+
+ if (zip64ExtendedInfo.getCompressedSize() != -1)
+ fileHeader.setCompressedSize(zip64ExtendedInfo.getCompressedSize());
+
+ if (zip64ExtendedInfo.getOffsetLocalHeader() != -1)
+ fileHeader.setOffsetLocalHeader(zip64ExtendedInfo.getOffsetLocalHeader());
+
+ if (zip64ExtendedInfo.getDiskNumberStart() != -1)
+ fileHeader.setDiskNumberStart(zip64ExtendedInfo.getDiskNumberStart());
+ }
+ }
+
+ /**
+ * Reads Zip64 Extended Info and saves it in the {@link LocalFileHeader}
+ * @param localFileHeader
+ * @throws ZipException
+ */
+ private void readAndSaveZip64ExtendedInfo(LocalFileHeader localFileHeader) throws ZipException {
+ if (localFileHeader == null) {
+ throw new ZipException("file header is null in reading Zip64 Extended Info");
+ }
+
+ if (localFileHeader.getExtraDataRecords() == null || localFileHeader.getExtraDataRecords().size() <= 0) {
+ return;
+ }
+
+ Zip64ExtendedInfo zip64ExtendedInfo = readZip64ExtendedInfo(
+ localFileHeader.getExtraDataRecords(),
+ localFileHeader.getUncompressedSize(),
+ localFileHeader.getCompressedSize(),
+ -1, -1);
+
+ if (zip64ExtendedInfo != null) {
+ localFileHeader.setZip64ExtendedInfo(zip64ExtendedInfo);
+
+ if (zip64ExtendedInfo.getUnCompressedSize() != -1)
+ localFileHeader.setUncompressedSize(zip64ExtendedInfo.getUnCompressedSize());
+
+ if (zip64ExtendedInfo.getCompressedSize() != -1)
+ localFileHeader.setCompressedSize(zip64ExtendedInfo.getCompressedSize());
+ }
+ }
+
+ /**
+ * Reads Zip64 Extended Info
+ * @param extraDataRecords
+ * @param unCompressedSize
+ * @param compressedSize
+ * @param offsetLocalHeader
+ * @param diskNumberStart
+ * @return {@link Zip64ExtendedInfo}
+ * @throws ZipException
+ */
+ private Zip64ExtendedInfo readZip64ExtendedInfo(
+ ArrayList extraDataRecords,
+ long unCompressedSize,
+ long compressedSize,
+ long offsetLocalHeader,
+ int diskNumberStart) throws ZipException {
+
+ for (int i = 0; i < extraDataRecords.size(); i++) {
+ ExtraDataRecord extraDataRecord = (ExtraDataRecord)extraDataRecords.get(i);
+ if (extraDataRecord == null) {
+ continue;
+ }
+
+ if (extraDataRecord.getHeader() == 0x0001) {
+
+ Zip64ExtendedInfo zip64ExtendedInfo = new Zip64ExtendedInfo();
+
+ byte[] byteBuff = extraDataRecord.getData();
+
+ if (extraDataRecord.getSizeOfData() <= 0) {
+ break;
+ }
+ byte[] longByteBuff = new byte[8];
+ byte[] intByteBuff = new byte[4];
+ int counter = 0;
+ boolean valueAdded = false;
+
+ if (((unCompressedSize & 0xFFFF) == 0xFFFF) && counter < extraDataRecord.getSizeOfData()) {
+ System.arraycopy(byteBuff, counter, longByteBuff, 0, 8);
+ long val = Raw.readLongLittleEndian(longByteBuff, 0);
+ zip64ExtendedInfo.setUnCompressedSize(val);
+ counter += 8;
+ valueAdded = true;
+ }
+
+ if (((compressedSize & 0xFFFF) == 0xFFFF) && counter < extraDataRecord.getSizeOfData()) {
+ System.arraycopy(byteBuff, counter, longByteBuff, 0, 8);
+ long val = Raw.readLongLittleEndian(longByteBuff, 0);
+ zip64ExtendedInfo.setCompressedSize(val);
+ counter += 8;
+ valueAdded = true;
+ }
+
+ if (((offsetLocalHeader & 0xFFFF) == 0xFFFF) && counter < extraDataRecord.getSizeOfData()) {
+ System.arraycopy(byteBuff, counter, longByteBuff, 0, 8);
+ long val = Raw.readLongLittleEndian(longByteBuff, 0);
+ zip64ExtendedInfo.setOffsetLocalHeader(val);
+ counter += 8;
+ valueAdded = true;
+ }
+
+ if (((diskNumberStart & 0xFFFF) == 0xFFFF) && counter < extraDataRecord.getSizeOfData()) {
+ System.arraycopy(byteBuff, counter, intByteBuff, 0, 4);
+ int val = Raw.readIntLittleEndian(intByteBuff, 0);
+ zip64ExtendedInfo.setDiskNumberStart(val);
+ counter += 8;
+ valueAdded = true;
+ }
+
+ if (valueAdded) {
+ return zip64ExtendedInfo;
+ }
+
+ break;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the current random access file pointer at the start of signature
+ * of the zip64 end of central directory record
+ * @throws ZipException
+ */
+ private void setFilePointerToReadZip64EndCentralDirLoc() throws ZipException {
+ try {
+ byte[] ebs = new byte[4];
+ long pos = zip4jRaf.length() - InternalZipConstants.ENDHDR;
+
+ do {
+ zip4jRaf.seek(pos--);
+ } while (Raw.readLeInt(zip4jRaf, ebs) != InternalZipConstants.ENDSIG);
+
+ // Now the file pointer is at the end of signature of Central Dir Rec
+ // Seek back with the following values
+ // 4 -> end of central dir signature
+ // 4 -> total number of disks
+ // 8 -> relative offset of the zip64 end of central directory record
+ // 4 -> number of the disk with the start of the zip64 end of central directory
+ // 4 -> zip64 end of central dir locator signature
+ // Refer to Appnote for more information
+ //TODO: Donot harcorde these values. Make use of ZipConstants
+ zip4jRaf.seek(zip4jRaf.getFilePointer() - 4 - 4 - 8 - 4 - 4);
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Reads local file header for the given file header
+ * @param fileHeader
+ * @return {@link LocalFileHeader}
+ * @throws ZipException
+ */
+ public LocalFileHeader readLocalFileHeader(FileHeader fileHeader) throws ZipException {
+ if (fileHeader == null || zip4jRaf == null) {
+ throw new ZipException("invalid read parameters for local header");
+ }
+
+ long locHdrOffset = fileHeader.getOffsetLocalHeader();
+
+ if (fileHeader.getZip64ExtendedInfo() != null) {
+ Zip64ExtendedInfo zip64ExtendedInfo = fileHeader.getZip64ExtendedInfo();
+ if (zip64ExtendedInfo.getOffsetLocalHeader() > 0) {
+ locHdrOffset = fileHeader.getOffsetLocalHeader();
+ }
+ }
+
+ if (locHdrOffset < 0) {
+ throw new ZipException("invalid local header offset");
+ }
+
+ try {
+ zip4jRaf.seek(locHdrOffset);
+
+ int length = 0;
+ LocalFileHeader localFileHeader = new LocalFileHeader();
+
+ byte[] shortBuff = new byte[2];
+ byte[] intBuff = new byte[4];
+ byte[] longBuff = new byte[8];
+
+ //signature
+ readIntoBuff(zip4jRaf, intBuff);
+ int sig = Raw.readIntLittleEndian(intBuff, 0);
+ if (sig != InternalZipConstants.LOCSIG) {
+ throw new ZipException("invalid local header signature for file: " + fileHeader.getFileName());
+ }
+ localFileHeader.setSignature(sig);
+ length += 4;
+
+ //version needed to extract
+ readIntoBuff(zip4jRaf, shortBuff);
+ localFileHeader.setVersionNeededToExtract(Raw.readShortLittleEndian(shortBuff, 0));
+ length += 2;
+
+ //general purpose bit flag
+ readIntoBuff(zip4jRaf, shortBuff);
+ localFileHeader.setFileNameUTF8Encoded((Raw.readShortLittleEndian(shortBuff, 0) & InternalZipConstants.UFT8_NAMES_FLAG) != 0);
+ int firstByte = shortBuff[0];
+ int result = firstByte & 1;
+ if (result != 0) {
+ localFileHeader.setEncrypted(true);
+ }
+ localFileHeader.setGeneralPurposeFlag(shortBuff);
+ length += 2;
+
+ //Check if data descriptor exists for local file header
+ String binary = Integer.toBinaryString(firstByte);
+ if (binary.length() >= 4)
+ localFileHeader.setDataDescriptorExists(binary.charAt(3) == '1');
+
+ //compression method
+ readIntoBuff(zip4jRaf, shortBuff);
+ localFileHeader.setCompressionMethod(Raw.readShortLittleEndian(shortBuff, 0));
+ length += 2;
+
+ //last mod file time
+ readIntoBuff(zip4jRaf, intBuff);
+ localFileHeader.setLastModFileTime(Raw.readIntLittleEndian(intBuff, 0));
+ length += 4;
+
+ //crc-32
+ readIntoBuff(zip4jRaf, intBuff);
+ localFileHeader.setCrc32(Raw.readIntLittleEndian(intBuff, 0));
+ localFileHeader.setCrcBuff((byte[])intBuff.clone());
+ length += 4;
+
+ //compressed size
+ readIntoBuff(zip4jRaf, intBuff);
+ longBuff = getLongByteFromIntByte(intBuff);
+ localFileHeader.setCompressedSize(Raw.readLongLittleEndian(longBuff, 0));
+ length += 4;
+
+ //uncompressed size
+ readIntoBuff(zip4jRaf, intBuff);
+ longBuff = getLongByteFromIntByte(intBuff);
+ localFileHeader.setUncompressedSize(Raw.readLongLittleEndian(longBuff, 0));
+ length += 4;
+
+ //file name length
+ readIntoBuff(zip4jRaf, shortBuff);
+ int fileNameLength = Raw.readShortLittleEndian(shortBuff, 0);
+ localFileHeader.setFileNameLength(fileNameLength);
+ length += 2;
+
+ //extra field length
+ readIntoBuff(zip4jRaf, shortBuff);
+ int extraFieldLength = Raw.readShortLittleEndian(shortBuff, 0);
+ localFileHeader.setExtraFieldLength(extraFieldLength);
+ length += 2;
+
+ //file name
+ if (fileNameLength > 0) {
+ byte[] fileNameBuf = new byte[fileNameLength];
+ readIntoBuff(zip4jRaf, fileNameBuf);
+ // 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 = Zip4jUtil.decodeFileName(fileNameBuf, localFileHeader.isFileNameUTF8Encoded());
+
+ if (fileName == null) {
+ throw new ZipException("file name is null, cannot assign file name to local file header");
+ }
+
+ if (fileName.indexOf(":" + System.getProperty("file.separator")) >= 0) {
+ fileName = fileName.substring(fileName.indexOf(":" + System.getProperty("file.separator")) + 2);
+ }
+
+ localFileHeader.setFileName(fileName);
+ length += fileNameLength;
+ } else {
+ localFileHeader.setFileName(null);
+ }
+
+ //extra field
+ readAndSaveExtraDataRecord(localFileHeader);
+ length += extraFieldLength;
+
+ localFileHeader.setOffsetStartOfData(locHdrOffset + length);
+
+ //Copy password from fileHeader to localFileHeader
+ localFileHeader.setPassword(fileHeader.getPassword());
+
+ readAndSaveZip64ExtendedInfo(localFileHeader);
+
+ readAndSaveAESExtraDataRecord(localFileHeader);
+
+ if (localFileHeader.isEncrypted()) {
+
+ if (localFileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ //Do nothing
+ } else {
+ if ((firstByte & 64) == 64) {
+ //hardcoded for now
+ localFileHeader.setEncryptionMethod(1);
+ } else {
+ localFileHeader.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);
+// localFileHeader.setCompressedSize(localFileHeader.getCompressedSize()
+// - ZipConstants.STD_DEC_HDR_SIZE);
+ }
+ }
+
+ }
+
+ if (localFileHeader.getCrc32() <= 0) {
+ localFileHeader.setCrc32(fileHeader.getCrc32());
+ localFileHeader.setCrcBuff(fileHeader.getCrcBuff());
+ }
+
+ if (localFileHeader.getCompressedSize() <= 0) {
+ localFileHeader.setCompressedSize(fileHeader.getCompressedSize());
+ }
+
+ if (localFileHeader.getUncompressedSize() <= 0) {
+ localFileHeader.setUncompressedSize(fileHeader.getUncompressedSize());
+ }
+
+ return localFileHeader;
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Reads AES Extra Data Record and saves it in the {@link FileHeader}
+ * @param fileHeader
+ * @throws ZipException
+ */
+ private void readAndSaveAESExtraDataRecord(FileHeader fileHeader) throws ZipException {
+ if (fileHeader == null) {
+ throw new ZipException("file header is null in reading Zip64 Extended Info");
+ }
+
+ if (fileHeader.getExtraDataRecords() == null || fileHeader.getExtraDataRecords().size() <= 0) {
+ return;
+ }
+
+ AESExtraDataRecord aesExtraDataRecord = readAESExtraDataRecord(fileHeader.getExtraDataRecords());
+ if (aesExtraDataRecord != null) {
+ fileHeader.setAesExtraDataRecord(aesExtraDataRecord);
+ fileHeader.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
+ }
+ }
+
+ /**
+ * Reads AES Extra Data Record and saves it in the {@link LocalFileHeader}
+ * @param localFileHeader
+ * @throws ZipException
+ */
+ private void readAndSaveAESExtraDataRecord(LocalFileHeader localFileHeader) throws ZipException {
+ if (localFileHeader == null) {
+ throw new ZipException("file header is null in reading Zip64 Extended Info");
+ }
+
+ if (localFileHeader.getExtraDataRecords() == null || localFileHeader.getExtraDataRecords().size() <= 0) {
+ return;
+ }
+
+ AESExtraDataRecord aesExtraDataRecord = readAESExtraDataRecord(localFileHeader.getExtraDataRecords());
+ if (aesExtraDataRecord != null) {
+ localFileHeader.setAesExtraDataRecord(aesExtraDataRecord);
+ localFileHeader.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
+ }
+ }
+
+ /**
+ * Reads AES Extra Data Record
+ * @param extraDataRecords
+ * @return {@link AESExtraDataRecord}
+ * @throws ZipException
+ */
+ private AESExtraDataRecord readAESExtraDataRecord(ArrayList extraDataRecords) throws ZipException {
+
+ if (extraDataRecords == null) {
+ return null;
+ }
+
+ for (int i = 0; i < extraDataRecords.size(); i++) {
+ ExtraDataRecord extraDataRecord = (ExtraDataRecord)extraDataRecords.get(i);
+ if (extraDataRecord == null) {
+ continue;
+ }
+
+ if (extraDataRecord.getHeader() == InternalZipConstants.AESSIG) {
+
+ if (extraDataRecord.getData() == null) {
+ throw new ZipException("corrput AES extra data records");
+ }
+
+ AESExtraDataRecord aesExtraDataRecord = new AESExtraDataRecord();
+
+ aesExtraDataRecord.setSignature(InternalZipConstants.AESSIG);
+ aesExtraDataRecord.setDataSize(extraDataRecord.getSizeOfData());
+
+ byte[] aesData = extraDataRecord.getData();
+ aesExtraDataRecord.setVersionNumber(Raw.readShortLittleEndian(aesData, 0));
+ byte[] vendorIDBytes = new byte[2];
+ System.arraycopy(aesData, 2, vendorIDBytes, 0, 2);
+ aesExtraDataRecord.setVendorID(new String(vendorIDBytes));
+ aesExtraDataRecord.setAesStrength((int)(aesData[4] & 0xFF));
+ aesExtraDataRecord.setCompressionMethod(Raw.readShortLittleEndian(aesData, 5));
+
+ return aesExtraDataRecord;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Reads buf length of bytes from the input stream to buf
+ * @param zip4jRaf
+ * @param buf
+ * @return byte array
+ * @throws ZipException
+ */
+ private byte[] readIntoBuff(RandomAccessFile zip4jRaf, byte[] buf) throws ZipException {
+ try {
+ if (zip4jRaf.read(buf, 0, buf.length) != -1) {
+ return buf;
+ } else {
+ throw new ZipException("unexpected end of file when reading short buff");
+ }
+ } catch (IOException e) {
+ throw new ZipException("IOException when reading short buff", e);
+ }
+ }
+
+ /**
+ * Returns a long byte from an int byte by appending last 4 bytes as 0's
+ * @param intByte
+ * @return byte array
+ * @throws ZipException
+ */
+ private byte[] getLongByteFromIntByte(byte[] intByte) throws ZipException {
+ if (intByte == null) {
+ throw new ZipException("input parameter is null, cannot expand to 8 bytes");
+ }
+
+ if (intByte.length != 4) {
+ throw new ZipException("invalid byte length, cannot expand to 8 bytes");
+ }
+
+ byte[] longBuff = {intByte[0], intByte[1], intByte[2], intByte[3], 0, 0, 0, 0};
+ return longBuff;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/core/HeaderWriter.java b/harmony/src/main/java/com/zip4j/core/HeaderWriter.java
new file mode 100644
index 0000000..01fdddd
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/core/HeaderWriter.java
@@ -0,0 +1,977 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.core;
+
+import com.zip4j.io.SplitOutputStream;
+import com.zip4j.model.AESExtraDataRecord;
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.LocalFileHeader;
+import com.zip4j.model.Zip64EndCentralDirLocator;
+import com.zip4j.model.Zip64EndCentralDirRecord;
+import com.zip4j.model.ZipModel;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Raw;
+import com.zip4j.util.Zip4jUtil;
+
+public class HeaderWriter {
+
+ private final int ZIP64_EXTRA_BUF = 50;
+
+ public int writeLocalFileHeader(ZipModel zipModel, LocalFileHeader localFileHeader,
+ OutputStream outputStream) throws ZipException {
+ if (localFileHeader == null) {
+ throw new ZipException("input parameters are null, cannot write local file header");
+ }
+
+ try {
+ ArrayList byteArrayList = new ArrayList();
+
+ int headerLength = 0;
+
+ byte[] shortByte = new byte[2];
+ byte[] intByte = new byte[4];
+ byte[] longByte = new byte[8];
+ byte[] emptyLongByte = {0,0,0,0,0,0,0,0};
+
+ Raw.writeIntLittleEndian(intByte, 0, localFileHeader.getSignature());
+ copyByteArrayToArrayList(intByte, byteArrayList);
+ headerLength += 4;
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)localFileHeader.getVersionNeededToExtract());
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+ headerLength += 2;
+
+ //General Purpose bit flags
+ copyByteArrayToArrayList(localFileHeader.getGeneralPurposeFlag(), byteArrayList);
+ headerLength += 2;
+
+ //Compression Method
+ Raw.writeShortLittleEndian(shortByte, 0, (short)localFileHeader.getCompressionMethod());
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+ headerLength += 2;
+
+ //File modified time
+ int dateTime = localFileHeader.getLastModFileTime();
+ Raw.writeIntLittleEndian(intByte, 0, (int)dateTime);
+ copyByteArrayToArrayList(intByte, byteArrayList);
+ headerLength += 4;
+
+ //Skip crc for now - this field will be updated after data is compressed
+ Raw.writeIntLittleEndian(intByte, 0, (int)localFileHeader.getCrc32());
+ copyByteArrayToArrayList(intByte, byteArrayList);
+ headerLength += 4;
+
+ boolean writingZip64Rec = false;
+
+ //compressed & uncompressed size
+ long uncompressedSize = localFileHeader.getUncompressedSize();
+ if (uncompressedSize + ZIP64_EXTRA_BUF >= InternalZipConstants.ZIP_64_LIMIT) {
+ Raw.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_LIMIT);
+ System.arraycopy(longByte, 0, intByte, 0, 4);
+
+ //Set the uncompressed size to ZipConstants.ZIP_64_LIMIT as
+ //these values will be stored in Zip64 extra record
+ copyByteArrayToArrayList(intByte, byteArrayList);
+
+ copyByteArrayToArrayList(intByte, byteArrayList);
+ zipModel.setZip64Format(true);
+ writingZip64Rec = true;
+ localFileHeader.setWriteComprSizeInZip64ExtraRecord(true);
+ } else {
+ Raw.writeLongLittleEndian(longByte, 0, localFileHeader.getCompressedSize());
+ System.arraycopy(longByte, 0, intByte, 0, 4);
+ copyByteArrayToArrayList(intByte, byteArrayList);
+
+ Raw.writeLongLittleEndian(longByte, 0, localFileHeader.getUncompressedSize());
+ System.arraycopy(longByte, 0, intByte, 0, 4);
+ //Raw.writeIntLittleEndian(intByte, 0, (int)localFileHeader.getUncompressedSize());
+ copyByteArrayToArrayList(intByte, byteArrayList);
+
+ localFileHeader.setWriteComprSizeInZip64ExtraRecord(false);
+ }
+ headerLength += 8;
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)localFileHeader.getFileNameLength());
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+ headerLength += 2;
+
+ // extra field length
+ int extraFieldLength = 0;
+ if (writingZip64Rec) {
+ extraFieldLength += 20;
+ }
+ if (localFileHeader.getAesExtraDataRecord() != null) {
+ extraFieldLength += 11;
+ }
+ Raw.writeShortLittleEndian(shortByte, 0, (short)(extraFieldLength));
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+ headerLength += 2;
+
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getFileNameCharset())) {
+ byte[] fileNameBytes = localFileHeader.getFileName().getBytes(zipModel.getFileNameCharset());
+ copyByteArrayToArrayList(fileNameBytes, byteArrayList);
+ headerLength += fileNameBytes.length;
+ } else {
+ copyByteArrayToArrayList(Zip4jUtil.convertCharset(localFileHeader.getFileName()), byteArrayList);
+ headerLength += Zip4jUtil.getEncodedStringLength(localFileHeader.getFileName());
+ }
+
+ //Zip64 should be the first extra data record that should be written
+ //This is NOT according to any specification but if this is changed
+ //then take care of updateLocalFileHeader for compressed size
+ if (writingZip64Rec) {
+
+
+ //Zip64 header
+ Raw.writeShortLittleEndian(shortByte, 0, (short)InternalZipConstants.EXTRAFIELDZIP64LENGTH);
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+ headerLength += 2;
+
+ //Zip64 extra data record size
+ //hardcoded it to 16 for local file header as we will just write
+ //compressed and uncompressed file sizes
+ Raw.writeShortLittleEndian(shortByte, 0, (short)16);
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+ headerLength += 2;
+
+ //uncompressed size
+ Raw.writeLongLittleEndian(longByte, 0, localFileHeader.getUncompressedSize());
+ copyByteArrayToArrayList(longByte, byteArrayList);
+ headerLength += 8;
+
+ //set compressed size to 0 for now
+ copyByteArrayToArrayList(emptyLongByte, byteArrayList);
+ headerLength += 8;
+ }
+
+ if (localFileHeader.getAesExtraDataRecord() != null) {
+ AESExtraDataRecord aesExtraDataRecord = localFileHeader.getAesExtraDataRecord();
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getSignature());
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getDataSize());
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getVersionNumber());
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+
+ copyByteArrayToArrayList(aesExtraDataRecord.getVendorID().getBytes(), byteArrayList);
+
+ byte[] aesStrengthBytes = new byte[1];
+ aesStrengthBytes[0] = (byte)aesExtraDataRecord.getAesStrength();
+ copyByteArrayToArrayList(aesStrengthBytes, byteArrayList);
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getCompressionMethod());
+ copyByteArrayToArrayList(shortByte, byteArrayList);
+ }
+ byte[] lhBytes = byteArrayListToByteArray(byteArrayList);
+ outputStream.write(lhBytes);
+ return lhBytes.length;
+ } catch (ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ public int writeExtendedLocalHeader(LocalFileHeader localFileHeader,
+ OutputStream outputStream) throws ZipException, IOException {
+ if (localFileHeader == null || outputStream == null) {
+ throw new ZipException("input parameters is null, cannot write extended local header");
+ }
+
+ ArrayList byteArrayList = new ArrayList();
+ byte[] intByte = new byte[4];
+
+ //Extended local file header signature
+ Raw.writeIntLittleEndian(intByte, 0, (int)InternalZipConstants.EXTSIG);
+ copyByteArrayToArrayList(intByte, byteArrayList);
+
+ //CRC
+ Raw.writeIntLittleEndian(intByte, 0, (int)localFileHeader.getCrc32());
+ copyByteArrayToArrayList(intByte, byteArrayList);
+
+ //compressed size
+ long compressedSize = localFileHeader.getCompressedSize();
+ if (compressedSize >= Integer.MAX_VALUE) {
+ compressedSize = Integer.MAX_VALUE;
+ }
+ Raw.writeIntLittleEndian(intByte, 0, (int)compressedSize);
+ copyByteArrayToArrayList(intByte, byteArrayList);
+
+ //uncompressed size
+ long uncompressedSize = localFileHeader.getUncompressedSize();
+ if (uncompressedSize >= Integer.MAX_VALUE) {
+ uncompressedSize = Integer.MAX_VALUE;
+ }
+ Raw.writeIntLittleEndian(intByte, 0, (int)uncompressedSize);
+ copyByteArrayToArrayList(intByte, byteArrayList);
+
+ byte[] extLocHdrBytes = byteArrayListToByteArray(byteArrayList);
+ outputStream.write(extLocHdrBytes);
+ return extLocHdrBytes.length;
+ }
+
+ /**
+ * Processes zip header data and writes this data to the zip file
+ * @param zipModel
+ * @param outputStream
+ * @throws ZipException
+ */
+ public void finalizeZipFile(ZipModel zipModel,
+ OutputStream outputStream) throws ZipException {
+ if (zipModel == null || outputStream == null) {
+ throw new ZipException("input parameters is null, cannot finalize zip file");
+ }
+
+ try {
+ processHeaderData(zipModel, outputStream);
+
+ long offsetCentralDir = zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir();
+
+ List headerBytesList = new ArrayList();
+
+ int sizeOfCentralDir = writeCentralDirectory(zipModel, outputStream, headerBytesList);
+
+ if (zipModel.isZip64Format()) {
+ if (zipModel.getZip64EndCentralDirRecord() == null) {
+ zipModel.setZip64EndCentralDirRecord(new Zip64EndCentralDirRecord());
+ }
+ if (zipModel.getZip64EndCentralDirLocator() == null) {
+ zipModel.setZip64EndCentralDirLocator(new Zip64EndCentralDirLocator());
+ }
+
+ zipModel.getZip64EndCentralDirLocator().setOffsetZip64EndOfCentralDirRec(offsetCentralDir + sizeOfCentralDir);
+ if (outputStream instanceof SplitOutputStream) {
+ zipModel.getZip64EndCentralDirLocator().setNoOfDiskStartOfZip64EndOfCentralDirRec(((SplitOutputStream)outputStream).getCurrSplitFileCounter());
+ zipModel.getZip64EndCentralDirLocator().setTotNumberOfDiscs(((SplitOutputStream)outputStream).getCurrSplitFileCounter() + 1);
+ } else {
+ zipModel.getZip64EndCentralDirLocator().setNoOfDiskStartOfZip64EndOfCentralDirRec(0);
+ zipModel.getZip64EndCentralDirLocator().setTotNumberOfDiscs(1);
+ }
+
+ writeZip64EndOfCentralDirectoryRecord(zipModel, outputStream, sizeOfCentralDir, offsetCentralDir, headerBytesList);
+
+ writeZip64EndOfCentralDirectoryLocator(zipModel, outputStream, headerBytesList);
+ }
+
+ writeEndOfCentralDirectoryRecord(zipModel, outputStream, sizeOfCentralDir, offsetCentralDir, headerBytesList);
+
+ writeZipHeaderBytes(zipModel, outputStream, byteArrayListToByteArray(headerBytesList));
+ } catch (ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Processes zip header data and writes this data to the zip file without any validations.
+ * This process is not intended to use for normal operations (adding, deleting, etc) of a zip file.
+ * This method is used when certain validations need to be skipped (ex: Merging split zip files,
+ * adding comment to a zip file, etc)
+ * @param zipModel
+ * @param outputStream
+ * @throws ZipException
+ */
+ public void finalizeZipFileWithoutValidations(ZipModel zipModel, OutputStream outputStream) throws ZipException {
+ if (zipModel == null || outputStream == null) {
+ throw new ZipException("input parameters is null, cannot finalize zip file without validations");
+ }
+
+ try {
+
+ List headerBytesList = new ArrayList();
+
+ long offsetCentralDir = zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir();
+
+ int sizeOfCentralDir = writeCentralDirectory(zipModel, outputStream, headerBytesList);
+
+ if (zipModel.isZip64Format()) {
+ if (zipModel.getZip64EndCentralDirRecord() == null) {
+ zipModel.setZip64EndCentralDirRecord(new Zip64EndCentralDirRecord());
+ }
+ if (zipModel.getZip64EndCentralDirLocator() == null) {
+ zipModel.setZip64EndCentralDirLocator(new Zip64EndCentralDirLocator());
+ }
+
+ zipModel.getZip64EndCentralDirLocator().setOffsetZip64EndOfCentralDirRec(offsetCentralDir + sizeOfCentralDir);
+
+ writeZip64EndOfCentralDirectoryRecord(zipModel, outputStream, sizeOfCentralDir, offsetCentralDir, headerBytesList);
+ writeZip64EndOfCentralDirectoryLocator(zipModel, outputStream, headerBytesList);
+ }
+
+ writeEndOfCentralDirectoryRecord(zipModel, outputStream, sizeOfCentralDir, offsetCentralDir, headerBytesList);
+
+ writeZipHeaderBytes(zipModel, outputStream, byteArrayListToByteArray(headerBytesList));
+ } catch(ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Writes the zip header data to the zip file
+ * @param outputStream
+ * @param buff
+ * @throws ZipException
+ */
+ private void writeZipHeaderBytes(ZipModel zipModel, OutputStream outputStream, byte[] buff) throws ZipException {
+ if (buff == null) {
+ throw new ZipException("invalid buff to write as zip headers");
+ }
+
+ try {
+ if (outputStream instanceof SplitOutputStream) {
+ if (((SplitOutputStream)outputStream).checkBuffSizeAndStartNextSplitFile(buff.length)) {
+ finalizeZipFile(zipModel, outputStream);
+ return;
+ }
+ }
+
+ outputStream.write(buff);
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Fills the header data in the zip model
+ * @param zipModel
+ * @param outputStream
+ * @throws ZipException
+ */
+ private void processHeaderData(ZipModel zipModel, OutputStream outputStream) throws ZipException {
+ try {
+ int currSplitFileCounter = 0;
+ if (outputStream instanceof SplitOutputStream) {
+ zipModel.getEndCentralDirRecord().setOffsetOfStartOfCentralDir(
+ ((SplitOutputStream)outputStream).getFilePointer());
+ currSplitFileCounter = ((SplitOutputStream)outputStream).getCurrSplitFileCounter();
+
+ }
+
+ if (zipModel.isZip64Format()) {
+ if (zipModel.getZip64EndCentralDirRecord() == null) {
+ zipModel.setZip64EndCentralDirRecord(new Zip64EndCentralDirRecord());
+ }
+ if (zipModel.getZip64EndCentralDirLocator() == null) {
+ zipModel.setZip64EndCentralDirLocator(new Zip64EndCentralDirLocator());
+ }
+
+ zipModel.getZip64EndCentralDirLocator().setNoOfDiskStartOfZip64EndOfCentralDirRec(currSplitFileCounter);
+ zipModel.getZip64EndCentralDirLocator().setTotNumberOfDiscs(currSplitFileCounter + 1);
+ }
+ zipModel.getEndCentralDirRecord().setNoOfThisDisk(currSplitFileCounter);
+ zipModel.getEndCentralDirRecord().setNoOfThisDiskStartOfCentralDir(currSplitFileCounter);
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Writes central directory header data to an array list
+ * @param zipModel
+ * @param outputStream
+ * @param headerBytesList
+ * @return size of central directory
+ * @throws ZipException
+ */
+ private int writeCentralDirectory(ZipModel zipModel,
+ OutputStream outputStream, List headerBytesList) throws ZipException {
+ if (zipModel == null || outputStream == null) {
+ throw new ZipException("input parameters is null, cannot write central directory");
+ }
+
+ if (zipModel.getCentralDirectory() == null ||
+ zipModel.getCentralDirectory().getFileHeaders() == null ||
+ zipModel.getCentralDirectory().getFileHeaders().size() <= 0) {
+ return 0;
+ }
+
+ int sizeOfCentralDir = 0;
+ for (int i = 0; i < zipModel.getCentralDirectory().getFileHeaders().size(); i++) {
+ FileHeader fileHeader = (FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i);
+ int sizeOfFileHeader = writeFileHeader(zipModel, fileHeader, outputStream, headerBytesList);
+ sizeOfCentralDir += sizeOfFileHeader;
+ }
+ return sizeOfCentralDir;
+ }
+
+ private int writeFileHeader(ZipModel zipModel, FileHeader fileHeader,
+ OutputStream outputStream, List headerBytesList) throws ZipException {
+
+ if (fileHeader == null || outputStream == null) {
+ throw new ZipException("input parameters is null, cannot write local file header");
+ }
+
+ try {
+ int sizeOfFileHeader = 0;
+
+ byte[] shortByte = new byte[2];
+ byte[] intByte = new byte[4];
+ byte[] longByte = new byte[8];
+ final byte[] emptyShortByte = {0,0};
+ final byte[] emptyIntByte = {0,0,0,0};
+
+ boolean writeZip64FileSize = false;
+ boolean writeZip64OffsetLocalHeader = false;
+
+ Raw.writeIntLittleEndian(intByte, 0, fileHeader.getSignature());
+ copyByteArrayToArrayList(intByte, headerBytesList);
+ sizeOfFileHeader += 4;
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)fileHeader.getVersionMadeBy());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)fileHeader.getVersionNeededToExtract());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ copyByteArrayToArrayList(fileHeader.getGeneralPurposeFlag(), headerBytesList);
+ sizeOfFileHeader += 2;
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)fileHeader.getCompressionMethod());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ int dateTime = fileHeader.getLastModFileTime();
+ Raw.writeIntLittleEndian(intByte, 0, dateTime);
+ copyByteArrayToArrayList(intByte, headerBytesList);
+ sizeOfFileHeader += 4;
+
+ Raw.writeIntLittleEndian(intByte, 0, (int)(fileHeader.getCrc32()));
+ copyByteArrayToArrayList(intByte, headerBytesList);
+ sizeOfFileHeader += 4;
+
+ if (fileHeader.getCompressedSize() >= InternalZipConstants.ZIP_64_LIMIT ||
+ fileHeader.getUncompressedSize() + ZIP64_EXTRA_BUF >= InternalZipConstants.ZIP_64_LIMIT) {
+ Raw.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_LIMIT);
+ System.arraycopy(longByte, 0, intByte, 0, 4);
+
+ copyByteArrayToArrayList(intByte, headerBytesList);
+ sizeOfFileHeader += 4;
+
+ copyByteArrayToArrayList(intByte, headerBytesList);
+ sizeOfFileHeader += 4;
+
+ writeZip64FileSize = true;
+ } else {
+ Raw.writeLongLittleEndian(longByte, 0, fileHeader.getCompressedSize());
+ System.arraycopy(longByte, 0, intByte, 0, 4);
+// Raw.writeIntLittleEndian(intByte, 0, (int)fileHeader.getCompressedSize());
+ copyByteArrayToArrayList(intByte, headerBytesList);
+ sizeOfFileHeader += 4;
+
+ Raw.writeLongLittleEndian(longByte, 0, fileHeader.getUncompressedSize());
+ System.arraycopy(longByte, 0, intByte, 0, 4);
+// Raw.writeIntLittleEndian(intByte, 0, (int)fileHeader.getUncompressedSize());
+ copyByteArrayToArrayList(intByte, headerBytesList);
+ sizeOfFileHeader += 4;
+ }
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)fileHeader.getFileNameLength());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ //Compute offset bytes before extra field is written for Zip64 compatibility
+ //NOTE: this data is not written now, but written at a later point
+ byte[] offsetLocalHeaderBytes = new byte[4];
+ if (fileHeader.getOffsetLocalHeader() > InternalZipConstants.ZIP_64_LIMIT) {
+ Raw.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_LIMIT);
+ System.arraycopy(longByte, 0, offsetLocalHeaderBytes, 0, 4);
+ writeZip64OffsetLocalHeader = true;
+ } else {
+ Raw.writeLongLittleEndian(longByte, 0, fileHeader.getOffsetLocalHeader());
+ System.arraycopy(longByte, 0, offsetLocalHeaderBytes, 0, 4);
+ }
+
+ // extra field length
+ int extraFieldLength = 0;
+ if (writeZip64FileSize || writeZip64OffsetLocalHeader) {
+ extraFieldLength += 4;
+ if (writeZip64FileSize)
+ extraFieldLength += 16;
+ if (writeZip64OffsetLocalHeader)
+ extraFieldLength += 8;
+ }
+ if (fileHeader.getAesExtraDataRecord() != null) {
+ extraFieldLength += 11;
+ }
+ Raw.writeShortLittleEndian(shortByte, 0, (short)(extraFieldLength));
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ //Skip file comment length for now
+ copyByteArrayToArrayList(emptyShortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ //Skip disk number start for now
+ Raw.writeShortLittleEndian(shortByte, 0, (short)(fileHeader.getDiskNumberStart()));
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ //Skip internal file attributes for now
+ copyByteArrayToArrayList(emptyShortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ //External file attributes
+ if (fileHeader.getExternalFileAttr() != null) {
+ copyByteArrayToArrayList(fileHeader.getExternalFileAttr(), headerBytesList);
+ } else {
+ copyByteArrayToArrayList(emptyIntByte, headerBytesList);
+ }
+ sizeOfFileHeader += 4;
+
+ //offset local header
+ //this data is computed above
+ copyByteArrayToArrayList(offsetLocalHeaderBytes, headerBytesList);
+ sizeOfFileHeader += 4;
+
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getFileNameCharset())) {
+ byte[] fileNameBytes = fileHeader.getFileName().getBytes(zipModel.getFileNameCharset());
+ copyByteArrayToArrayList(fileNameBytes, headerBytesList);
+ sizeOfFileHeader += fileNameBytes.length;
+ } else {
+ copyByteArrayToArrayList(Zip4jUtil.convertCharset(fileHeader.getFileName()), headerBytesList);
+ sizeOfFileHeader += Zip4jUtil.getEncodedStringLength(fileHeader.getFileName());
+ }
+
+ if (writeZip64FileSize || writeZip64OffsetLocalHeader) {
+ zipModel.setZip64Format(true);
+
+ //Zip64 header
+ Raw.writeShortLittleEndian(shortByte, 0, (short)InternalZipConstants.EXTRAFIELDZIP64LENGTH);
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ //Zip64 extra data record size
+ int dataSize = 0;
+
+ if (writeZip64FileSize) {
+ dataSize += 16;
+ }
+ if (writeZip64OffsetLocalHeader) {
+ dataSize += 8;
+ }
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)dataSize);
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ sizeOfFileHeader += 2;
+
+ if (writeZip64FileSize) {
+ Raw.writeLongLittleEndian(longByte, 0, fileHeader.getUncompressedSize());
+ copyByteArrayToArrayList(longByte, headerBytesList);
+ sizeOfFileHeader += 8;
+
+ Raw.writeLongLittleEndian(longByte, 0, fileHeader.getCompressedSize());
+ copyByteArrayToArrayList(longByte, headerBytesList);
+ sizeOfFileHeader += 8;
+ }
+
+ if (writeZip64OffsetLocalHeader) {
+ Raw.writeLongLittleEndian(longByte, 0, fileHeader.getOffsetLocalHeader());
+ copyByteArrayToArrayList(longByte, headerBytesList);
+ sizeOfFileHeader += 8;
+ }
+ }
+
+ if (fileHeader.getAesExtraDataRecord() != null) {
+ AESExtraDataRecord aesExtraDataRecord = fileHeader.getAesExtraDataRecord();
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getSignature());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getDataSize());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getVersionNumber());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+
+ copyByteArrayToArrayList(aesExtraDataRecord.getVendorID().getBytes(), headerBytesList);
+
+ byte[] aesStrengthBytes = new byte[1];
+ aesStrengthBytes[0] = (byte)aesExtraDataRecord.getAesStrength();
+ copyByteArrayToArrayList(aesStrengthBytes, headerBytesList);
+
+ Raw.writeShortLittleEndian(shortByte, 0, (short)aesExtraDataRecord.getCompressionMethod());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+
+ sizeOfFileHeader += 11;
+ }
+
+// outputStream.write(byteArrayListToByteArray(headerBytesList));
+
+ return sizeOfFileHeader;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void writeZip64EndOfCentralDirectoryRecord(ZipModel zipModel,
+ OutputStream outputStream, int sizeOfCentralDir,
+ long offsetCentralDir, List headerBytesList) throws ZipException {
+ if (zipModel == null || outputStream == null) {
+ throw new ZipException("zip model or output stream is null, cannot write zip64 end of central directory record");
+ }
+
+ try {
+
+ byte[] shortByte = new byte[2];
+ byte[] emptyShortByte = {0,0};
+ byte[] intByte = new byte[4];
+ byte[] longByte = new byte[8];
+
+ //zip64 end of central dir signature
+ Raw.writeIntLittleEndian(intByte, 0, (int)InternalZipConstants.ZIP64ENDCENDIRREC);
+ copyByteArrayToArrayList(intByte, headerBytesList);
+
+ //size of zip64 end of central directory record
+ Raw.writeLongLittleEndian(longByte, 0, (long)44);
+ copyByteArrayToArrayList(longByte, headerBytesList);
+
+ //version made by
+ //version needed to extract
+ if (zipModel.getCentralDirectory() != null &&
+ zipModel.getCentralDirectory().getFileHeaders() != null &&
+ zipModel.getCentralDirectory().getFileHeaders().size() > 0) {
+ Raw.writeShortLittleEndian(shortByte, 0,
+ (short)((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(0)).getVersionMadeBy());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+
+ Raw.writeShortLittleEndian(shortByte, 0,
+ (short)((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(0)).getVersionNeededToExtract());
+ copyByteArrayToArrayList(shortByte, headerBytesList);
+ } else {
+ copyByteArrayToArrayList(emptyShortByte, headerBytesList);
+ copyByteArrayToArrayList(emptyShortByte, headerBytesList);
+ }
+
+ //number of this disk
+ Raw.writeIntLittleEndian(intByte, 0, zipModel.getEndCentralDirRecord().getNoOfThisDisk());
+ copyByteArrayToArrayList(intByte, headerBytesList);
+
+ //number of the disk with start of central directory
+ Raw.writeIntLittleEndian(intByte, 0, zipModel.getEndCentralDirRecord().getNoOfThisDiskStartOfCentralDir());
+ copyByteArrayToArrayList(intByte, headerBytesList);
+
+ //total number of entries in the central directory on this disk
+ int numEntries = 0;
+ int numEntriesOnThisDisk = 0;
+ if (zipModel.getCentralDirectory() == null ||
+ zipModel.getCentralDirectory().getFileHeaders() == null) {
+ throw new ZipException("invalid central directory/file headers, " +
+ "cannot write end of central directory record");
+ } else {
+ numEntries = zipModel.getCentralDirectory().getFileHeaders().size();
+ if (zipModel.isSplitArchive()) {
+ countNumberOfFileHeaderEntriesOnDisk(zipModel.getCentralDirectory().getFileHeaders(),
+ zipModel.getEndCentralDirRecord().getNoOfThisDisk());
+ } else {
+ numEntriesOnThisDisk = numEntries;
+ }
+ }
+ Raw.writeLongLittleEndian(longByte, 0, numEntriesOnThisDisk);
+ copyByteArrayToArrayList(longByte, headerBytesList);
+
+ //Total number of entries in central directory
+ Raw.writeLongLittleEndian(longByte, 0, numEntries);
+ copyByteArrayToArrayList(longByte, headerBytesList);
+
+ //Size of central directory
+ Raw.writeLongLittleEndian(longByte, 0, sizeOfCentralDir);
+ copyByteArrayToArrayList(longByte, headerBytesList);
+
+ //offset of start of central directory with respect to the starting disk number
+ Raw.writeLongLittleEndian(longByte, 0, offsetCentralDir);
+ copyByteArrayToArrayList(longByte, headerBytesList);
+
+ } catch (ZipException zipException) {
+ throw zipException;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void writeZip64EndOfCentralDirectoryLocator(ZipModel zipModel,
+ OutputStream outputStream, List headerBytesList) throws ZipException {
+ if (zipModel == null || outputStream == null) {
+ throw new ZipException("zip model or output stream is null, cannot write zip64 end of central directory locator");
+ }
+
+ try {
+
+ byte[] intByte = new byte[4];
+ byte[] longByte = new byte[8];
+
+ //zip64 end of central dir locator signature
+ Raw.writeIntLittleEndian(intByte, 0, (int)InternalZipConstants.ZIP64ENDCENDIRLOC);
+ copyByteArrayToArrayList(intByte, headerBytesList);
+
+ //number of the disk with the start of the zip64 end of central directory
+ Raw.writeIntLittleEndian(intByte, 0, zipModel.getZip64EndCentralDirLocator().getNoOfDiskStartOfZip64EndOfCentralDirRec());
+ copyByteArrayToArrayList(intByte, headerBytesList);
+
+ //relative offset of the zip64 end of central directory record
+ Raw.writeLongLittleEndian(longByte, 0, zipModel.getZip64EndCentralDirLocator().getOffsetZip64EndOfCentralDirRec());
+ copyByteArrayToArrayList(longByte, headerBytesList);
+
+ //total number of disks
+ Raw.writeIntLittleEndian(intByte, 0, zipModel.getZip64EndCentralDirLocator().getTotNumberOfDiscs());
+ copyByteArrayToArrayList(intByte, headerBytesList);
+ } catch (ZipException zipException) {
+ throw zipException;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void writeEndOfCentralDirectoryRecord(ZipModel zipModel,
+ OutputStream outputStream,
+ int sizeOfCentralDir,
+ long offsetCentralDir,
+ List headrBytesList) throws ZipException {
+ if (zipModel == null || outputStream == null) {
+ throw new ZipException("zip model or output stream is null, cannot write end of central directory record");
+ }
+
+ try {
+
+ byte[] shortByte = new byte[2];
+ byte[] intByte = new byte[4];
+ byte[] longByte = new byte[8];
+
+ //End of central directory signature
+ Raw.writeIntLittleEndian(intByte, 0, (int)zipModel.getEndCentralDirRecord().getSignature());
+ copyByteArrayToArrayList(intByte, headrBytesList);
+
+ //number of this disk
+ Raw.writeShortLittleEndian(shortByte, 0, (short)(zipModel.getEndCentralDirRecord().getNoOfThisDisk()));
+ copyByteArrayToArrayList(shortByte, headrBytesList);
+
+ //number of the disk with start of central directory
+ Raw.writeShortLittleEndian(shortByte, 0, (short)(zipModel.getEndCentralDirRecord().getNoOfThisDiskStartOfCentralDir()));
+ copyByteArrayToArrayList(shortByte, headrBytesList);
+
+ //Total number of entries in central directory on this disk
+ int numEntries = 0;
+ int numEntriesOnThisDisk = 0;
+ if (zipModel.getCentralDirectory() == null ||
+ zipModel.getCentralDirectory().getFileHeaders() == null) {
+ throw new ZipException("invalid central directory/file headers, " +
+ "cannot write end of central directory record");
+ } else {
+ numEntries = zipModel.getCentralDirectory().getFileHeaders().size();
+ if (zipModel.isSplitArchive()) {
+ numEntriesOnThisDisk = countNumberOfFileHeaderEntriesOnDisk(zipModel.getCentralDirectory().getFileHeaders(),
+ zipModel.getEndCentralDirRecord().getNoOfThisDisk());
+ } else {
+ numEntriesOnThisDisk = numEntries;
+ }
+ }
+ Raw.writeShortLittleEndian(shortByte, 0, (short)numEntriesOnThisDisk);
+ copyByteArrayToArrayList(shortByte, headrBytesList);
+
+ //Total number of entries in central directory
+ Raw.writeShortLittleEndian(shortByte, 0, (short)numEntries);
+ copyByteArrayToArrayList(shortByte, headrBytesList);
+
+ //Size of central directory
+ Raw.writeIntLittleEndian(intByte, 0, sizeOfCentralDir);
+ copyByteArrayToArrayList(intByte, headrBytesList);
+
+ //Offset central directory
+ if (offsetCentralDir > InternalZipConstants.ZIP_64_LIMIT) {
+ Raw.writeLongLittleEndian(longByte, 0, InternalZipConstants.ZIP_64_LIMIT);
+ System.arraycopy(longByte, 0, intByte, 0, 4);
+ copyByteArrayToArrayList(intByte, headrBytesList);
+ } else {
+ Raw.writeLongLittleEndian(longByte, 0, offsetCentralDir);
+ System.arraycopy(longByte, 0, intByte, 0, 4);
+// Raw.writeIntLittleEndian(intByte, 0, (int)offsetCentralDir);
+ copyByteArrayToArrayList(intByte, headrBytesList);
+ }
+
+ //Zip File comment length
+ int commentLength = 0;
+ if (zipModel.getEndCentralDirRecord().getComment() != null) {
+ commentLength = zipModel.getEndCentralDirRecord().getCommentLength();
+ }
+ Raw.writeShortLittleEndian(shortByte, 0, (short)commentLength);
+ copyByteArrayToArrayList(shortByte, headrBytesList);
+
+ //Comment
+ if (commentLength > 0) {
+ copyByteArrayToArrayList(zipModel.getEndCentralDirRecord().getCommentBytes(), headrBytesList);
+ }
+
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ public void updateLocalFileHeader(LocalFileHeader localFileHeader, long offset,
+ int toUpdate, ZipModel zipModel, byte[] bytesToWrite, int noOfDisk, SplitOutputStream outputStream) throws ZipException {
+ if (localFileHeader == null || offset < 0 || zipModel == null) {
+ throw new ZipException("invalid input parameters, cannot update local file header");
+ }
+
+ try {
+ boolean closeFlag = false;
+ SplitOutputStream currOutputStream = null;
+
+ if (noOfDisk != outputStream.getCurrSplitFileCounter()) {
+ File zipFile = new File(zipModel.getZipFile());
+ String parentFile = zipFile.getParent();
+ String fileNameWithoutExt = Zip4jUtil.getZipFileNameWithoutExt(zipFile.getName());
+ String fileName = parentFile + System.getProperty("file.separator");
+ if (noOfDisk < 9) {
+ fileName += fileNameWithoutExt + ".z0" + (noOfDisk + 1);
+ } else {
+ fileName += fileNameWithoutExt + ".z" + (noOfDisk + 1);
+ }
+ currOutputStream = new SplitOutputStream(new File(fileName));
+ closeFlag = true;
+ } else {
+ currOutputStream = outputStream;
+ }
+
+ long currOffset = currOutputStream.getFilePointer();
+
+ if (currOutputStream == null) {
+ throw new ZipException("invalid output stream handler, cannot update local file header");
+ }
+
+ switch (toUpdate) {
+ case InternalZipConstants.UPDATE_LFH_CRC:
+ currOutputStream.seek(offset + toUpdate);
+ currOutputStream.write(bytesToWrite);
+ break;
+ case InternalZipConstants.UPDATE_LFH_COMP_SIZE:
+ case InternalZipConstants.UPDATE_LFH_UNCOMP_SIZE:
+ updateCompressedSizeInLocalFileHeader(currOutputStream, localFileHeader,
+ offset, toUpdate, bytesToWrite, zipModel.isZip64Format());
+ break;
+ default:
+ break;
+ }
+ if (closeFlag) {
+ currOutputStream.close();
+ } else {
+ outputStream.seek(currOffset);
+ }
+
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void updateCompressedSizeInLocalFileHeader(SplitOutputStream outputStream, LocalFileHeader localFileHeader,
+ long offset, long toUpdate, byte[] bytesToWrite, boolean isZip64Format) throws ZipException {
+
+ if (outputStream == null) {
+ throw new ZipException("invalid output stream, cannot update compressed size for local file header");
+ }
+
+ try {
+ if (localFileHeader.isWriteComprSizeInZip64ExtraRecord()) {
+ if (bytesToWrite.length != 8) {
+ throw new ZipException("attempting to write a non 8-byte compressed size block for a zip64 file");
+ }
+
+ //4 - compressed size
+ //4 - uncomprssed size
+ //2 - file name length
+ //2 - extra field length
+ //file name length
+ //2 - Zip64 signature
+ //2 - size of zip64 data
+ //8 - crc
+ //8 - compressed size
+ //8 - uncompressed size
+ long zip64CompressedSizeOffset = offset + toUpdate + 4 + 4 + 2 + 2 + localFileHeader.getFileNameLength() + 2 + 2 + 8;
+ if (toUpdate == InternalZipConstants.UPDATE_LFH_UNCOMP_SIZE) {
+ zip64CompressedSizeOffset += 8;
+ }
+ outputStream.seek(zip64CompressedSizeOffset);
+ outputStream.write(bytesToWrite);
+ } else {
+ outputStream.seek(offset + toUpdate);
+ outputStream.write(bytesToWrite);
+ }
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+
+ }
+
+ private void copyByteArrayToArrayList(byte[] byteArray, List arrayList) throws ZipException {
+ if (arrayList == null || byteArray == null) {
+ throw new ZipException("one of the input parameters is null, cannot copy byte array to array list");
+ }
+
+ for (int i = 0; i < byteArray.length; i++) {
+ arrayList.add(Byte.toString(byteArray[i]));
+ }
+ }
+
+ private byte[] byteArrayListToByteArray(List arrayList) throws ZipException {
+ if (arrayList == null) {
+ throw new ZipException("input byte array list is null, cannot conver to byte array");
+ }
+
+ if (arrayList.size() <= 0) {
+ return null;
+ }
+
+ byte[] retBytes = new byte[arrayList.size()];
+
+ for (int i = 0; i < arrayList.size(); i++) {
+ retBytes[i] = Byte.parseByte((String)arrayList.get(i));
+ }
+
+ return retBytes;
+ }
+
+ private int countNumberOfFileHeaderEntriesOnDisk(ArrayList fileHeaders,
+ int numOfDisk) throws ZipException {
+ if (fileHeaders == null) {
+ throw new ZipException("file headers are null, cannot calculate number of entries on this disk");
+ }
+
+ int noEntries = 0;
+ for (int i = 0; i < fileHeaders.size(); i++) {
+ FileHeader fileHeader = (FileHeader)fileHeaders.get(i);
+ if (fileHeader.getDiskNumberStart() == numOfDisk) {
+ noEntries++;
+ }
+ }
+ return noEntries;
+ }
+
+}
\ No newline at end of file
diff --git a/harmony/src/main/java/com/zip4j/core/ZipFile.java b/harmony/src/main/java/com/zip4j/core/ZipFile.java
new file mode 100644
index 0000000..a9383c4
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/core/ZipFile.java
@@ -0,0 +1,1042 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.core;
+
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.UnzipParameters;
+import com.zip4j.model.ZipModel;
+import com.zip4j.model.ZipParameters;
+import com.zip4j.zip.ZipEngine;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.exception.ZipExceptionConstants;
+import com.zip4j.io.ZipInputStream;
+import com.zip4j.progress.ProgressMonitor;
+import com.zip4j.unzip.Unzip;
+import com.zip4j.util.ArchiveMaintainer;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Zip4jUtil;
+
+/**
+ * Base class to handle zip files. Some of the operations supported
+ * in this class are:
+ *
+ * - Create Zip File
+ * - Add files to zip file
+ * - Add folder to zip file
+ * - Extract files from zip files
+ * - Remove files from zip file
+ *
+ *
+ */
+
+public class ZipFile {
+
+ private String file;
+ private int mode;
+ private ZipModel zipModel;
+ private boolean isEncrypted;
+ private ProgressMonitor progressMonitor;
+ private boolean runInThread;
+ private String fileNameCharset;
+
+ /**
+ * Creates a new Zip File Object with the given zip file path.
+ * If the zip file does not exist, it is not created at this point.
+ * @param zipFile
+ * @throws ZipException
+ */
+ public ZipFile(String zipFile) throws ZipException {
+ this(new File(zipFile));
+ }
+
+ /**
+ * Creates a new Zip File Object with the input file.
+ * If the zip file does not exist, it is not created at this point.
+ * @param zipFile
+ * @throws ZipException
+ */
+ public ZipFile(File zipFile) throws ZipException {
+ if (zipFile == null) {
+ throw new ZipException("Input zip file parameter is not null",
+ ZipExceptionConstants.inputZipParamIsNull);
+ }
+
+ this.file = zipFile.getPath();
+ this.mode = InternalZipConstants.MODE_UNZIP;
+ this.progressMonitor = new ProgressMonitor();
+ this.runInThread = false;
+ }
+
+ /**
+ * Creates a zip file and adds the source file to the zip file. If the zip file
+ * exists then this method throws an exception. Parameters such as compression type, etc
+ * can be set in the input parameters
+ * @param sourceFile - File to be added to the zip file
+ * @param parameters - parameters to create the zip file
+ * @throws ZipException
+ */
+ public void createZipFile(File sourceFile, ZipParameters parameters) throws ZipException {
+ ArrayList sourceFileList = new ArrayList();
+ sourceFileList.add(sourceFile);
+ createZipFile(sourceFileList, parameters, false, -1);
+ }
+
+ /**
+ * Creates a zip file and adds the source file to the zip file. If the zip file
+ * exists then this method throws an exception. Parameters such as compression type, etc
+ * can be set in the input parameters. While the method addFile/addFiles also creates the
+ * zip file if it does not exist, the main functionality of this method is to create a split
+ * zip file. To create a split zip file, set the splitArchive parameter to true with a valid
+ * splitLength. Split Length has to be more than 65536 bytes
+ * @param sourceFile - File to be added to the zip file
+ * @param parameters - parameters to create the zip file
+ * @param splitArchive - if archive has to be split or not
+ * @param splitLength - if archive has to be split, then length in bytes at which it has to be split
+ * @throws ZipException
+ */
+ public void createZipFile(File sourceFile, ZipParameters parameters,
+ boolean splitArchive, long splitLength) throws ZipException {
+
+ ArrayList sourceFileList = new ArrayList();
+ sourceFileList.add(sourceFile);
+ createZipFile(sourceFileList, parameters, splitArchive, splitLength);
+ }
+
+ /**
+ * Creates a zip file and adds the list of source file(s) to the zip file. If the zip file
+ * exists then this method throws an exception. Parameters such as compression type, etc
+ * can be set in the input parameters
+ * @param sourceFileList - File to be added to the zip file
+ * @param parameters - parameters to create the zip file
+ * @throws ZipException
+ */
+ public void createZipFile(ArrayList sourceFileList,
+ ZipParameters parameters) throws ZipException {
+ createZipFile(sourceFileList, parameters, false, -1);
+ }
+
+ /**
+ * Creates a zip file and adds the list of source file(s) to the zip file. If the zip file
+ * exists then this method throws an exception. Parameters such as compression type, etc
+ * can be set in the input parameters. While the method addFile/addFiles also creates the
+ * zip file if it does not exist, the main functionality of this method is to create a split
+ * zip file. To create a split zip file, set the splitArchive parameter to true with a valid
+ * splitLength. Split Length has to be more than 65536 bytes
+ * @param sourceFileList - File to be added to the zip file
+ * @param parameters - zip parameters for this file list
+ * @param splitArchive - if archive has to be split or not
+ * @param splitLength - if archive has to be split, then length in bytes at which it has to be split
+ * @throws ZipException
+ */
+ public void createZipFile(ArrayList sourceFileList, ZipParameters parameters,
+ boolean splitArchive, long splitLength) throws ZipException {
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(file)) {
+ throw new ZipException("zip file path is empty");
+ }
+
+ if (Zip4jUtil.checkFileExists(file)) {
+ throw new ZipException("zip file: " + file + " already exists. To add files to existing zip file use addFile method");
+ }
+
+ if (sourceFileList == null) {
+ throw new ZipException("input file ArrayList is null, cannot create zip file");
+ }
+
+ if (!Zip4jUtil.checkArrayListTypes(sourceFileList, InternalZipConstants.LIST_TYPE_FILE)) {
+ throw new ZipException("One or more elements in the input ArrayList is not of type File");
+ }
+
+ createNewZipModel();
+ this.zipModel.setSplitArchive(splitArchive);
+ this.zipModel.setSplitLength(splitLength);
+ addFiles(sourceFileList, parameters);
+ }
+
+ /**
+ * Creates a zip file and adds the files/folders from the specified folder to the zip file.
+ * This method does the same functionality as in addFolder method except that this method
+ * can also create split zip files when adding a folder. To create a split zip file, set the
+ * splitArchive parameter to true and specify the splitLength. Split length has to be more than
+ * or equal to 65536 bytes. Note that this method throws an exception if the zip file already
+ * exists.
+ * @param folderToAdd
+ * @param parameters
+ * @param splitArchive
+ * @param splitLength
+ * @throws ZipException
+ */
+ public void createZipFileFromFolder(String folderToAdd, ZipParameters parameters,
+ boolean splitArchive, long splitLength) throws ZipException {
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(folderToAdd)) {
+ throw new ZipException("folderToAdd is empty or null, cannot create Zip File from folder");
+ }
+
+ createZipFileFromFolder(new File(folderToAdd), parameters, splitArchive, splitLength);
+
+ }
+
+ /**
+ * Creates a zip file and adds the files/folders from the specified folder to the zip file.
+ * This method does the same functionality as in addFolder method except that this method
+ * can also create split zip files when adding a folder. To create a split zip file, set the
+ * splitArchive parameter to true and specify the splitLength. Split length has to be more than
+ * or equal to 65536 bytes. Note that this method throws an exception if the zip file already
+ * exists.
+ * @param folderToAdd
+ * @param parameters
+ * @param splitArchive
+ * @param splitLength
+ * @throws ZipException
+ */
+ public void createZipFileFromFolder(File folderToAdd, ZipParameters parameters,
+ boolean splitArchive, long splitLength) throws ZipException {
+
+ if (folderToAdd == null) {
+ throw new ZipException("folderToAdd is null, cannot create zip file from folder");
+ }
+
+ if (parameters == null) {
+ throw new ZipException("input parameters are null, cannot create zip file from folder");
+ }
+
+ if (Zip4jUtil.checkFileExists(file)) {
+ throw new ZipException("zip file: " + file + " already exists. To add files to existing zip file use addFolder method");
+ }
+
+ createNewZipModel();
+ this.zipModel.setSplitArchive(splitArchive);
+ if (splitArchive)
+ this.zipModel.setSplitLength(splitLength);
+
+ addFolder(folderToAdd, parameters, false);
+ }
+
+ /**
+ * Adds input source file to the zip file. If zip file does not exist, then
+ * this method creates a new zip file. Parameters such as compression type, etc
+ * can be set in the input parameters.
+ * @param sourceFile - File to tbe added to the zip file
+ * @param parameters - zip parameters for this file
+ * @throws ZipException
+ */
+ public void addFile(File sourceFile, ZipParameters parameters) throws ZipException {
+ ArrayList sourceFileList = new ArrayList();
+ sourceFileList.add(sourceFile);
+ addFiles(sourceFileList, parameters);
+ }
+
+ /**
+ * Adds the list of input files to the zip file. If zip file does not exist, then
+ * this method creates a new zip file. Parameters such as compression type, etc
+ * can be set in the input parameters.
+ * @param sourceFileList
+ * @param parameters
+ * @throws ZipException
+ */
+ public void addFiles(ArrayList sourceFileList, ZipParameters parameters) throws ZipException {
+
+ checkZipModel();
+
+ if (this.zipModel == null) {
+ throw new ZipException("internal error: zip model is null");
+ }
+
+ if (sourceFileList == null) {
+ throw new ZipException("input file ArrayList is null, cannot add files");
+ }
+
+ if (!Zip4jUtil.checkArrayListTypes(sourceFileList, InternalZipConstants.LIST_TYPE_FILE)) {
+ throw new ZipException("One or more elements in the input ArrayList is not of type File");
+ }
+
+ if (parameters == null) {
+ throw new ZipException("input parameters are null, cannot add files to zip");
+ }
+
+ if (progressMonitor.getState() == ProgressMonitor.STATE_BUSY) {
+ throw new ZipException("invalid operation - Zip4j is in busy state");
+ }
+
+ if (Zip4jUtil.checkFileExists(file)) {
+ if (zipModel.isSplitArchive()) {
+ throw new ZipException("Zip file already exists. Zip file format does not allow updating split/spanned files");
+ }
+ }
+
+ ZipEngine zipEngine = new ZipEngine(zipModel);
+ zipEngine.addFiles(sourceFileList, parameters, progressMonitor, runInThread);
+ }
+
+ /**
+ * Adds the folder in the given path to the zip file. If zip file does not exist,
+ * then a new zip file is created. If input folder path is invalid then an exception
+ * is thrown. Zip parameters for the files in the folder to be added can be set in
+ * the input parameters
+ * @param path
+ * @param parameters
+ * @throws ZipException
+ */
+ public void addFolder(String path, ZipParameters parameters) throws ZipException {
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(path)) {
+ throw new ZipException("input path is null or empty, cannot add folder to zip file");
+ }
+
+ addFolder(new File(path), parameters);
+ }
+
+ /**
+ * Adds the folder in the given file object to the zip file. If zip file does not exist,
+ * then a new zip file is created. If input folder is invalid then an exception
+ * is thrown. Zip parameters for the files in the folder to be added can be set in
+ * the input parameters
+ * @param path
+ * @param parameters
+ * @throws ZipException
+ */
+ public void addFolder(File path, ZipParameters parameters) throws ZipException {
+ if (path == null) {
+ throw new ZipException("input path is null, cannot add folder to zip file");
+ }
+
+ if (parameters == null) {
+ throw new ZipException("input parameters are null, cannot add folder to zip file");
+ }
+
+ addFolder(path, parameters, true);
+ }
+
+ /**
+ * Internal method to add a folder to the zip file.
+ * @param path
+ * @param parameters
+ * @param checkSplitArchive
+ * @throws ZipException
+ */
+ private void addFolder(File path, ZipParameters parameters,
+ boolean checkSplitArchive) throws ZipException {
+
+ checkZipModel();
+
+ if (this.zipModel == null) {
+ throw new ZipException("internal error: zip model is null");
+ }
+
+ if (checkSplitArchive) {
+ if (this.zipModel.isSplitArchive()) {
+ throw new ZipException("This is a split archive. Zip file format does not allow updating split/spanned files");
+ }
+ }
+
+ ZipEngine zipEngine = new ZipEngine(zipModel);
+ zipEngine.addFolderToZip(path, parameters, progressMonitor, runInThread);
+
+ }
+
+ /**
+ * Creates a new entry in the zip file and adds the content of the inputstream to the
+ * zip file. ZipParameters.isSourceExternalStream and ZipParameters.fileNameInZip have to be
+ * set before in the input parameters. If the file name ends with / or \, this method treats the
+ * content as a directory. Setting the flag ProgressMonitor.setRunInThread to true will have
+ * no effect for this method and hence this method cannot be used to add content to zip in
+ * thread mode
+ * @param inputStream
+ * @param parameters
+ * @throws ZipException
+ */
+ public void addStream(InputStream inputStream, ZipParameters parameters) throws ZipException {
+ if (inputStream == null) {
+ throw new ZipException("inputstream is null, cannot add file to zip");
+ }
+
+ if (parameters == null) {
+ throw new ZipException("zip parameters are null");
+ }
+
+ this.setRunInThread(false);
+
+ checkZipModel();
+
+ if (this.zipModel == null) {
+ throw new ZipException("internal error: zip model is null");
+ }
+
+ if (Zip4jUtil.checkFileExists(file)) {
+ if (zipModel.isSplitArchive()) {
+ throw new ZipException("Zip file already exists. Zip file format does not allow updating split/spanned files");
+ }
+ }
+
+ ZipEngine zipEngine = new ZipEngine(zipModel);
+ zipEngine.addStreamToZip(inputStream, parameters);
+ }
+
+ /**
+ * Reads the zip header information for this zip file. If the zip file
+ * does not exist, then this method throws an exception.
+ * Note: This method does not read local file header information
+ * @throws ZipException
+ */
+ private void readZipInfo() throws ZipException {
+
+ if (!Zip4jUtil.checkFileExists(file)) {
+ throw new ZipException("zip file does not exist");
+ }
+
+ if (!Zip4jUtil.checkFileReadAccess(this.file)) {
+ throw new ZipException("no read access for the input zip file");
+ }
+
+ if (this.mode != InternalZipConstants.MODE_UNZIP) {
+ throw new ZipException("Invalid mode");
+ }
+
+ RandomAccessFile raf = null;
+ try {
+ raf = new RandomAccessFile(new File(file), InternalZipConstants.READ_MODE);
+
+ if (zipModel == null) {
+
+ HeaderReader headerReader = new HeaderReader(raf);
+ zipModel = headerReader.readAllHeaders(this.fileNameCharset);
+ if (zipModel != null) {
+ zipModel.setZipFile(file);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ } finally {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Extracts all the files in the given zip file to the input destination path.
+ * If zip file does not exist or destination path is invalid then an
+ * exception is thrown.
+ * @param destPath
+ * @throws ZipException
+ */
+ public void extractAll(String destPath) throws ZipException {
+ extractAll(destPath, null);
+
+ }
+
+ /**
+ * Extracts all the files in the given zip file to the input destination path.
+ * If zip file does not exist or destination path is invalid then an
+ * exception is thrown.
+ * @param destPath
+ * @param unzipParameters
+ * @throws ZipException
+ */
+ public void extractAll(String destPath,
+ UnzipParameters unzipParameters) throws ZipException {
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(destPath)) {
+ throw new ZipException("output path is null or invalid");
+ }
+
+ if (!Zip4jUtil.checkOutputFolder(destPath)) {
+ throw new ZipException("invalid output path");
+ }
+
+ if (zipModel == null) {
+ readZipInfo();
+ }
+
+ // Throw an exception if zipModel is still null
+ if (zipModel == null) {
+ throw new ZipException("Internal error occurred when extracting zip file");
+ }
+
+ if (progressMonitor.getState() == ProgressMonitor.STATE_BUSY) {
+ throw new ZipException("invalid operation - Zip4j is in busy state");
+ }
+
+ Unzip unzip = new Unzip(zipModel);
+ unzip.extractAll(unzipParameters, destPath, progressMonitor, runInThread);
+
+ }
+
+ /**
+ * Extracts a specific file from the zip file to the destination path.
+ * If destination path is invalid, then this method throws an exception.
+ * @param fileHeader
+ * @param destPath
+ * @throws ZipException
+ */
+ public void extractFile(FileHeader fileHeader, String destPath) throws ZipException {
+ extractFile(fileHeader, destPath, null);
+ }
+
+ /**
+ * Extracts a specific file from the zip file to the destination path.
+ * If destination path is invalid, then this method throws an exception.
+ *
+ * If newFileName is not null or empty, newly created file name will be replaced by
+ * the value in newFileName. If this value is null, then the file name will be the
+ * value in FileHeader.getFileName
+ * @param fileHeader
+ * @param destPath
+ * @param unzipParameters
+ * @throws ZipException
+ */
+ public void extractFile(FileHeader fileHeader,
+ String destPath, UnzipParameters unzipParameters) throws ZipException {
+ extractFile(fileHeader, destPath, unzipParameters, null);
+ }
+
+ /**
+ * Extracts a specific file from the zip file to the destination path.
+ * If destination path is invalid, then this method throws an exception.
+ * @param fileHeader
+ * @param destPath
+ * @param unzipParameters
+ * @param newFileName
+ * @throws ZipException
+ */
+ public void extractFile(FileHeader fileHeader, String destPath,
+ UnzipParameters unzipParameters, String newFileName) throws ZipException {
+
+ if (fileHeader == null) {
+ throw new ZipException("input file header is null, cannot extract file");
+ }
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(destPath)) {
+ throw new ZipException("destination path is empty or null, cannot extract file");
+ }
+
+ readZipInfo();
+
+ if (progressMonitor.getState() == ProgressMonitor.STATE_BUSY) {
+ throw new ZipException("invalid operation - Zip4j is in busy state");
+ }
+
+ fileHeader.extractFile(zipModel, destPath, unzipParameters, newFileName, progressMonitor, runInThread);
+
+ }
+
+ /**
+ * Extracts a specific file from the zip file to the destination path.
+ * This method first finds the necessary file header from the input file name.
+ *
+ * File name is relative file name in the zip file. For example if a zip file contains
+ * a file "a.txt", then to extract this file, input file name has to be "a.txt". Another
+ * example is if there is a file "b.txt" in a folder "abc" in the zip file, then the
+ * input file name has to be abc/b.txt
+ *
+ * Throws an exception if file header could not be found for the given file name or if
+ * the destination path is invalid
+ * @param fileName
+ * @param destPath
+ * @throws ZipException
+ */
+ public void extractFile(String fileName, String destPath) throws ZipException {
+ extractFile(fileName, destPath, null);
+ }
+
+ /**
+ * Extracts a specific file from the zip file to the destination path.
+ * This method first finds the necessary file header from the input file name.
+ *
+ * File name is relative file name in the zip file. For example if a zip file contains
+ * a file "a.txt", then to extract this file, input file name has to be "a.txt". Another
+ * example is if there is a file "b.txt" in a folder "abc" in the zip file, then the
+ * input file name has to be abc/b.txt
+ *
+ * Throws an exception if file header could not be found for the given file name or if
+ * the destination path is invalid
+ * @param fileName
+ * @param destPath
+ * @param unzipParameters
+ * @throws ZipException
+ */
+ public void extractFile(String fileName,
+ String destPath, UnzipParameters unzipParameters) throws ZipException {
+ extractFile(fileName, destPath, unzipParameters, null);
+ }
+
+ /**
+ * Extracts a specific file from the zip file to the destination path.
+ * This method first finds the necessary file header from the input file name.
+ *
+ * File name is relative file name in the zip file. For example if a zip file contains
+ * a file "a.txt", then to extract this file, input file name has to be "a.txt". Another
+ * example is if there is a file "b.txt" in a folder "abc" in the zip file, then the
+ * input file name has to be abc/b.txt
+ *
+ * If newFileName is not null or empty, newly created file name will be replaced by
+ * the value in newFileName. If this value is null, then the file name will be the
+ * value in FileHeader.getFileName
+ *
+ * Throws an exception if file header could not be found for the given file name or if
+ * the destination path is invalid
+ * @param fileName
+ * @param destPath
+ * @param unzipParameters
+ * @param newFileName
+ * @throws ZipException
+ */
+ public void extractFile(String fileName, String destPath,
+ UnzipParameters unzipParameters, String newFileName) throws ZipException {
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(fileName)) {
+ throw new ZipException("file to extract is null or empty, cannot extract file");
+ }
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(destPath)) {
+ throw new ZipException("destination string path is empty or null, cannot extract file");
+ }
+
+ readZipInfo();
+
+ FileHeader fileHeader = Zip4jUtil.getFileHeader(zipModel, fileName);
+
+ if (fileHeader == null) {
+ throw new ZipException("file header not found for given file name, cannot extract file");
+ }
+
+ if (progressMonitor.getState() == ProgressMonitor.STATE_BUSY) {
+ throw new ZipException("invalid operation - Zip4j is in busy state");
+ }
+
+ fileHeader.extractFile(zipModel, destPath, unzipParameters, newFileName, progressMonitor, runInThread);
+
+ }
+
+ /**
+ * Sets the password for the zip file.
+ * Note: For security reasons, usage of this method is discouraged. Use
+ * setPassword(char[]) instead. As strings are immutable, they cannot be wiped
+ * out from memory explicitly after usage. Therefore, usage of Strings to store
+ * passwords is discouraged. More info here:
+ * http://docs.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#PBEEx
+ * @param password
+ * @throws ZipException
+ */
+ public void setPassword(String password) throws ZipException {
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(password)) {
+ throw new NullPointerException();
+ }
+ setPassword(password.toCharArray());
+ }
+
+ /**
+ * Sets the password for the zip file
+ * @param password
+ * @throws ZipException
+ */
+ public void setPassword(char[] password) throws ZipException {
+ if (zipModel == null) {
+ readZipInfo();
+ if (zipModel == null) {
+ throw new ZipException("Zip Model is null");
+ }
+ }
+
+ if (zipModel.getCentralDirectory() == null || zipModel.getCentralDirectory().getFileHeaders() == null) {
+ throw new ZipException("invalid zip file");
+ }
+
+ for (int i = 0; i < zipModel.getCentralDirectory().getFileHeaders().size(); i++) {
+ if (zipModel.getCentralDirectory().getFileHeaders().get(i) != null) {
+ if (((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).isEncrypted()) {
+ ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).setPassword(password);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the list of file headers in the zip file. Throws an exception if the
+ * zip file does not exist
+ * @return list of file headers
+ * @throws ZipException
+ */
+ public List getFileHeaders() throws ZipException {
+ readZipInfo();
+ if (zipModel == null || zipModel.getCentralDirectory() == null) {
+ return null;
+ }
+ return zipModel.getCentralDirectory().getFileHeaders();
+ }
+
+ /**
+ * Returns FileHeader if a file header with the given fileHeader
+ * string exists in the zip model: If not returns null
+ * @param fileName
+ * @return FileHeader
+ * @throws ZipException
+ */
+ public FileHeader getFileHeader(String fileName) throws ZipException {
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(fileName)) {
+ throw new ZipException("input file name is emtpy or null, cannot get FileHeader");
+ }
+
+ readZipInfo();
+ if (zipModel == null || zipModel.getCentralDirectory() == null) {
+ return null;
+ }
+
+ return Zip4jUtil.getFileHeader(zipModel, fileName);
+ }
+
+ /**
+ * Checks to see if the zip file is encrypted
+ * @return true if encrypted, false if not
+ * @throws ZipException
+ */
+ public boolean isEncrypted() throws ZipException {
+ if (zipModel == null) {
+ readZipInfo();
+ if (zipModel == null) {
+ throw new ZipException("Zip Model is null");
+ }
+ }
+
+ if (zipModel.getCentralDirectory() == null || zipModel.getCentralDirectory().getFileHeaders() == null) {
+ throw new ZipException("invalid zip file");
+ }
+
+ ArrayList fileHeaderList = zipModel.getCentralDirectory().getFileHeaders();
+ for (int i = 0; i < fileHeaderList.size(); i++) {
+ FileHeader fileHeader = (FileHeader)fileHeaderList.get(i);
+ if (fileHeader != null) {
+ if (fileHeader.isEncrypted()) {
+ isEncrypted = true;
+ break;
+ }
+ }
+ }
+
+ return isEncrypted;
+ }
+
+ /**
+ * Checks if the zip file is a split archive
+ * @return true if split archive, false if not
+ * @throws ZipException
+ */
+ public boolean isSplitArchive() throws ZipException {
+
+ if (zipModel == null) {
+ readZipInfo();
+ if (zipModel == null) {
+ throw new ZipException("Zip Model is null");
+ }
+ }
+
+ return zipModel.isSplitArchive();
+
+ }
+
+ /**
+ * Removes the file provided in the input paramters from the zip file.
+ * This method first finds the file header and then removes the file.
+ * If file does not exist, then this method throws an exception.
+ * If zip file is a split zip file, then this method throws an exception as
+ * zip specification does not allow for updating split zip archives.
+ * @param fileName
+ * @throws ZipException
+ */
+ public void removeFile(String fileName) throws ZipException {
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(fileName)) {
+ throw new ZipException("file name is empty or null, cannot remove file");
+ }
+
+ if (zipModel == null) {
+ if (Zip4jUtil.checkFileExists(file)) {
+ readZipInfo();
+ }
+ }
+
+ if (zipModel.isSplitArchive()) {
+ throw new ZipException("Zip file format does not allow updating split/spanned files");
+ }
+
+ FileHeader fileHeader = Zip4jUtil.getFileHeader(zipModel, fileName);
+ if (fileHeader == null) {
+ throw new ZipException("could not find file header for file: " + fileName);
+ }
+
+ removeFile(fileHeader);
+ }
+
+ /**
+ * Removes the file provided in the input file header from the zip file.
+ * If zip file is a split zip file, then this method throws an exception as
+ * zip specification does not allow for updating split zip archives.
+ * @param fileHeader
+ * @throws ZipException
+ */
+ public void removeFile(FileHeader fileHeader) throws ZipException {
+ if (fileHeader == null) {
+ throw new ZipException("file header is null, cannot remove file");
+ }
+
+ if (zipModel == null) {
+ if (Zip4jUtil.checkFileExists(file)) {
+ readZipInfo();
+ }
+ }
+
+ if (zipModel.isSplitArchive()) {
+ throw new ZipException("Zip file format does not allow updating split/spanned files");
+ }
+
+ ArchiveMaintainer archiveMaintainer = new ArchiveMaintainer();
+ archiveMaintainer.initProgressMonitorForRemoveOp(zipModel, fileHeader, progressMonitor);
+ archiveMaintainer.removeZipFile(zipModel, fileHeader, progressMonitor, runInThread);
+ }
+
+ /**
+ * Merges split zip files into a single zip file without the need to extract the
+ * files in the archive
+ * @param outputZipFile
+ * @throws ZipException
+ */
+ public void mergeSplitFiles(File outputZipFile) throws ZipException {
+ if (outputZipFile == null) {
+ throw new ZipException("outputZipFile is null, cannot merge split files");
+ }
+
+ if (outputZipFile.exists()) {
+ throw new ZipException("output Zip File already exists");
+ }
+
+ checkZipModel();
+
+ if (this.zipModel == null) {
+ throw new ZipException("zip model is null, corrupt zip file?");
+ }
+
+ ArchiveMaintainer archiveMaintainer = new ArchiveMaintainer();
+ archiveMaintainer.initProgressMonitorForMergeOp(zipModel, progressMonitor);
+ archiveMaintainer.mergeSplitZipFiles(zipModel, outputZipFile, progressMonitor, runInThread);
+ }
+
+ /**
+ * Sets comment for the Zip file
+ * @param comment
+ * @throws ZipException
+ */
+ public void setComment(String comment) throws ZipException {
+ if (comment == null) {
+ throw new ZipException("input comment is null, cannot update zip file");
+ }
+
+ if (!Zip4jUtil.checkFileExists(file)) {
+ throw new ZipException("zip file does not exist, cannot set comment for zip file");
+ }
+
+ readZipInfo();
+
+ if (this.zipModel == null) {
+ throw new ZipException("zipModel is null, cannot update zip file");
+ }
+
+ if (zipModel.getEndCentralDirRecord() == null) {
+ throw new ZipException("end of central directory is null, cannot set comment");
+ }
+
+ ArchiveMaintainer archiveMaintainer = new ArchiveMaintainer();
+ archiveMaintainer.setComment(zipModel, comment);
+ }
+
+ /**
+ * Returns the comment set for the Zip file
+ * @return String
+ * @throws ZipException
+ */
+ public String getComment() throws ZipException {
+ return getComment(null);
+ }
+
+ /**
+ * Returns the comment set for the Zip file in the input encoding
+ * @param encoding
+ * @return String
+ * @throws ZipException
+ */
+ public String getComment(String encoding) throws ZipException {
+ if (encoding == null) {
+ if (Zip4jUtil.isSupportedCharset(InternalZipConstants.CHARSET_COMMENTS_DEFAULT)) {
+ encoding = InternalZipConstants.CHARSET_COMMENTS_DEFAULT;
+ } else {
+ encoding = InternalZipConstants.CHARSET_DEFAULT;
+ }
+ }
+
+ if (Zip4jUtil.checkFileExists(file)) {
+ checkZipModel();
+ } else {
+ throw new ZipException("zip file does not exist, cannot read comment");
+ }
+
+ if (this.zipModel == null) {
+ throw new ZipException("zip model is null, cannot read comment");
+ }
+
+ if (this.zipModel.getEndCentralDirRecord() == null) {
+ throw new ZipException("end of central directory record is null, cannot read comment");
+ }
+
+ if (this.zipModel.getEndCentralDirRecord().getCommentBytes() == null ||
+ this.zipModel.getEndCentralDirRecord().getCommentBytes().length <= 0) {
+ return null;
+ }
+
+ try {
+ return new String(this.zipModel.getEndCentralDirRecord().getCommentBytes(), encoding);
+ } catch (UnsupportedEncodingException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Loads the zip model if zip model is null and if zip file exists.
+ * @throws ZipException
+ */
+ private void checkZipModel() throws ZipException {
+ if (this.zipModel == null) {
+ if (Zip4jUtil.checkFileExists(file)) {
+ readZipInfo();
+ } else {
+ createNewZipModel();
+ }
+ }
+ }
+
+ /**
+ * Creates a new instance of zip model
+ * @throws ZipException
+ */
+ private void createNewZipModel() {
+ zipModel = new ZipModel();
+ zipModel.setZipFile(file);
+ zipModel.setFileNameCharset(fileNameCharset);
+ }
+
+ /**
+ * Zip4j will encode all the file names with the input charset. This method throws
+ * an exception if the Charset is not supported
+ * @param charsetName
+ * @throws ZipException
+ */
+ public void setFileNameCharset(String charsetName) throws ZipException {
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(charsetName)) {
+ throw new ZipException("null or empty charset name");
+ }
+
+ if (!Zip4jUtil.isSupportedCharset(charsetName)) {
+ throw new ZipException("unsupported charset: " + charsetName);
+ }
+
+ this.fileNameCharset = charsetName;
+ }
+
+ /**
+ * Returns an input stream for reading the contents of the Zip file corresponding
+ * to the input FileHeader. Throws an exception if the FileHeader does not exist
+ * in the ZipFile
+ * @param fileHeader
+ * @return ZipInputStream
+ * @throws ZipException
+ */
+ public ZipInputStream getInputStream(FileHeader fileHeader) throws ZipException {
+ if (fileHeader == null) {
+ throw new ZipException("FileHeader is null, cannot get InputStream");
+ }
+
+ checkZipModel();
+
+ if (zipModel == null) {
+ throw new ZipException("zip model is null, cannot get inputstream");
+ }
+
+ Unzip unzip = new Unzip(zipModel);
+ return unzip.getInputStream(fileHeader);
+ }
+
+ /**
+ * Checks to see if the input zip file is a valid zip file. This method
+ * will try to read zip headers. If headers are read successfully, this
+ * method returns true else false
+ * @return boolean
+ * @since 1.2.3
+ */
+ public boolean isValidZipFile() {
+ try {
+ readZipInfo();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the full file path+names of all split zip files
+ * in an ArrayList. For example: If a split zip file(abc.zip) has a 10 split parts
+ * this method returns an array list with path + "abc.z01", path + "abc.z02", etc.
+ * Returns null if the zip file does not exist
+ * @return ArrayList of Strings
+ * @throws ZipException
+ */
+ public ArrayList getSplitZipFiles() throws ZipException {
+ checkZipModel();
+ return Zip4jUtil.getSplitZipFiles(zipModel);
+ }
+
+ public ProgressMonitor getProgressMonitor() {
+ return progressMonitor;
+ }
+
+ public boolean isRunInThread() {
+ return runInThread;
+ }
+
+ public void setRunInThread(boolean runInThread) {
+ this.runInThread = runInThread;
+ }
+
+ /**
+ * Returns the File object of the zip file
+ * @return File
+ */
+ public File getFile() {
+ return new File(this.file);
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/AESDecrypter.java b/harmony/src/main/java/com/zip4j/crypto/AESDecrypter.java
new file mode 100644
index 0000000..6b2d654
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/AESDecrypter.java
@@ -0,0 +1,227 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto;
+
+import com.zip4j.crypto.PBKDF2.MacBasedPRF;
+import com.zip4j.crypto.PBKDF2.PBKDF2Engine;
+import com.zip4j.crypto.PBKDF2.PBKDF2Parameters;
+import com.zip4j.crypto.engine.AESEngine;
+import com.zip4j.model.AESExtraDataRecord;
+import com.zip4j.model.LocalFileHeader;
+
+import java.util.Arrays;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.exception.ZipExceptionConstants;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Raw;
+import com.zip4j.util.Zip4jConstants;
+
+public class AESDecrypter implements IDecrypter {
+
+ private LocalFileHeader localFileHeader;
+ private AESEngine aesEngine;
+ private MacBasedPRF mac;
+
+ private final int PASSWORD_VERIFIER_LENGTH = 2;
+ private int KEY_LENGTH;
+ private int MAC_LENGTH;
+ private int SALT_LENGTH;
+
+ private byte[] aesKey;
+ private byte[] macKey;
+ private byte[] derivedPasswordVerifier;
+ private byte[] storedMac;
+
+ private int nonce = 1;
+ private byte[] iv;
+ private byte[] counterBlock;
+ private int loopCount = 0;
+
+ public AESDecrypter(LocalFileHeader localFileHeader,
+ byte[] salt, byte[] passwordVerifier) throws ZipException {
+
+ if (localFileHeader == null) {
+ throw new ZipException("one of the input parameters is null in AESDecryptor Constructor");
+ }
+
+ this.localFileHeader = localFileHeader;
+ this.storedMac = null;
+ iv = new byte[InternalZipConstants.AES_BLOCK_SIZE];
+ counterBlock = new byte[InternalZipConstants.AES_BLOCK_SIZE];
+ init(salt, passwordVerifier);
+ }
+
+ private void init(byte[] salt, byte[] passwordVerifier) throws ZipException {
+ if (localFileHeader == null) {
+ throw new ZipException("invalid file header in init method of AESDecryptor");
+ }
+
+ AESExtraDataRecord aesExtraDataRecord = localFileHeader.getAesExtraDataRecord();
+ if (aesExtraDataRecord == null) {
+ throw new ZipException("invalid aes extra data record - in init method of AESDecryptor");
+ }
+
+ switch (aesExtraDataRecord.getAesStrength()) {
+ case Zip4jConstants.AES_STRENGTH_128:
+ KEY_LENGTH = 16;
+ MAC_LENGTH = 16;
+ SALT_LENGTH = 8;
+ break;
+ case Zip4jConstants.AES_STRENGTH_192:
+ KEY_LENGTH = 24;
+ MAC_LENGTH = 24;
+ SALT_LENGTH = 12;
+ break;
+ case Zip4jConstants.AES_STRENGTH_256:
+ KEY_LENGTH = 32;
+ MAC_LENGTH = 32;
+ SALT_LENGTH = 16;
+ break;
+ default:
+ throw new ZipException("invalid aes key strength for file: " + localFileHeader.getFileName());
+ }
+
+ if (localFileHeader.getPassword() == null || localFileHeader.getPassword().length <= 0) {
+ throw new ZipException("empty or null password provided for AES Decryptor");
+ }
+
+ byte[] derivedKey = deriveKey(salt, localFileHeader.getPassword());
+ if (derivedKey == null ||
+ derivedKey.length != (KEY_LENGTH + MAC_LENGTH + PASSWORD_VERIFIER_LENGTH)) {
+ throw new ZipException("invalid derived key");
+ }
+
+ aesKey = new byte[KEY_LENGTH];
+ macKey = new byte[MAC_LENGTH];
+ derivedPasswordVerifier = new byte[PASSWORD_VERIFIER_LENGTH];
+
+ System.arraycopy(derivedKey, 0, aesKey, 0, KEY_LENGTH);
+ System.arraycopy(derivedKey, KEY_LENGTH, macKey, 0, MAC_LENGTH);
+ System.arraycopy(derivedKey, KEY_LENGTH + MAC_LENGTH, derivedPasswordVerifier, 0, PASSWORD_VERIFIER_LENGTH);
+
+ if (derivedPasswordVerifier == null) {
+ throw new ZipException("invalid derived password verifier for AES");
+ }
+
+ if (!Arrays.equals(passwordVerifier, derivedPasswordVerifier)) {
+ throw new ZipException("Wrong Password for file: " + localFileHeader.getFileName(), ZipExceptionConstants.WRONG_PASSWORD);
+ }
+
+ aesEngine = new AESEngine(aesKey);
+ mac = new MacBasedPRF("HmacSHA1");
+ mac.init(macKey);
+ }
+
+ public int decryptData(byte[] buff, int start, int len) throws ZipException {
+
+ if (aesEngine == null) {
+ throw new ZipException("AES not initialized properly");
+ }
+
+ try {
+
+ for (int j = start; j < (start + len); j += InternalZipConstants.AES_BLOCK_SIZE) {
+ loopCount = (j + InternalZipConstants.AES_BLOCK_SIZE <= (start + len)) ?
+ InternalZipConstants.AES_BLOCK_SIZE : ((start + len) - j);
+
+ mac.update(buff, j, loopCount);
+ Raw.prepareBuffAESIVBytes(iv, nonce, InternalZipConstants.AES_BLOCK_SIZE);
+ aesEngine.processBlock(iv, counterBlock);
+
+ for (int k = 0; k < loopCount; k++) {
+ buff[j + k] = (byte)(buff[j + k] ^ counterBlock[k]);
+ }
+
+ nonce++;
+ }
+
+ return len;
+
+ } catch (ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ public int decryptData(byte[] buff) throws ZipException {
+ return decryptData(buff, 0, buff.length);
+ }
+
+ private byte[] deriveKey(byte[] salt, char[] password) throws ZipException {
+ try {
+ PBKDF2Parameters p = new PBKDF2Parameters("HmacSHA1", "ISO-8859-1",
+ salt, 1000);
+ PBKDF2Engine e = new PBKDF2Engine(p);
+ byte[] derivedKey = e.deriveKey(password, KEY_LENGTH + MAC_LENGTH + PASSWORD_VERIFIER_LENGTH);
+ return derivedKey;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ public int getPasswordVerifierLength() {
+ return PASSWORD_VERIFIER_LENGTH;
+ }
+
+ public int getSaltLength() {
+ return SALT_LENGTH;
+ }
+
+ public byte[] getCalculatedAuthenticationBytes() {
+ return mac.doFinal();
+ }
+
+ public void setStoredMac(byte[] storedMac) {
+ this.storedMac = storedMac;
+ }
+
+ public byte[] getStoredMac() {
+ return storedMac;
+ }
+
+// public byte[] getStoredMac() throws ZipException {
+// if (raf == null) {
+// throw new ZipException("attempting to read MAC on closed file handle");
+// }
+//
+// try {
+// byte[] storedMacBytes = new byte[InternalZipConstants.AES_AUTH_LENGTH];
+// int bytesRead = raf.read(storedMacBytes);
+// if (bytesRead != InternalZipConstants.AES_AUTH_LENGTH) {
+// if (zipModel.isSplitArchive()) {
+//// unzipEngine.startNextSplitFile();
+// if (bytesRead == -1) bytesRead = 0;
+// int newlyRead = raf.read(storedMacBytes, bytesRead, InternalZipConstants.AES_AUTH_LENGTH - bytesRead);
+// bytesRead += newlyRead;
+// if (bytesRead != InternalZipConstants.AES_AUTH_LENGTH) {
+// throw new ZipException("invalid number of bytes read for stored MAC after starting split file");
+// }
+// } else {
+// throw new ZipException("invalid number of bytes read for stored MAC");
+// }
+// }
+// return storedMacBytes;
+// } catch (IOException e) {
+// throw new ZipException(e);
+// } catch (Exception e) {
+// throw new ZipException(e);
+// }
+//
+// }
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/AESEncrpyter.java b/harmony/src/main/java/com/zip4j/crypto/AESEncrpyter.java
new file mode 100644
index 0000000..d97bd85
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/AESEncrpyter.java
@@ -0,0 +1,215 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto;
+
+import com.zip4j.crypto.PBKDF2.MacBasedPRF;
+import com.zip4j.crypto.PBKDF2.PBKDF2Engine;
+import com.zip4j.crypto.PBKDF2.PBKDF2Parameters;
+import com.zip4j.crypto.engine.AESEngine;
+
+import java.util.Random;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Raw;
+import com.zip4j.util.Zip4jConstants;
+
+public class AESEncrpyter implements IEncrypter {
+
+ private char[] password;
+ private int keyStrength;
+ private AESEngine aesEngine;
+ private MacBasedPRF mac;
+
+ private int KEY_LENGTH;
+ private int MAC_LENGTH;
+ private int SALT_LENGTH;
+ private final int PASSWORD_VERIFIER_LENGTH = 2;
+
+ private byte[] aesKey;
+ private byte[] macKey;
+ private byte[] derivedPasswordVerifier;
+ private byte[] saltBytes;
+
+ private boolean finished;
+
+ private int nonce = 1;
+ private int loopCount = 0;
+
+ private byte[] iv;
+ private byte[] counterBlock;
+
+ public AESEncrpyter(char[] password, int keyStrength) throws ZipException {
+ if (password == null || password.length == 0) {
+ throw new ZipException("input password is empty or null in AES encrypter constructor");
+ }
+ if (keyStrength != Zip4jConstants.AES_STRENGTH_128 &&
+ keyStrength != Zip4jConstants.AES_STRENGTH_256) {
+ throw new ZipException("Invalid key strength in AES encrypter constructor");
+ }
+
+ this.password = password;
+ this.keyStrength = keyStrength;
+ this.finished = false;
+ counterBlock = new byte[InternalZipConstants.AES_BLOCK_SIZE];
+ iv = new byte[InternalZipConstants.AES_BLOCK_SIZE];
+ init();
+ }
+
+ private void init() throws ZipException {
+ switch (keyStrength) {
+ case Zip4jConstants.AES_STRENGTH_128:
+ KEY_LENGTH = 16;
+ MAC_LENGTH = 16;
+ SALT_LENGTH = 8;
+ break;
+ case Zip4jConstants.AES_STRENGTH_256:
+ KEY_LENGTH = 32;
+ MAC_LENGTH = 32;
+ SALT_LENGTH = 16;
+ break;
+ default:
+ throw new ZipException("invalid aes key strength, cannot determine key sizes");
+ }
+
+ saltBytes = generateSalt(SALT_LENGTH);
+ byte[] keyBytes = deriveKey(saltBytes, password);
+
+ if (keyBytes == null || keyBytes.length != (KEY_LENGTH + MAC_LENGTH + PASSWORD_VERIFIER_LENGTH)) {
+ throw new ZipException("invalid key generated, cannot decrypt file");
+ }
+
+ aesKey = new byte[KEY_LENGTH];
+ macKey = new byte[MAC_LENGTH];
+ derivedPasswordVerifier = new byte[PASSWORD_VERIFIER_LENGTH];
+
+ System.arraycopy(keyBytes, 0, aesKey, 0, KEY_LENGTH);
+ System.arraycopy(keyBytes, KEY_LENGTH, macKey, 0, MAC_LENGTH);
+ System.arraycopy(keyBytes, KEY_LENGTH + MAC_LENGTH, derivedPasswordVerifier, 0, PASSWORD_VERIFIER_LENGTH);
+
+ aesEngine = new AESEngine(aesKey);
+ mac = new MacBasedPRF("HmacSHA1");
+ mac.init(macKey);
+ }
+
+ private byte[] deriveKey(byte[] salt, char[] password) throws ZipException {
+ try {
+ PBKDF2Parameters p = new PBKDF2Parameters("HmacSHA1", "ISO-8859-1",
+ salt, 1000);
+ PBKDF2Engine e = new PBKDF2Engine(p);
+ byte[] derivedKey = e.deriveKey(password, KEY_LENGTH + MAC_LENGTH + PASSWORD_VERIFIER_LENGTH);
+ return derivedKey;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ public int encryptData(byte[] buff) throws ZipException {
+
+ if (buff == null) {
+ throw new ZipException("input bytes are null, cannot perform AES encrpytion");
+ }
+ return encryptData(buff, 0, buff.length);
+ }
+
+ public int encryptData(byte[] buff, int start, int len) throws ZipException {
+
+ if (finished) {
+ // A non 16 byte block has already been passed to encrypter
+ // non 16 byte block should be the last block of compressed data in AES encryption
+ // any more encryption will lead to corruption of data
+ throw new ZipException("AES Encrypter is in finished state (A non 16 byte block has already been passed to encrypter)");
+ }
+
+ if (len%16!=0) {
+ this.finished = true;
+ }
+
+ for (int j = start; j < (start + len); j += InternalZipConstants.AES_BLOCK_SIZE) {
+ loopCount = (j + InternalZipConstants.AES_BLOCK_SIZE <= (start + len)) ?
+ InternalZipConstants.AES_BLOCK_SIZE : ((start + len) - j);
+
+ Raw.prepareBuffAESIVBytes(iv, nonce, InternalZipConstants.AES_BLOCK_SIZE);
+ aesEngine.processBlock(iv, counterBlock);
+
+ for (int k = 0; k < loopCount; k++) {
+ buff[j + k] = (byte)(buff[j + k] ^ counterBlock[k]);
+ }
+
+ mac.update(buff, j, loopCount);
+ nonce++;
+ }
+
+ return len;
+ }
+
+ private static byte[] generateSalt(int size) throws ZipException {
+
+ if (size != 8 && size != 16) {
+ throw new ZipException("invalid salt size, cannot generate salt");
+ }
+
+ int rounds = 0;
+
+ if (size == 8)
+ rounds = 2;
+ if (size == 16)
+ rounds = 4;
+
+ byte[] salt = new byte[size];
+ for( int j = 0; j < rounds; j++ ) {
+ Random rand = new Random();
+ int i = rand.nextInt();
+ salt[0+j*4] = (byte)(i>>24);
+ salt[1+j*4] = (byte)(i>>16);
+ salt[2+j*4] = (byte)(i>>8);
+ salt[3+j*4] = (byte)i;
+ }
+ return salt;
+ }
+
+ public byte[] getFinalMac() {
+ byte[] rawMacBytes = mac.doFinal();
+ byte[] macBytes = new byte[10];
+ System.arraycopy(rawMacBytes, 0, macBytes, 0, 10);
+ return macBytes;
+ }
+
+ public byte[] getDerivedPasswordVerifier() {
+ return derivedPasswordVerifier;
+ }
+
+ public void setDerivedPasswordVerifier(byte[] derivedPasswordVerifier) {
+ this.derivedPasswordVerifier = derivedPasswordVerifier;
+ }
+
+ public byte[] getSaltBytes() {
+ return saltBytes;
+ }
+
+ public void setSaltBytes(byte[] saltBytes) {
+ this.saltBytes = saltBytes;
+ }
+
+ public int getSaltLength() {
+ return SALT_LENGTH;
+ }
+
+ public int getPasswordVeriifierLength() {
+ return PASSWORD_VERIFIER_LENGTH;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/IDecrypter.java b/harmony/src/main/java/com/zip4j/crypto/IDecrypter.java
new file mode 100644
index 0000000..f362dc2
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/IDecrypter.java
@@ -0,0 +1,27 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto;
+
+import com.zip4j.exception.ZipException;
+
+public interface IDecrypter {
+
+ public int decryptData(byte[] buff, int start, int len) throws ZipException;
+
+ public int decryptData(byte[] buff) throws ZipException;
+
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/IEncrypter.java b/harmony/src/main/java/com/zip4j/crypto/IEncrypter.java
new file mode 100644
index 0000000..0b7b190
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/IEncrypter.java
@@ -0,0 +1,27 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto;
+
+import com.zip4j.exception.ZipException;
+
+public interface IEncrypter {
+
+ public int encryptData(byte[] buff) throws ZipException;
+
+ public int encryptData(byte[] buff, int start, int len) throws ZipException;
+
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/PBKDF2/BinTools.java b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/BinTools.java
new file mode 100644
index 0000000..28dfc9c
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/BinTools.java
@@ -0,0 +1,85 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto.PBKDF2;
+
+/*
+ * Source referred from Matthias Gartner's PKCS#5 implementation -
+ * see http://rtner.de/software/PBKDF2.html
+ */
+
+class BinTools
+{
+ public static final String hex = "0123456789ABCDEF";
+
+ public static String bin2hex(final byte[] b)
+ {
+ if (b == null)
+ {
+ return "";
+ }
+ StringBuffer sb = new StringBuffer(2 * b.length);
+ for (int i = 0; i < b.length; i++)
+ {
+ int v = (256 + b[i]) % 256;
+ sb.append(hex.charAt((v / 16) & 15));
+ sb.append(hex.charAt((v % 16) & 15));
+ }
+ return sb.toString();
+ }
+
+ public static byte[] hex2bin(final String s)
+ {
+ String m = s;
+ if (s == null)
+ {
+ // Allow empty input string.
+ m = "";
+ }
+ else if (s.length() % 2 != 0)
+ {
+ // Assume leading zero for odd string length
+ m = "0" + s;
+ }
+ byte r[] = new byte[m.length() / 2];
+ for (int i = 0, n = 0; i < m.length(); n++)
+ {
+ char h = m.charAt(i++);
+ char l = m.charAt(i++);
+ r[n] = (byte) (hex2bin(h) * 16 + hex2bin(l));
+ }
+ return r;
+ }
+
+ public static int hex2bin(char c)
+ {
+ if (c >= '0' && c <= '9')
+ {
+ return (c - '0');
+ }
+ if (c >= 'A' && c <= 'F')
+ {
+ return (c - 'A' + 10);
+ }
+ if (c >= 'a' && c <= 'f')
+ {
+ return (c - 'a' + 10);
+ }
+ throw new IllegalArgumentException(
+ "Input string may only contain hex digits, but found '" + c
+ + "'");
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/PBKDF2/MacBasedPRF.java b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/MacBasedPRF.java
new file mode 100644
index 0000000..8215f1f
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/MacBasedPRF.java
@@ -0,0 +1,116 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto.PBKDF2;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/*
+ * Source referred from Matthias Gartner's PKCS#5 implementation -
+ * see http://rtner.de/software/PBKDF2.html
+ */
+
+public class MacBasedPRF implements PRF
+{
+ protected Mac mac;
+
+ protected int hLen;
+
+ protected String macAlgorithm;
+
+ public MacBasedPRF(String macAlgorithm)
+ {
+ this.macAlgorithm = macAlgorithm;
+ try
+ {
+ mac = Mac.getInstance(macAlgorithm);
+ hLen = mac.getMacLength();
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public MacBasedPRF(String macAlgorithm, String provider)
+ {
+ this.macAlgorithm = macAlgorithm;
+ try
+ {
+ mac = Mac.getInstance(macAlgorithm, provider);
+ hLen = mac.getMacLength();
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (NoSuchProviderException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public byte[] doFinal(byte[] M)
+ {
+ byte[] r = mac.doFinal(M);
+ return r;
+ }
+
+ public byte[] doFinal() {
+ byte[] r = mac.doFinal();
+ return r;
+ }
+
+ public int getHLen()
+ {
+ return hLen;
+ }
+
+ public void init(byte[] P)
+ {
+ try
+ {
+ mac.init(new SecretKeySpec(P, macAlgorithm));
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void update(byte[] U) {
+
+ try {
+ mac.update(U);
+ } catch (IllegalStateException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public void update (byte[] U, int start, int len) {
+ try {
+ mac.update(U, start, len);
+ } catch (IllegalStateException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2Engine.java b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2Engine.java
new file mode 100644
index 0000000..fa8049a
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2Engine.java
@@ -0,0 +1,198 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto.PBKDF2;
+
+import com.zip4j.util.Raw;
+
+/*
+ * Source referred from Matthias Gartner's PKCS#5 implementation -
+ * see http://rtner.de/software/PBKDF2.html
+ */
+
+public class PBKDF2Engine
+{
+ protected PBKDF2Parameters parameters;
+
+ protected PRF prf;
+
+ public PBKDF2Engine()
+ {
+ this.parameters = null;
+ prf = null;
+ }
+
+ public PBKDF2Engine(PBKDF2Parameters parameters)
+ {
+ this.parameters = parameters;
+ prf = null;
+ }
+
+ public PBKDF2Engine(PBKDF2Parameters parameters, PRF prf)
+ {
+ this.parameters = parameters;
+ this.prf = prf;
+ }
+
+ public byte[] deriveKey(char[] inputPassword)
+ {
+ return deriveKey(inputPassword, 0);
+ }
+
+ public byte[] deriveKey(char[] inputPassword, int dkLen)
+ {
+ byte[] r = null;
+ byte P[] = null;
+ if (inputPassword == null)
+ {
+ throw new NullPointerException();
+ }
+
+ P = Raw.convertCharArrayToByteArray(inputPassword);
+
+ assertPRF(P);
+ if (dkLen == 0)
+ {
+ dkLen = prf.getHLen();
+ }
+ r = PBKDF2(prf, parameters.getSalt(), parameters.getIterationCount(),
+ dkLen);
+ return r;
+ }
+
+ public boolean verifyKey(char[] inputPassword)
+ {
+ byte[] referenceKey = getParameters().getDerivedKey();
+ if (referenceKey == null || referenceKey.length == 0)
+ {
+ return false;
+ }
+ byte[] inputKey = deriveKey(inputPassword, referenceKey.length);
+
+ if (inputKey == null || inputKey.length != referenceKey.length)
+ {
+ return false;
+ }
+ for (int i = 0; i < inputKey.length; i++)
+ {
+ if (inputKey[i] != referenceKey[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected void assertPRF(byte[] P)
+ {
+ if (prf == null)
+ {
+ prf = new MacBasedPRF(parameters.getHashAlgorithm());
+ }
+ prf.init(P);
+ }
+
+ public PRF getPseudoRandomFunction()
+ {
+ return prf;
+ }
+
+ protected byte[] PBKDF2(PRF prf, byte[] S, int c, int dkLen)
+ {
+ if (S == null)
+ {
+ S = new byte[0];
+ }
+ int hLen = prf.getHLen();
+ int l = ceil(dkLen, hLen);
+ int r = dkLen - (l - 1) * hLen;
+ byte T[] = new byte[l * hLen];
+ int ti_offset = 0;
+ for (int i = 1; i <= l; i++)
+ {
+ _F(T, ti_offset, prf, S, c, i);
+ ti_offset += hLen;
+ }
+ if (r < hLen)
+ {
+ // Incomplete last block
+ byte DK[] = new byte[dkLen];
+ System.arraycopy(T, 0, DK, 0, dkLen);
+ return DK;
+ }
+ return T;
+ }
+
+ protected int ceil(int a, int b)
+ {
+ int m = 0;
+ if (a % b > 0)
+ {
+ m = 1;
+ }
+ return a / b + m;
+ }
+
+ protected void _F(byte[] dest, int offset, PRF prf, byte[] S, int c,
+ int blockIndex)
+ {
+ int hLen = prf.getHLen();
+ byte U_r[] = new byte[hLen];
+
+ // U0 = S || INT (i);
+ byte U_i[] = new byte[S.length + 4];
+ System.arraycopy(S, 0, U_i, 0, S.length);
+ INT(U_i, S.length, blockIndex);
+
+ for (int i = 0; i < c; i++)
+ {
+ U_i = prf.doFinal(U_i);
+ xor(U_r, U_i);
+ }
+ System.arraycopy(U_r, 0, dest, offset, hLen);
+ }
+
+ protected void xor(byte[] dest, byte[] src)
+ {
+ for (int i = 0; i < dest.length; i++)
+ {
+ dest[i] ^= src[i];
+ }
+ }
+
+ protected void INT(byte[] dest, int offset, int i)
+ {
+ dest[offset + 0] = (byte) (i / (256 * 256 * 256));
+ dest[offset + 1] = (byte) (i / (256 * 256));
+ dest[offset + 2] = (byte) (i / (256));
+ dest[offset + 3] = (byte) (i);
+ }
+
+ public PBKDF2Parameters getParameters()
+ {
+ return parameters;
+ }
+
+ public void setParameters(PBKDF2Parameters parameters)
+ {
+ this.parameters = parameters;
+ }
+
+ public void setPseudoRandomFunction(PRF prf)
+ {
+ this.prf = prf;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2HexFormatter.java b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2HexFormatter.java
new file mode 100644
index 0000000..14b45d9
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2HexFormatter.java
@@ -0,0 +1,56 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto.PBKDF2;
+
+/*
+ * Source referred from Matthias Gartner's PKCS#5 implementation -
+ * see http://rtner.de/software/PBKDF2.html
+ */
+
+class PBKDF2HexFormatter
+{
+ public boolean fromString(PBKDF2Parameters p, String s)
+ {
+ if (p == null || s == null)
+ {
+ return true;
+ }
+
+ String[] p123 = s.split(":");
+ if (p123 == null || p123.length != 3)
+ {
+ return true;
+ }
+
+ byte salt[] = BinTools.hex2bin(p123[0]);
+ int iterationCount = Integer.parseInt(p123[1]);
+ byte bDK[] = BinTools.hex2bin(p123[2]);
+
+ p.setSalt(salt);
+ p.setIterationCount(iterationCount);
+ p.setDerivedKey(bDK);
+ return false;
+ }
+
+ public String toString(PBKDF2Parameters p)
+ {
+ String s = BinTools.bin2hex(p.getSalt()) + ":"
+ + String.valueOf(p.getIterationCount()) + ":"
+ + BinTools.bin2hex(p.getDerivedKey());
+ return s;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2Parameters.java b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2Parameters.java
new file mode 100644
index 0000000..8f6e8fa
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PBKDF2Parameters.java
@@ -0,0 +1,112 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto.PBKDF2;
+/*
+ * Source referred from Matthias Gartner's PKCS#5 implementation -
+ * see http://rtner.de/software/PBKDF2.html
+ */
+public class PBKDF2Parameters
+{
+ protected byte[] salt;
+
+ protected int iterationCount;
+
+ protected String hashAlgorithm;
+
+ protected String hashCharset;
+
+ protected byte[] derivedKey;
+
+ public PBKDF2Parameters()
+ {
+ this.hashAlgorithm = null;
+ this.hashCharset = "UTF-8";
+ this.salt = null;
+ this.iterationCount = 1000;
+ this.derivedKey = null;
+ }
+
+ public PBKDF2Parameters(String hashAlgorithm, String hashCharset,
+ byte[] salt, int iterationCount)
+ {
+ this.hashAlgorithm = hashAlgorithm;
+ this.hashCharset = hashCharset;
+ this.salt = salt;
+ this.iterationCount = iterationCount;
+ this.derivedKey = null;
+ }
+
+ public PBKDF2Parameters(String hashAlgorithm, String hashCharset,
+ byte[] salt, int iterationCount, byte[] derivedKey)
+ {
+ this.hashAlgorithm = hashAlgorithm;
+ this.hashCharset = hashCharset;
+ this.salt = salt;
+ this.iterationCount = iterationCount;
+ this.derivedKey = derivedKey;
+ }
+
+ public int getIterationCount()
+ {
+ return iterationCount;
+ }
+
+ public void setIterationCount(int iterationCount)
+ {
+ this.iterationCount = iterationCount;
+ }
+
+ public byte[] getSalt()
+ {
+ return salt;
+ }
+
+ public void setSalt(byte[] salt)
+ {
+ this.salt = salt;
+ }
+
+ public byte[] getDerivedKey()
+ {
+ return derivedKey;
+ }
+
+ public void setDerivedKey(byte[] derivedKey)
+ {
+ this.derivedKey = derivedKey;
+ }
+
+ public String getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+
+ public void setHashAlgorithm(String hashAlgorithm)
+ {
+ this.hashAlgorithm = hashAlgorithm;
+ }
+
+ public String getHashCharset()
+ {
+ return hashCharset;
+ }
+
+ public void setHashCharset(String hashCharset)
+ {
+ this.hashCharset = hashCharset;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PRF.java b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PRF.java
new file mode 100644
index 0000000..222dd75
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/PBKDF2/PRF.java
@@ -0,0 +1,31 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto.PBKDF2;
+
+/*
+ * Source referred from Matthias Gartner's PKCS#5 implementation -
+ * see http://rtner.de/software/PBKDF2.html
+ */
+
+interface PRF
+{
+ public void init(byte[] P);
+
+ public byte[] doFinal(byte[] M);
+
+ public int getHLen();
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/StandardDecrypter.java b/harmony/src/main/java/com/zip4j/crypto/StandardDecrypter.java
new file mode 100644
index 0000000..1b100b8
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/StandardDecrypter.java
@@ -0,0 +1,97 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto;
+
+import com.zip4j.crypto.engine.ZipCryptoEngine;
+import com.zip4j.model.FileHeader;
+import com.zip4j.exception.ZipException;
+import com.zip4j.exception.ZipExceptionConstants;
+import com.zip4j.util.InternalZipConstants;
+
+public class StandardDecrypter implements IDecrypter {
+
+ private FileHeader fileHeader;
+ private byte[] crc = new byte[4];
+ private ZipCryptoEngine zipCryptoEngine;
+
+ public StandardDecrypter(FileHeader fileHeader, byte[] headerBytes) throws ZipException{
+ if (fileHeader == null) {
+ throw new ZipException("one of more of the input parameters were null in StandardDecryptor");
+ }
+
+ this.fileHeader = fileHeader;
+ this.zipCryptoEngine = new ZipCryptoEngine();
+ init(headerBytes);
+ }
+
+ public int decryptData(byte[] buff) throws ZipException {
+ return decryptData(buff, 0, buff.length);
+ }
+
+ public int decryptData(byte[] buff, int start, int len) throws ZipException {
+ if (start < 0 || len < 0) {
+ throw new ZipException("one of the input parameters were null in standard decrpyt data");
+ }
+
+ try {
+ for (int i = 0; i < len; i++) {
+ int val = buff[i] & 0xff;
+ val = (val ^ zipCryptoEngine.decryptByte()) & 0xff;
+ zipCryptoEngine.updateKeys((byte) val);
+ buff[i] = (byte)val;
+ }
+ return len;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ public void init(byte[] headerBytes) throws ZipException {
+ byte[] crcBuff = fileHeader.getCrcBuff();
+ crc[3] = (byte) (crcBuff[3] & 0xFF);
+ crc[2] = (byte) ((crcBuff[3] >> 8) & 0xFF);
+ crc[1] = (byte) ((crcBuff[3] >> 16) & 0xFF);
+ crc[0] = (byte) ((crcBuff[3] >> 24) & 0xFF);
+
+ if(crc[2] > 0 || crc[1] > 0 || crc[0] > 0)
+ throw new IllegalStateException("Invalid CRC in File Header");
+
+ if (fileHeader.getPassword() == null || fileHeader.getPassword().length <= 0) {
+ throw new ZipException("Wrong password!", ZipExceptionConstants.WRONG_PASSWORD);
+ }
+
+ zipCryptoEngine.initKeys(fileHeader.getPassword());
+
+ try {
+ int result = headerBytes[0];
+ for (int i = 0; i < InternalZipConstants.STD_DEC_HDR_SIZE; i++) {
+// Commented this as this check cannot always be trusted
+// New functionality: If there is an error in extracting a password protected file,
+// "Wrong Password?" text is appended to the exception message
+// if(i+1 == InternalZipConstants.STD_DEC_HDR_SIZE && ((byte)(result ^ zipCryptoEngine.decryptByte()) != crc[3]) && !isSplit)
+// throw new ZipException("Wrong password!", ZipExceptionConstants.WRONG_PASSWORD);
+
+ zipCryptoEngine.updateKeys((byte) (result ^ zipCryptoEngine.decryptByte()));
+ if (i+1 != InternalZipConstants.STD_DEC_HDR_SIZE)
+ result = headerBytes[i+1];
+ }
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/StandardEncrypter.java b/harmony/src/main/java/com/zip4j/crypto/StandardEncrypter.java
new file mode 100644
index 0000000..f324b87
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/StandardEncrypter.java
@@ -0,0 +1,134 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto;
+
+import com.zip4j.crypto.engine.ZipCryptoEngine;
+
+import java.util.Random;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.util.InternalZipConstants;
+
+public class StandardEncrypter implements IEncrypter {
+
+ private ZipCryptoEngine zipCryptoEngine;
+ private byte[] headerBytes;
+
+ public StandardEncrypter(char[] password, int crc) throws ZipException {
+ if (password == null || password.length <= 0) {
+ throw new ZipException("input password is null or empty in standard encrpyter constructor");
+ }
+
+ this.zipCryptoEngine = new ZipCryptoEngine();
+
+ this.headerBytes = new byte[InternalZipConstants.STD_DEC_HDR_SIZE];
+ init(password, crc);
+ }
+
+ private void init(char[] password, int crc) throws ZipException {
+ if (password == null || password.length <= 0) {
+ throw new ZipException("input password is null or empty, cannot initialize standard encrypter");
+ }
+ zipCryptoEngine.initKeys(password);
+ headerBytes = generateRandomBytes(InternalZipConstants.STD_DEC_HDR_SIZE);
+ // Initialize again since the generated bytes were encrypted.
+ zipCryptoEngine.initKeys(password);
+
+ headerBytes[InternalZipConstants.STD_DEC_HDR_SIZE - 1] = (byte)((crc >>> 24));
+ headerBytes[InternalZipConstants.STD_DEC_HDR_SIZE - 2] = (byte)((crc >>> 16));
+
+ if (headerBytes.length < InternalZipConstants.STD_DEC_HDR_SIZE) {
+ throw new ZipException("invalid header bytes generated, cannot perform standard encryption");
+ }
+
+ encryptData(headerBytes);
+ }
+
+ public int encryptData(byte[] buff) throws ZipException {
+ if (buff == null) {
+ throw new NullPointerException();
+ }
+ return encryptData(buff, 0, buff.length);
+ }
+
+ public int encryptData(byte[] buff, int start, int len) throws ZipException {
+
+ if (len < 0) {
+ throw new ZipException("invalid length specified to decrpyt data");
+ }
+
+ try {
+ for (int i = start; i < start + len; i++) {
+ buff[i] = encryptByte(buff[i]);
+ }
+ return len;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ protected byte encryptByte(byte val) {
+ byte temp_val = (byte) (val ^ zipCryptoEngine.decryptByte() & 0xff);
+ zipCryptoEngine.updateKeys(val);
+ return temp_val;
+ }
+
+ protected byte[] generateRandomBytes(int size) throws ZipException {
+
+ if (size <= 0) {
+ throw new ZipException("size is either 0 or less than 0, cannot generate header for standard encryptor");
+ }
+
+ byte[] buff = new byte[size];
+
+ Random rand = new Random();
+
+ for (int i = 0; i < buff.length; i ++) {
+ // Encrypted to get less predictability for poorly implemented
+ // rand functions.
+ buff[i] = encryptByte((byte) rand.nextInt(256));
+ }
+
+// buff[0] = (byte)87;
+// buff[1] = (byte)176;
+// buff[2] = (byte)-49;
+// buff[3] = (byte)-43;
+// buff[4] = (byte)93;
+// buff[5] = (byte)-204;
+// buff[6] = (byte)-105;
+// buff[7] = (byte)213;
+// buff[8] = (byte)-80;
+// buff[9] = (byte)-8;
+// buff[10] = (byte)21;
+// buff[11] = (byte)242;
+
+// for( int j=0; j<2; j++ ) {
+// Random rand = new Random();
+// int i = rand.nextInt();
+// buff[0+j*4] = (byte)(i>>24);
+// buff[1+j*4] = (byte)(i>>16);
+// buff[2+j*4] = (byte)(i>>8);
+// buff[3+j*4] = (byte)i;
+// }
+ return buff;
+ }
+
+ public byte[] getHeaderBytes() {
+ return headerBytes;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/engine/AESEngine.java b/harmony/src/main/java/com/zip4j/crypto/engine/AESEngine.java
new file mode 100644
index 0000000..d24808e
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/engine/AESEngine.java
@@ -0,0 +1,291 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto.engine;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.util.InternalZipConstants;
+
+/**
+ * Core Engine for AES Encryption
+ * @author Srikanth Reddy Lingala
+ *
+ */
+public class AESEngine {
+
+ private int rounds;
+ private int[][] workingKey = null;
+ private int C0, C1, C2, C3;
+
+ public AESEngine(byte[] key) throws ZipException {
+ init(key);
+ }
+
+ public void init(byte[] key) throws ZipException {
+ workingKey = generateWorkingKey(key);
+ }
+
+ private int[][] generateWorkingKey(byte[] key) throws ZipException {
+ int kc = key.length / 4;
+ int t;
+
+ if (((kc != 4) && (kc != 6) && (kc != 8)) || ((kc * 4) != key.length))
+ {
+ throw new ZipException("invalid key length (not 128/192/256)");
+ }
+
+ rounds = kc + 6;
+ int[][] W = new int[rounds+1][4];
+
+ t = 0;
+ int i = 0;
+ while (i < key.length)
+ {
+ W[t >> 2][t & 3] = (key[i]&0xff) | ((key[i+1]&0xff) << 8) | ((key[i+2]&0xff) << 16) | (key[i+3] << 24);
+ i+=4;
+ t++;
+ }
+
+ int k = (rounds + 1) << 2;
+ for (i = kc; (i < k); i++)
+ {
+ int temp = W[(i-1)>>2][(i-1)&3];
+ if ((i % kc) == 0)
+ {
+ temp = subWord(shift(temp, 8)) ^ rcon[(i / kc)-1];
+ }
+ else if ((kc > 6) && ((i % kc) == 4))
+ {
+ temp = subWord(temp);
+ }
+
+ W[i>>2][i&3] = W[(i - kc)>>2][(i-kc)&3] ^ temp;
+ }
+ return W;
+ }
+
+ public int processBlock(byte[] in, byte[] out) throws ZipException {
+ return processBlock(in, 0, out, 0);
+ }
+
+ public int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws ZipException {
+ if (workingKey == null)
+ {
+ throw new ZipException("AES engine not initialised");
+ }
+
+ if ((inOff + (32 / 2)) > in.length)
+ {
+ throw new ZipException("input buffer too short");
+ }
+
+ if ((outOff + (32 / 2)) > out.length)
+ {
+ throw new ZipException("output buffer too short");
+ }
+
+ stateIn(in, inOff);
+ encryptBlock(workingKey);
+ stateOut(out, outOff);
+
+ return InternalZipConstants.AES_BLOCK_SIZE;
+ }
+
+ private final void stateIn(byte[] bytes, int off) {
+ int index = off;
+
+ C0 = (bytes[index++] & 0xff);
+ C0 |= (bytes[index++] & 0xff) << 8;
+ C0 |= (bytes[index++] & 0xff) << 16;
+ C0 |= bytes[index++] << 24;
+
+ C1 = (bytes[index++] & 0xff);
+ C1 |= (bytes[index++] & 0xff) << 8;
+ C1 |= (bytes[index++] & 0xff) << 16;
+ C1 |= bytes[index++] << 24;
+
+ C2 = (bytes[index++] & 0xff);
+ C2 |= (bytes[index++] & 0xff) << 8;
+ C2 |= (bytes[index++] & 0xff) << 16;
+ C2 |= bytes[index++] << 24;
+
+ C3 = (bytes[index++] & 0xff);
+ C3 |= (bytes[index++] & 0xff) << 8;
+ C3 |= (bytes[index++] & 0xff) << 16;
+ C3 |= bytes[index++] << 24;
+ }
+
+ private final void stateOut(byte[] bytes, int off) {
+ int index = off;
+
+ bytes[index++] = (byte)C0;
+ bytes[index++] = (byte)(C0 >> 8);
+ bytes[index++] = (byte)(C0 >> 16);
+ bytes[index++] = (byte)(C0 >> 24);
+
+ bytes[index++] = (byte)C1;
+ bytes[index++] = (byte)(C1 >> 8);
+ bytes[index++] = (byte)(C1 >> 16);
+ bytes[index++] = (byte)(C1 >> 24);
+
+ bytes[index++] = (byte)C2;
+ bytes[index++] = (byte)(C2 >> 8);
+ bytes[index++] = (byte)(C2 >> 16);
+ bytes[index++] = (byte)(C2 >> 24);
+
+ bytes[index++] = (byte)C3;
+ bytes[index++] = (byte)(C3 >> 8);
+ bytes[index++] = (byte)(C3 >> 16);
+ bytes[index++] = (byte)(C3 >> 24);
+ }
+
+ private final void encryptBlock(int[][] KW) {
+ int r, r0, r1, r2, r3;
+
+ C0 ^= KW[0][0];
+ C1 ^= KW[0][1];
+ C2 ^= KW[0][2];
+ C3 ^= KW[0][3];
+
+ r = 1;
+
+ while (r < rounds - 1)
+ {
+ r0 = T0[C0&255] ^ shift(T0[(C1>>8)&255], 24) ^ shift(T0[(C2>>16)&255],16) ^ shift(T0[(C3>>24)&255],8) ^ KW[r][0];
+ r1 = T0[C1&255] ^ shift(T0[(C2>>8)&255], 24) ^ shift(T0[(C3>>16)&255], 16) ^ shift(T0[(C0>>24)&255], 8) ^ KW[r][1];
+ r2 = T0[C2&255] ^ shift(T0[(C3>>8)&255], 24) ^ shift(T0[(C0>>16)&255], 16) ^ shift(T0[(C1>>24)&255], 8) ^ KW[r][2];
+ r3 = T0[C3&255] ^ shift(T0[(C0>>8)&255], 24) ^ shift(T0[(C1>>16)&255], 16) ^ shift(T0[(C2>>24)&255], 8) ^ KW[r++][3];
+ C0 = T0[r0&255] ^ shift(T0[(r1>>8)&255], 24) ^ shift(T0[(r2>>16)&255], 16) ^ shift(T0[(r3>>24)&255], 8) ^ KW[r][0];
+ C1 = T0[r1&255] ^ shift(T0[(r2>>8)&255], 24) ^ shift(T0[(r3>>16)&255], 16) ^ shift(T0[(r0>>24)&255], 8) ^ KW[r][1];
+ C2 = T0[r2&255] ^ shift(T0[(r3>>8)&255], 24) ^ shift(T0[(r0>>16)&255], 16) ^ shift(T0[(r1>>24)&255], 8) ^ KW[r][2];
+ C3 = T0[r3&255] ^ shift(T0[(r0>>8)&255], 24) ^ shift(T0[(r1>>16)&255], 16) ^ shift(T0[(r2>>24)&255], 8) ^ KW[r++][3];
+ }
+
+ r0 = T0[C0&255] ^ shift(T0[(C1>>8)&255], 24) ^ shift(T0[(C2>>16)&255], 16) ^ shift(T0[(C3>>24)&255], 8) ^ KW[r][0];
+ r1 = T0[C1&255] ^ shift(T0[(C2>>8)&255], 24) ^ shift(T0[(C3>>16)&255], 16) ^ shift(T0[(C0>>24)&255], 8) ^ KW[r][1];
+ r2 = T0[C2&255] ^ shift(T0[(C3>>8)&255], 24) ^ shift(T0[(C0>>16)&255], 16) ^ shift(T0[(C1>>24)&255], 8) ^ KW[r][2];
+ r3 = T0[C3&255] ^ shift(T0[(C0>>8)&255], 24) ^ shift(T0[(C1>>16)&255], 16) ^ shift(T0[(C2>>24)&255], 8) ^ KW[r++][3];
+
+ C0 = (S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24) ^ KW[r][0];
+ C1 = (S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24) ^ KW[r][1];
+ C2 = (S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24) ^ KW[r][2];
+ C3 = (S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24) ^ KW[r][3];
+
+ }
+
+ private int shift(int r, int shift) {
+ return (r >>> shift) | (r << -shift);
+ }
+
+ private int subWord(int x) {
+ return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24);
+ }
+
+ private static final byte[] S = {
+ (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197,
+ (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118,
+ (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240,
+ (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192,
+ (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204,
+ (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21,
+ (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154,
+ (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117,
+ (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160,
+ (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132,
+ (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91,
+ (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207,
+ (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133,
+ (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168,
+ (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245,
+ (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210,
+ (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23,
+ (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115,
+ (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136,
+ (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219,
+ (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92,
+ (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121,
+ (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169,
+ (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8,
+ (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198,
+ (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138,
+ (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14,
+ (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158,
+ (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148,
+ (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223,
+ (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104,
+ (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22,
+ };
+
+ private static final int[] rcon = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
+ 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 };
+
+ private static final int[] T0 =
+ {
+ 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff,
+ 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102,
+ 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d,
+ 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa,
+ 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41,
+ 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453,
+ 0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d,
+ 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83,
+ 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2,
+ 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795,
+ 0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a,
+ 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df,
+ 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912,
+ 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc,
+ 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7,
+ 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413,
+ 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040,
+ 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d,
+ 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0,
+ 0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed,
+ 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a,
+ 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78,
+ 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080,
+ 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1,
+ 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020,
+ 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18,
+ 0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488,
+ 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a,
+ 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0,
+ 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54,
+ 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b,
+ 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad,
+ 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992,
+ 0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd,
+ 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3,
+ 0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda,
+ 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8,
+ 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4,
+ 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a,
+ 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697,
+ 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96,
+ 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c,
+ 0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7,
+ 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969,
+ 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9,
+ 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9,
+ 0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715,
+ 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5,
+ 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65,
+ 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929,
+ 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d,
+ 0x3a16162c};
+
+}
diff --git a/harmony/src/main/java/com/zip4j/crypto/engine/ZipCryptoEngine.java b/harmony/src/main/java/com/zip4j/crypto/engine/ZipCryptoEngine.java
new file mode 100644
index 0000000..09b9b67
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/crypto/engine/ZipCryptoEngine.java
@@ -0,0 +1,65 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.crypto.engine;
+
+public class ZipCryptoEngine {
+
+ private final int keys[] = new int[3];
+ private static final int[] CRC_TABLE = new int[256];
+
+ static {
+ for (int i = 0; i < 256; i++) {
+ int r = i;
+ for (int j = 0; j < 8; j++) {
+ if ((r & 1) == 1) {
+ r = (r >>> 1) ^ 0xedb88320;
+ } else {
+ r >>>= 1;
+ }
+ }
+ CRC_TABLE[i] = r;
+ }
+ }
+
+ public ZipCryptoEngine() {
+ }
+
+ public void initKeys(char[] password) {
+ keys[0] = 305419896;
+ keys[1] = 591751049;
+ keys[2] = 878082192;
+ for (int i = 0; i < password.length; i++) {
+ updateKeys((byte) (password[i] & 0xff));
+ }
+ }
+
+ public void updateKeys(byte charAt) {
+ keys[0] = crc32(keys[0], charAt);
+ keys[1] += keys[0] & 0xff;
+ keys[1] = keys[1] * 134775813 + 1;
+ keys[2] = crc32(keys[2], (byte) (keys[1] >> 24));
+ }
+
+ private int crc32(int oldCrc, byte charAt) {
+ return ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff]);
+ }
+
+ public byte decryptByte() {
+ int temp = keys[2] | 2;
+ return (byte) ((temp * (temp ^ 1)) >>> 8);
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/exception/ZipException.java b/harmony/src/main/java/com/zip4j/exception/ZipException.java
new file mode 100644
index 0000000..01e4796
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/exception/ZipException.java
@@ -0,0 +1,59 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.exception;
+
+public class ZipException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private int code = -1;
+
+ public ZipException() {
+ }
+
+ public ZipException(String msg) {
+ super(msg);
+ }
+
+ public ZipException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ZipException(String msg, int code) {
+ super(msg);
+ this.code = code;
+ }
+
+ public ZipException(String message, Throwable cause, int code) {
+ super(message, cause);
+ this.code = code;
+ }
+
+ public ZipException(Throwable cause) {
+ super(cause);
+ }
+
+ public ZipException(Throwable cause, int code) {
+ super(cause);
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/exception/ZipExceptionConstants.java b/harmony/src/main/java/com/zip4j/exception/ZipExceptionConstants.java
new file mode 100644
index 0000000..840681c
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/exception/ZipExceptionConstants.java
@@ -0,0 +1,31 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.exception;
+
+public interface ZipExceptionConstants {
+
+ public static int inputZipParamIsNull = 0001;
+
+ public static int constuctorFileNotFoundException = 0002;
+
+ public static int randomAccessFileNull = 0003;
+
+ public static int notZipFile = 0004;
+
+ public static int WRONG_PASSWORD = 0005;
+
+}
diff --git a/harmony/src/main/java/com/zip4j/io/BaseInputStream.java b/harmony/src/main/java/com/zip4j/io/BaseInputStream.java
new file mode 100644
index 0000000..3751e8b
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/BaseInputStream.java
@@ -0,0 +1,25 @@
+package com.zip4j.io;
+
+import com.zip4j.unzip.UnzipEngine;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class BaseInputStream extends InputStream {
+
+ public int read() throws IOException {
+ return 0;
+ }
+
+ public void seek(long pos) throws IOException {
+ }
+
+ public int available() throws IOException {
+ return 0;
+ }
+
+ public UnzipEngine getUnzipEngine() {
+ return null;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/io/BaseOutputStream.java b/harmony/src/main/java/com/zip4j/io/BaseOutputStream.java
new file mode 100644
index 0000000..7e0adc1
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/BaseOutputStream.java
@@ -0,0 +1,27 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public abstract class BaseOutputStream extends OutputStream {
+
+ public void write(int b) throws IOException {
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/io/CipherOutputStream.java b/harmony/src/main/java/com/zip4j/io/CipherOutputStream.java
new file mode 100644
index 0000000..d266adb
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/CipherOutputStream.java
@@ -0,0 +1,574 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.zip.CRC32;
+
+import com.zip4j.core.HeaderWriter;
+import com.zip4j.crypto.AESEncrpyter;
+import com.zip4j.crypto.IEncrypter;
+import com.zip4j.crypto.StandardEncrypter;
+import com.zip4j.exception.ZipException;
+import com.zip4j.model.AESExtraDataRecord;
+import com.zip4j.model.CentralDirectory;
+import com.zip4j.model.EndCentralDirRecord;
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.LocalFileHeader;
+import com.zip4j.model.ZipModel;
+import com.zip4j.model.ZipParameters;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Raw;
+import com.zip4j.util.Zip4jConstants;
+import com.zip4j.util.Zip4jUtil;
+
+public class CipherOutputStream extends BaseOutputStream {
+
+ protected OutputStream outputStream;
+ private File sourceFile;
+ protected FileHeader fileHeader;
+ protected LocalFileHeader localFileHeader;
+ private IEncrypter encrypter;
+ protected ZipParameters zipParameters;
+ protected ZipModel zipModel;
+ private long totalBytesWritten;
+ protected CRC32 crc;
+ private long bytesWrittenForThisFile;
+ private byte[] pendingBuffer;
+ private int pendingBufferLength;
+ private long totalBytesRead;
+
+ public CipherOutputStream(OutputStream outputStream, ZipModel zipModel) {
+ this.outputStream = outputStream;
+ initZipModel(zipModel);
+ crc = new CRC32();
+ this.totalBytesWritten = 0;
+ this.bytesWrittenForThisFile = 0;
+ this.pendingBuffer = new byte[InternalZipConstants.AES_BLOCK_SIZE];
+ this.pendingBufferLength = 0;
+ this.totalBytesRead = 0;
+ }
+
+ public void putNextEntry(File file, ZipParameters zipParameters) throws ZipException {
+ if (!zipParameters.isSourceExternalStream() && file == null) {
+ throw new ZipException("input file is null");
+ }
+
+ if (!zipParameters.isSourceExternalStream() && !Zip4jUtil.checkFileExists(file)) {
+ throw new ZipException("input file does not exist");
+ }
+
+ if (zipParameters == null) {
+ zipParameters = new ZipParameters();
+ }
+
+ try {
+ sourceFile = file;
+
+ this.zipParameters = (ZipParameters)zipParameters.clone();
+
+ if (!zipParameters.isSourceExternalStream()) {
+ if (sourceFile.isDirectory()) {
+ this.zipParameters.setEncryptFiles(false);
+ this.zipParameters.setEncryptionMethod(-1);
+ this.zipParameters.setCompressionMethod(Zip4jConstants.COMP_STORE);
+ }
+ } else {
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(this.zipParameters.getFileNameInZip())) {
+ throw new ZipException("file name is empty for external stream");
+ }
+ if (this.zipParameters.getFileNameInZip().endsWith("/") ||
+ this.zipParameters.getFileNameInZip().endsWith("\\")) {
+ this.zipParameters.setEncryptFiles(false);
+ this.zipParameters.setEncryptionMethod(-1);
+ this.zipParameters.setCompressionMethod(Zip4jConstants.COMP_STORE);
+ }
+ }
+
+ createFileHeader();
+ createLocalFileHeader();
+
+ if (zipModel.isSplitArchive()) {
+ if (zipModel.getCentralDirectory() == null ||
+ zipModel.getCentralDirectory().getFileHeaders() == null ||
+ zipModel.getCentralDirectory().getFileHeaders().size() == 0) {
+ byte[] intByte = new byte[4];
+ Raw.writeIntLittleEndian(intByte, 0, (int)InternalZipConstants.SPLITSIG);
+ outputStream.write(intByte);
+ totalBytesWritten += 4;
+ }
+ }
+
+ if (this.outputStream instanceof SplitOutputStream) {
+ if (totalBytesWritten == 4) {
+ fileHeader.setOffsetLocalHeader(4);
+ } else {
+ fileHeader.setOffsetLocalHeader(((SplitOutputStream)outputStream).getFilePointer());
+ }
+ } else {
+ if (totalBytesWritten == 4) {
+ fileHeader.setOffsetLocalHeader(4);
+ } else {
+ fileHeader.setOffsetLocalHeader(totalBytesWritten);
+ }
+ }
+
+ HeaderWriter headerWriter = new HeaderWriter();
+ totalBytesWritten += headerWriter.writeLocalFileHeader(zipModel, localFileHeader, outputStream);
+
+ if (this.zipParameters.isEncryptFiles()) {
+ initEncrypter();
+ if (encrypter != null) {
+ if (zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ byte[] headerBytes = ((StandardEncrypter)encrypter).getHeaderBytes();
+ outputStream.write(headerBytes);
+ totalBytesWritten += headerBytes.length;
+ bytesWrittenForThisFile += headerBytes.length;
+ } else if (zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ byte[] saltBytes = ((AESEncrpyter)encrypter).getSaltBytes();
+ byte[] passwordVerifier = ((AESEncrpyter)encrypter).getDerivedPasswordVerifier();
+ outputStream.write(saltBytes);
+ outputStream.write(passwordVerifier);
+ totalBytesWritten += saltBytes.length + passwordVerifier.length;
+ bytesWrittenForThisFile += saltBytes.length + passwordVerifier.length;
+ }
+ }
+ }
+
+ crc.reset();
+ } catch (CloneNotSupportedException e) {
+ throw new ZipException(e);
+ } catch (ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void initEncrypter() throws ZipException {
+ if (!zipParameters.isEncryptFiles()) {
+ encrypter = null;
+ return;
+ }
+
+ switch (zipParameters.getEncryptionMethod()) {
+ case Zip4jConstants.ENC_METHOD_STANDARD:
+ // Since we do not know the crc here, we use the modification time for encrypting.
+ encrypter = new StandardEncrypter(zipParameters.getPassword(), (localFileHeader.getLastModFileTime() & 0x0000ffff) << 16);
+ break;
+ case Zip4jConstants.ENC_METHOD_AES:
+ encrypter = new AESEncrpyter(zipParameters.getPassword(), zipParameters.getAesKeyStrength());
+ break;
+ default:
+ throw new ZipException("invalid encprytion method");
+ }
+ }
+
+ private void initZipModel(ZipModel zipModel) {
+ if (zipModel == null) {
+ this.zipModel = new ZipModel();
+ } else {
+ this.zipModel = zipModel;
+ }
+
+ if (this.zipModel.getEndCentralDirRecord() == null)
+ this.zipModel.setEndCentralDirRecord(new EndCentralDirRecord());
+
+ if (this.zipModel.getCentralDirectory() == null)
+ this.zipModel.setCentralDirectory(new CentralDirectory());
+
+ if (this.zipModel.getCentralDirectory().getFileHeaders() == null)
+ this.zipModel.getCentralDirectory().setFileHeaders(new ArrayList());
+
+ if (this.zipModel.getLocalFileHeaderList() == null)
+ this.zipModel.setLocalFileHeaderList(new ArrayList());
+
+ if (this.outputStream instanceof SplitOutputStream) {
+ if (((SplitOutputStream)outputStream).isSplitZipFile()) {
+ this.zipModel.setSplitArchive(true);
+ this.zipModel.setSplitLength(((SplitOutputStream)outputStream).getSplitLength());
+ }
+ }
+
+ this.zipModel.getEndCentralDirRecord().setSignature(InternalZipConstants.ENDSIG);
+ }
+
+ public void write(int bval) throws IOException {
+ byte[] b = new byte[1];
+ b[0] = (byte) bval;
+ write(b, 0, 1);
+ }
+
+ public void write(byte[] b) throws IOException {
+ if (b == null)
+ throw new NullPointerException();
+
+ if (b.length == 0) return;
+
+ write(b, 0, b.length);
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (len == 0) return;
+
+ if (zipParameters.isEncryptFiles() &&
+ zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ if (pendingBufferLength != 0) {
+ if (len >= (InternalZipConstants.AES_BLOCK_SIZE - pendingBufferLength)) {
+ System.arraycopy(b, off, pendingBuffer, pendingBufferLength,
+ (InternalZipConstants.AES_BLOCK_SIZE - pendingBufferLength));
+ encryptAndWrite(pendingBuffer, 0, pendingBuffer.length);
+ off = (InternalZipConstants.AES_BLOCK_SIZE - pendingBufferLength);
+ len = len - off;
+ pendingBufferLength = 0;
+ } else {
+ System.arraycopy(b, off, pendingBuffer, pendingBufferLength,
+ len);
+ pendingBufferLength += len;
+ return;
+ }
+ }
+ if (len != 0 && len % 16 != 0) {
+ System.arraycopy(b, (len + off) - (len % 16), pendingBuffer, 0, len % 16);
+ pendingBufferLength = len % 16;
+ len = len - pendingBufferLength;
+ }
+ }
+ if (len != 0)
+ encryptAndWrite(b, off, len);
+ }
+
+ private void encryptAndWrite(byte[] b, int off, int len) throws IOException {
+ if (encrypter != null) {
+ try {
+ encrypter.encryptData(b, off, len);
+ } catch (ZipException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+ outputStream.write(b, off, len);
+ totalBytesWritten += len;
+ bytesWrittenForThisFile += len;
+ }
+
+ public void closeEntry() throws IOException, ZipException {
+
+ if (this.pendingBufferLength != 0) {
+ encryptAndWrite(pendingBuffer, 0, pendingBufferLength);
+ pendingBufferLength = 0;
+ }
+
+ if (this.zipParameters.isEncryptFiles() &&
+ this.zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ if (encrypter instanceof AESEncrpyter) {
+ outputStream.write(((AESEncrpyter)encrypter).getFinalMac());
+ bytesWrittenForThisFile += 10;
+ totalBytesWritten += 10;
+ } else {
+ throw new ZipException("invalid encrypter for AES encrypted file");
+ }
+ }
+ fileHeader.setCompressedSize(bytesWrittenForThisFile);
+ localFileHeader.setCompressedSize(bytesWrittenForThisFile);
+
+ if (zipParameters.isSourceExternalStream()) {
+ fileHeader.setUncompressedSize(totalBytesRead);
+ if (localFileHeader.getUncompressedSize() != totalBytesRead) {
+ localFileHeader.setUncompressedSize(totalBytesRead);
+ }
+ }
+
+ long crc32 = crc.getValue();
+ if (fileHeader.isEncrypted()) {
+ if (fileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ crc32 = 0;
+ }
+ }
+
+ if (zipParameters.isEncryptFiles() &&
+ zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ fileHeader.setCrc32(0);
+ localFileHeader.setCrc32(0);
+ } else {
+ fileHeader.setCrc32(crc32);
+ localFileHeader.setCrc32(crc32);
+ }
+
+ zipModel.getLocalFileHeaderList().add(localFileHeader);
+ zipModel.getCentralDirectory().getFileHeaders().add(fileHeader);
+
+ HeaderWriter headerWriter = new HeaderWriter();
+ totalBytesWritten += headerWriter.writeExtendedLocalHeader(localFileHeader, outputStream);
+
+ crc.reset();
+ bytesWrittenForThisFile = 0;
+ encrypter = null;
+ totalBytesRead = 0;
+ }
+
+ public void finish() throws IOException, ZipException {
+ zipModel.getEndCentralDirRecord().setOffsetOfStartOfCentralDir(totalBytesWritten);
+
+ HeaderWriter headerWriter = new HeaderWriter();
+ headerWriter.finalizeZipFile(zipModel, outputStream);
+ }
+
+ public void close() throws IOException {
+ if (outputStream != null)
+ outputStream.close();
+ }
+
+ private void createFileHeader() throws ZipException {
+ this.fileHeader = new FileHeader();
+ fileHeader.setSignature((int)InternalZipConstants.CENSIG);
+ fileHeader.setVersionMadeBy(20);
+ fileHeader.setVersionNeededToExtract(20);
+ if (zipParameters.isEncryptFiles() &&
+ zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ fileHeader.setCompressionMethod(Zip4jConstants.ENC_METHOD_AES);
+ fileHeader.setAesExtraDataRecord(generateAESExtraDataRecord(zipParameters));
+ } else {
+ fileHeader.setCompressionMethod(zipParameters.getCompressionMethod());
+ }
+ if (zipParameters.isEncryptFiles()) {
+ fileHeader.setEncrypted(true);
+ fileHeader.setEncryptionMethod(zipParameters.getEncryptionMethod());
+ }
+ String fileName = null;
+ if (zipParameters.isSourceExternalStream()) {
+ fileHeader.setLastModFileTime((int) Zip4jUtil.javaToDosTime(System.currentTimeMillis()));
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(zipParameters.getFileNameInZip())) {
+ throw new ZipException("fileNameInZip is null or empty");
+ }
+ fileName = zipParameters.getFileNameInZip();
+ } else {
+ fileHeader.setLastModFileTime((int) Zip4jUtil.javaToDosTime((Zip4jUtil.getLastModifiedFileTime(
+ sourceFile, zipParameters.getTimeZone()))));
+ fileHeader.setUncompressedSize(sourceFile.length());
+ fileName = Zip4jUtil.getRelativeFileName(
+ sourceFile.getAbsolutePath(), zipParameters.getRootFolderInZip(), zipParameters.getDefaultFolderPath());
+
+ }
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(fileName)) {
+ throw new ZipException("fileName is null or empty. unable to create file header");
+ }
+
+ fileHeader.setFileName(fileName);
+
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getFileNameCharset())) {
+ fileHeader.setFileNameLength(Zip4jUtil.getEncodedStringLength(fileName,
+ zipModel.getFileNameCharset()));
+ } else {
+ fileHeader.setFileNameLength(Zip4jUtil.getEncodedStringLength(fileName));
+ }
+
+ if (outputStream instanceof SplitOutputStream) {
+ fileHeader.setDiskNumberStart(((SplitOutputStream)outputStream).getCurrSplitFileCounter());
+ } else {
+ fileHeader.setDiskNumberStart(0);
+ }
+
+ int fileAttrs = 0;
+ if (!zipParameters.isSourceExternalStream())
+ fileAttrs = getFileAttributes(sourceFile);
+ byte[] externalFileAttrs = {(byte)fileAttrs, 0, 0, 0};
+ fileHeader.setExternalFileAttr(externalFileAttrs);
+
+ if (zipParameters.isSourceExternalStream()) {
+ fileHeader.setDirectory(fileName.endsWith("/") || fileName.endsWith("\\"));
+ } else {
+ fileHeader.setDirectory(this.sourceFile.isDirectory());
+ }
+ if (fileHeader.isDirectory()) {
+ fileHeader.setCompressedSize(0);
+ fileHeader.setUncompressedSize(0);
+ } else {
+ if (!zipParameters.isSourceExternalStream()) {
+ long fileSize = Zip4jUtil.getFileLengh(sourceFile);
+ if (zipParameters.getCompressionMethod() == Zip4jConstants.COMP_STORE) {
+ if (zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ fileHeader.setCompressedSize(fileSize
+ + InternalZipConstants.STD_DEC_HDR_SIZE);
+ } else if (zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ int saltLength = 0;
+ switch (zipParameters.getAesKeyStrength()) {
+ case Zip4jConstants.AES_STRENGTH_128:
+ saltLength = 8;
+ break;
+ case Zip4jConstants.AES_STRENGTH_256:
+ saltLength = 16;
+ break;
+ default:
+ throw new ZipException("invalid aes key strength, cannot determine key sizes");
+ }
+ fileHeader.setCompressedSize(fileSize + saltLength
+ + InternalZipConstants.AES_AUTH_LENGTH + 2); //2 is password verifier
+ } else {
+ fileHeader.setCompressedSize(0);
+ }
+ } else {
+ fileHeader.setCompressedSize(0);
+ }
+ fileHeader.setUncompressedSize(fileSize);
+ }
+ }
+ if (zipParameters.isEncryptFiles() &&
+ zipParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ fileHeader.setCrc32(zipParameters.getSourceFileCRC());
+ }
+ byte[] shortByte = new byte[2];
+ shortByte[0] = Raw.bitArrayToByte(generateGeneralPurposeBitArray(
+ fileHeader.isEncrypted(), zipParameters.getCompressionMethod()));
+ boolean isFileNameCharsetSet = Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getFileNameCharset());
+ if ((isFileNameCharsetSet &&
+ zipModel.getFileNameCharset().equalsIgnoreCase(InternalZipConstants.CHARSET_UTF8)) ||
+ (!isFileNameCharsetSet &&
+ Zip4jUtil.detectCharSet(fileHeader.getFileName()).equals(InternalZipConstants.CHARSET_UTF8))) {
+ shortByte[1] = 8;
+ } else {
+ shortByte[1] = 0;
+ }
+ fileHeader.setGeneralPurposeFlag(shortByte);
+ }
+
+ private void createLocalFileHeader() throws ZipException {
+ if (fileHeader == null) {
+ throw new ZipException("file header is null, cannot create local file header");
+ }
+ this.localFileHeader = new LocalFileHeader();
+ localFileHeader.setSignature((int)InternalZipConstants.LOCSIG);
+ localFileHeader.setVersionNeededToExtract(fileHeader.getVersionNeededToExtract());
+ localFileHeader.setCompressionMethod(fileHeader.getCompressionMethod());
+ localFileHeader.setLastModFileTime(fileHeader.getLastModFileTime());
+ localFileHeader.setUncompressedSize(fileHeader.getUncompressedSize());
+ localFileHeader.setFileNameLength(fileHeader.getFileNameLength());
+ localFileHeader.setFileName(fileHeader.getFileName());
+ localFileHeader.setEncrypted(fileHeader.isEncrypted());
+ localFileHeader.setEncryptionMethod(fileHeader.getEncryptionMethod());
+ localFileHeader.setAesExtraDataRecord(fileHeader.getAesExtraDataRecord());
+ localFileHeader.setCrc32(fileHeader.getCrc32());
+ localFileHeader.setCompressedSize(fileHeader.getCompressedSize());
+ localFileHeader.setGeneralPurposeFlag((byte[])fileHeader.getGeneralPurposeFlag().clone());
+ }
+
+ /**
+ * Checks the file attributes and returns an integer
+ * @param file
+ * @return
+ * @throws ZipException
+ */
+ private int getFileAttributes(File file) throws ZipException {
+ if (file == null) {
+ throw new ZipException("input file is null, cannot get file attributes");
+ }
+
+ if (!file.exists()) {
+ return 0;
+ }
+
+ if (file.isDirectory()) {
+ if (file.isHidden()) {
+ return InternalZipConstants.FOLDER_MODE_HIDDEN;
+ } else {
+ return InternalZipConstants.FOLDER_MODE_NONE;
+ }
+ } else {
+ if (!file.canWrite() && file.isHidden()) {
+ return InternalZipConstants.FILE_MODE_READ_ONLY_HIDDEN;
+ } else if (!file.canWrite()) {
+ return InternalZipConstants.FILE_MODE_READ_ONLY;
+ } else if (file.isHidden()) {
+ return InternalZipConstants.FILE_MODE_HIDDEN;
+ } else {
+ return InternalZipConstants.FILE_MODE_NONE;
+ }
+ }
+ }
+
+ private int[] generateGeneralPurposeBitArray(boolean isEncrpyted, int compressionMethod) {
+
+ int[] generalPurposeBits = new int[8];
+ if (isEncrpyted) {
+ generalPurposeBits[0] = 1;
+ } else {
+ generalPurposeBits[0] = 0;
+ }
+
+ if (compressionMethod == Zip4jConstants.COMP_DEFLATE) {
+ // Have to set flags for deflate
+ } else {
+ generalPurposeBits[1] = 0;
+ generalPurposeBits[2] = 0;
+ }
+
+ generalPurposeBits[3] = 1;
+
+ return generalPurposeBits;
+ }
+
+ private AESExtraDataRecord generateAESExtraDataRecord(ZipParameters parameters) throws ZipException {
+
+ if (parameters == null) {
+ throw new ZipException("zip parameters are null, cannot generate AES Extra Data record");
+ }
+
+ AESExtraDataRecord aesDataRecord = new AESExtraDataRecord();
+ aesDataRecord.setSignature(InternalZipConstants.AESSIG);
+ aesDataRecord.setDataSize(7);
+ aesDataRecord.setVendorID("AE");
+ // Always set the version number to 2 as we do not store CRC for any AES encrypted files
+ // only MAC is stored and as per the specification, if version number is 2, then MAC is read
+ // and CRC is ignored
+ aesDataRecord.setVersionNumber(2);
+ if (parameters.getAesKeyStrength() == Zip4jConstants.AES_STRENGTH_128) {
+ aesDataRecord.setAesStrength(Zip4jConstants.AES_STRENGTH_128);
+ } else if (parameters.getAesKeyStrength() == Zip4jConstants.AES_STRENGTH_256) {
+ aesDataRecord.setAesStrength(Zip4jConstants.AES_STRENGTH_256);
+ } else {
+ throw new ZipException("invalid AES key strength, cannot generate AES Extra data record");
+ }
+ aesDataRecord.setCompressionMethod(parameters.getCompressionMethod());
+
+ return aesDataRecord;
+ }
+
+ public void decrementCompressedFileSize(int value) {
+ if (value <= 0) return;
+
+ if (value <= this.bytesWrittenForThisFile) {
+ this.bytesWrittenForThisFile -= value;
+ }
+ }
+
+ protected void updateTotalBytesRead(int toUpdate) {
+ if (toUpdate > 0) {
+ totalBytesRead += toUpdate;
+ }
+ }
+
+ public void setSourceFile(File sourceFile) {
+ this.sourceFile = sourceFile;
+ }
+
+ public File getSourceFile() {
+ return sourceFile;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/io/DeflaterOutputStream.java b/harmony/src/main/java/com/zip4j/io/DeflaterOutputStream.java
new file mode 100644
index 0000000..98b61b3
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/DeflaterOutputStream.java
@@ -0,0 +1,115 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.Deflater;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.model.ZipModel;
+import com.zip4j.model.ZipParameters;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Zip4jConstants;
+
+public class DeflaterOutputStream extends CipherOutputStream {
+
+ private byte[] buff;
+ protected Deflater deflater;
+ private boolean firstBytesRead;
+
+ public DeflaterOutputStream(OutputStream outputStream, ZipModel zipModel) {
+ super(outputStream, zipModel);
+ deflater = new Deflater();
+ buff = new byte[InternalZipConstants.BUFF_SIZE];
+ firstBytesRead = false;
+ }
+
+ public void putNextEntry(File file, ZipParameters zipParameters)
+ throws ZipException {
+ super.putNextEntry(file, zipParameters);
+ if (zipParameters.getCompressionMethod() == Zip4jConstants.COMP_DEFLATE) {
+ deflater.reset();
+ if ((zipParameters.getCompressionLevel() < 0 || zipParameters
+ .getCompressionLevel() > 9)
+ && zipParameters.getCompressionLevel() != -1) {
+ throw new ZipException(
+ "invalid compression level for deflater. compression level should be in the range of 0-9");
+ }
+ deflater.setLevel(zipParameters.getCompressionLevel());
+ }
+ }
+
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ private void deflate () throws IOException {
+ int len = deflater.deflate(buff, 0, buff.length);
+ if (len > 0) {
+ if (deflater.finished()) {
+ if (len == 4) return;
+ if (len < 4) {
+ decrementCompressedFileSize(4 - len);
+ return;
+ }
+ len -= 4;
+ }
+ if (!firstBytesRead) {
+ super.write(buff, 2, len - 2);
+ firstBytesRead = true;
+ } else {
+ super.write(buff, 0, len);
+ }
+ }
+ }
+
+ public void write(int bval) throws IOException {
+ byte[] b = new byte[1];
+ b[0] = (byte) bval;
+ write(b, 0, 1);
+ }
+
+ public void write(byte[] buf, int off, int len) throws IOException {
+ if (zipParameters.getCompressionMethod() != Zip4jConstants.COMP_DEFLATE) {
+ super.write(buf, off, len);
+ } else {
+ deflater.setInput(buf, off, len);
+ while (!deflater.needsInput()) {
+ deflate();
+ }
+ }
+ }
+
+ public void closeEntry() throws IOException, ZipException {
+ if (zipParameters.getCompressionMethod() == Zip4jConstants.COMP_DEFLATE) {
+ if (!deflater.finished()) {
+ deflater.finish();
+ while (!deflater.finished()) {
+ deflate();
+ }
+ }
+ firstBytesRead = false;
+ }
+ super.closeEntry();
+ }
+
+ public void finish() throws IOException, ZipException {
+ super.finish();
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/io/InflaterInputStream.java b/harmony/src/main/java/com/zip4j/io/InflaterInputStream.java
new file mode 100644
index 0000000..0d63901
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/InflaterInputStream.java
@@ -0,0 +1,161 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+import com.zip4j.unzip.UnzipEngine;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Zip4jConstants;
+
+public class InflaterInputStream extends PartInputStream {
+
+ private Inflater inflater;
+ private byte[] buff;
+ private byte[] oneByteBuff = new byte[1];
+ private UnzipEngine unzipEngine;
+ private long bytesWritten;
+ private long uncompressedSize;
+
+ public InflaterInputStream(RandomAccessFile raf, long start, long len, UnzipEngine unzipEngine) {
+ super(raf, start, len, unzipEngine);
+ this.inflater = new Inflater(true);
+ this.buff = new byte[InternalZipConstants.BUFF_SIZE];
+ this.unzipEngine = unzipEngine;
+ bytesWritten = 0;
+ uncompressedSize = unzipEngine.getFileHeader().getUncompressedSize();
+ }
+
+ public int read() throws IOException {
+ return read(oneByteBuff, 0, 1) == -1 ? -1 : oneByteBuff[0] & 0xff;
+ }
+
+ public int read(byte[] b) throws IOException {
+ if (b == null) {
+ throw new NullPointerException("input buffer is null");
+ }
+
+ return read(b, 0, b.length);
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+
+ if (b == null) {
+ throw new NullPointerException("input buffer is null");
+ } else if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ try {
+ int n;
+ if (bytesWritten >= uncompressedSize)
+ return -1;
+ while ((n = inflater.inflate(b, off, len)) == 0) {
+ if (inflater.finished() || inflater.needsDictionary()) {
+ return -1;
+ }
+ if (inflater.needsInput()) {
+ fill();
+ }
+ }
+ bytesWritten += n;
+ return n;
+ } catch (DataFormatException e) {
+ String s = "Invalid ZLIB data format";
+ if (e.getMessage() != null) {
+ s = e.getMessage();
+ }
+ if (unzipEngine != null) {
+ if (unzipEngine.getLocalFileHeader().isEncrypted() &&
+ unzipEngine.getLocalFileHeader().getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ s += " - Wrong Password?";
+ }
+ }
+ throw new IOException(s);
+ }
+ }
+
+ private void fill() throws IOException {
+ int len = super.read(buff, 0, buff.length);
+ if (len == -1) {
+ throw new EOFException("Unexpected end of ZLIB input stream");
+ }
+ inflater.setInput(buff, 0, len);
+ }
+
+ /**
+ * Skips specified number of bytes of uncompressed data.
+ * @param n the number of bytes to skip
+ * @return the actual number of bytes skipped.
+ * @exception IOException if an I/O error has occurred
+ * @exception IllegalArgumentException if n < 0
+ */
+ public long skip(long n) throws IOException {
+ if (n < 0) {
+ throw new IllegalArgumentException("negative skip length");
+ }
+ int max = (int)Math.min(n, Integer.MAX_VALUE);
+ int total = 0;
+ byte[] b = new byte[512];
+ while (total < max) {
+ int len = max - total;
+ if (len > b.length) {
+ len = b.length;
+ }
+ len = read(b, 0, len);
+ if (len == -1) {
+ break;
+ }
+ total += len;
+ }
+ return total;
+ }
+
+
+ public void seek(long pos) throws IOException {
+ super.seek(pos);
+ }
+
+ /**
+ * Returns 0 after EOF has been reached, otherwise always return 1.
+ *
+ * Programs should not count on this method to return the actual number
+ * of bytes that could be read without blocking.
+ *
+ * @return 1 before EOF and 0 after EOF.
+ * @exception IOException if an I/O error occurs.
+ *
+ */
+ public int available() {
+ return inflater.finished() ? 0 : 1;
+ }
+
+ public void close() throws IOException {
+ inflater.end();
+ super.close();
+ }
+
+ public UnzipEngine getUnzipEngine() {
+ return super.getUnzipEngine();
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/io/PartInputStream.java b/harmony/src/main/java/com/zip4j/io/PartInputStream.java
new file mode 100644
index 0000000..436668d
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/PartInputStream.java
@@ -0,0 +1,172 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.io;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import com.zip4j.crypto.AESDecrypter;
+import com.zip4j.crypto.IDecrypter;
+import com.zip4j.exception.ZipException;
+import com.zip4j.unzip.UnzipEngine;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Zip4jConstants;
+
+public class PartInputStream extends BaseInputStream
+{
+ private RandomAccessFile raf;
+ private long bytesRead, length;
+ private UnzipEngine unzipEngine;
+ private IDecrypter decrypter;
+ private byte[] oneByteBuff = new byte[1];
+ private byte[] aesBlockByte = new byte[16];
+ private int aesBytesReturned = 0;
+ private boolean isAESEncryptedFile = false;
+ private int count = -1;
+
+ public PartInputStream(RandomAccessFile raf, long start, long len, UnzipEngine unzipEngine) {
+ this.raf = raf;
+ this.unzipEngine = unzipEngine;
+ this.decrypter = unzipEngine.getDecrypter();
+ this.bytesRead = 0;
+ this.length = len;
+ this.isAESEncryptedFile = unzipEngine.getFileHeader().isEncrypted() &&
+ unzipEngine.getFileHeader().getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES;
+ }
+
+ public int available() {
+ long amount = length - bytesRead;
+ if (amount > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE;
+ return (int) amount;
+ }
+
+ public int read() throws IOException {
+ if (bytesRead >= length)
+ return -1;
+
+ if (isAESEncryptedFile) {
+ if (aesBytesReturned == 0 || aesBytesReturned == 16) {
+ if (read(aesBlockByte) == -1) {
+ return -1;
+ }
+ aesBytesReturned = 0;
+ }
+ return aesBlockByte[aesBytesReturned++] & 0xff;
+ } else {
+ return read(oneByteBuff, 0, 1) == -1 ? -1 : oneByteBuff[0] & 0xff;
+ }
+ }
+
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (len > length - bytesRead) {
+ len = (int) (length - bytesRead);
+ if (len == 0) {
+ checkAndReadAESMacBytes();
+ return -1;
+ }
+ }
+
+ if (unzipEngine.getDecrypter() instanceof AESDecrypter) {
+ if (bytesRead + len < length) {
+ if (len % 16 != 0) {
+ len = len - (len%16);
+ }
+ }
+ }
+
+ synchronized (raf) {
+ count = raf.read(b, off, len);
+ if ((count < len) && unzipEngine.getZipModel().isSplitArchive()) {
+ raf.close();
+ raf = unzipEngine.startNextSplitFile();
+ if (count < 0) count = 0;
+ int newlyRead = raf.read(b, count, len-count);
+ if (newlyRead > 0)
+ count += newlyRead;
+ }
+ }
+
+ if (count > 0) {
+ if (decrypter != null) {
+ try {
+ decrypter.decryptData(b, off, count);
+ } catch (ZipException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+ bytesRead += count;
+ }
+
+ if (bytesRead >= length) {
+ checkAndReadAESMacBytes();
+ }
+
+ return count;
+ }
+
+ private void checkAndReadAESMacBytes() throws IOException {
+ if (isAESEncryptedFile) {
+ if (decrypter != null && decrypter instanceof AESDecrypter) {
+ if (((AESDecrypter)decrypter).getStoredMac() != null) {
+ //Stored mac already set
+ return;
+ }
+ byte[] macBytes = new byte[InternalZipConstants.AES_AUTH_LENGTH];
+ int readLen = -1;
+ readLen = raf.read(macBytes);
+ if (readLen != InternalZipConstants.AES_AUTH_LENGTH) {
+ if (unzipEngine.getZipModel().isSplitArchive()) {
+ raf.close();
+ raf = unzipEngine.startNextSplitFile();
+ int newlyRead = raf.read(macBytes, readLen, InternalZipConstants.AES_AUTH_LENGTH - readLen);
+ readLen += newlyRead;
+ } else {
+ throw new IOException("Error occured while reading stored AES authentication bytes");
+ }
+ }
+
+ ((AESDecrypter)unzipEngine.getDecrypter()).setStoredMac(macBytes);
+ }
+ }
+ }
+
+ public long skip(long amount) throws IOException {
+ if (amount < 0)
+ throw new IllegalArgumentException();
+ if (amount > length - bytesRead)
+ amount = length - bytesRead;
+ bytesRead += amount;
+ return amount;
+ }
+
+ public void close() throws IOException {
+ raf.close();
+ }
+
+ public void seek(long pos) throws IOException {
+ raf.seek(pos);
+ }
+
+ public UnzipEngine getUnzipEngine() {
+ return this.unzipEngine;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/io/SplitOutputStream.java b/harmony/src/main/java/com/zip4j/io/SplitOutputStream.java
new file mode 100644
index 0000000..f601682
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/SplitOutputStream.java
@@ -0,0 +1,235 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Raw;
+import com.zip4j.util.Zip4jUtil;
+
+public class SplitOutputStream extends OutputStream {
+
+ private RandomAccessFile raf;
+ private long splitLength;
+ private File zipFile;
+ private File outFile;
+ private int currSplitFileCounter;
+ private long bytesWrittenForThisPart;
+
+ public SplitOutputStream(String name) throws FileNotFoundException, ZipException {
+ this(Zip4jUtil.isStringNotNullAndNotEmpty(name) ?
+ new File(name) : null);
+ }
+
+ public SplitOutputStream(File file) throws FileNotFoundException, ZipException {
+ this(file, -1);
+ }
+
+ public SplitOutputStream(String name, long splitLength) throws FileNotFoundException, ZipException {
+ this(!Zip4jUtil.isStringNotNullAndNotEmpty(name) ?
+ new File(name) : null, splitLength);
+ }
+
+ public SplitOutputStream(File file, long splitLength) throws FileNotFoundException, ZipException {
+ if (splitLength >= 0 && splitLength < InternalZipConstants.MIN_SPLIT_LENGTH) {
+ throw new ZipException("split length less than minimum allowed split length of " + InternalZipConstants.MIN_SPLIT_LENGTH +" Bytes");
+ }
+ this.raf = new RandomAccessFile(file, InternalZipConstants.WRITE_MODE);
+ this.splitLength = splitLength;
+ this.outFile = file;
+ this.zipFile = file;
+ this.currSplitFileCounter = 0;
+ this.bytesWrittenForThisPart = 0;
+ }
+
+ public void write(int b) throws IOException {
+ byte[] buff = new byte[1];
+ buff[0] = (byte) b;
+ write(buff, 0, 1);
+ }
+
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (len <= 0) return;
+
+ if (splitLength != -1) {
+
+ if (splitLength < InternalZipConstants.MIN_SPLIT_LENGTH) {
+ throw new IOException("split length less than minimum allowed split length of " + InternalZipConstants.MIN_SPLIT_LENGTH +" Bytes");
+ }
+
+ if (bytesWrittenForThisPart >= splitLength) {
+ startNextSplitFile();
+ raf.write(b, off, len);
+ bytesWrittenForThisPart = len;
+ } else if (bytesWrittenForThisPart + len > splitLength) {
+ if (isHeaderData(b)) {
+ startNextSplitFile();
+ raf.write(b, off, len);
+ bytesWrittenForThisPart = len;
+ } else {
+ raf.write(b, off, (int)(splitLength - bytesWrittenForThisPart));
+ startNextSplitFile();
+ raf.write(b, off + (int)(splitLength - bytesWrittenForThisPart), (int)(len - (splitLength - bytesWrittenForThisPart)));
+ bytesWrittenForThisPart = len - (splitLength - bytesWrittenForThisPart);
+ }
+ } else {
+ raf.write(b, off, len);
+ bytesWrittenForThisPart += len;
+ }
+
+ } else {
+ raf.write(b, off, len);
+ bytesWrittenForThisPart += len;
+ }
+
+ }
+
+ private void startNextSplitFile() throws IOException {
+ try {
+ String zipFileWithoutExt = Zip4jUtil.getZipFileNameWithoutExt(outFile.getName());
+ File currSplitFile = null;
+ String zipFileName = zipFile.getAbsolutePath();
+
+ if (currSplitFileCounter < 9) {
+ currSplitFile = new File(outFile.getParent() + System.getProperty("file.separator") +
+ zipFileWithoutExt + ".z0" + (currSplitFileCounter + 1));
+ } else {
+ currSplitFile = new File(outFile.getParent() + System.getProperty("file.separator") +
+ zipFileWithoutExt + ".z" + (currSplitFileCounter + 1));
+ }
+
+ raf.close();
+
+ if (currSplitFile.exists()) {
+ throw new IOException("split file: " + currSplitFile.getName() + " already exists in the current directory, cannot rename this file");
+ }
+
+ if (!zipFile.renameTo(currSplitFile)) {
+ throw new IOException("cannot rename newly created split file");
+ }
+
+ zipFile = new File(zipFileName);
+ raf = new RandomAccessFile(zipFile, InternalZipConstants.WRITE_MODE);
+ currSplitFileCounter++;
+ } catch (ZipException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ private boolean isHeaderData(byte[] buff) {
+ if (buff == null || buff.length < 4) {
+ return false;
+ }
+
+ int signature = Raw.readIntLittleEndian(buff, 0);
+ long[] allHeaderSignatures = Zip4jUtil.getAllHeaderSignatures();
+ if (allHeaderSignatures != null && allHeaderSignatures.length > 0) {
+ for (int i = 0; i < allHeaderSignatures.length; i++) {
+ //Ignore split signature
+ if (allHeaderSignatures[i] != InternalZipConstants.SPLITSIG &&
+ allHeaderSignatures[i] == signature) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if the buffer size is sufficient for the current split file. If not
+ * a new split file will be started.
+ * @param bufferSize
+ * @return true if a new split file was started else false
+ * @throws ZipException
+ */
+ public boolean checkBuffSizeAndStartNextSplitFile(int bufferSize) throws ZipException {
+ if (bufferSize < 0) {
+ throw new ZipException("negative buffersize for checkBuffSizeAndStartNextSplitFile");
+ }
+
+ if (!isBuffSizeFitForCurrSplitFile(bufferSize)) {
+ try {
+ startNextSplitFile();
+ bytesWrittenForThisPart = 0;
+ return true;
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if the given buffer size will be fit in the current split file.
+ * If this output stream is a non-split file, then this method always returns true
+ * @param bufferSize
+ * @return true if the buffer size is fit in the current split file or else false.
+ * @throws ZipException
+ */
+ public boolean isBuffSizeFitForCurrSplitFile(int bufferSize) throws ZipException {
+ if (bufferSize < 0) {
+ throw new ZipException("negative buffersize for isBuffSizeFitForCurrSplitFile");
+ }
+
+ if (splitLength >= InternalZipConstants.MIN_SPLIT_LENGTH) {
+ return (bytesWrittenForThisPart + bufferSize <= splitLength);
+ } else {
+ //Non split zip -- return true
+ return true;
+ }
+ }
+
+ public void seek(long pos) throws IOException {
+ raf.seek(pos);
+ }
+
+ public void close() throws IOException {
+ if (raf != null)
+ raf.close();
+ }
+
+ public void flush() throws IOException {
+ }
+
+ public long getFilePointer() throws IOException {
+ return raf.getFilePointer();
+ }
+
+ public boolean isSplitZipFile() {
+ return splitLength!=-1;
+ }
+
+ public long getSplitLength() {
+ return splitLength;
+ }
+
+ public int getCurrSplitFileCounter() {
+ return currSplitFileCounter;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/io/ZipInputStream.java b/harmony/src/main/java/com/zip4j/io/ZipInputStream.java
new file mode 100644
index 0000000..1e6c3aa
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/ZipInputStream.java
@@ -0,0 +1,89 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.io;
+
+import com.zip4j.exception.ZipException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ZipInputStream extends InputStream {
+
+ private BaseInputStream is;
+
+ public ZipInputStream(BaseInputStream is) {
+ this.is = is;
+ }
+
+ public int read() throws IOException {
+ int readByte = is.read();
+ if (readByte != -1) {
+ is.getUnzipEngine().updateCRC(readByte);
+ }
+ return readByte;
+ }
+
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ int readLen = is.read(b, off, len);
+ if (readLen > 0 && is.getUnzipEngine() != null) {
+ is.getUnzipEngine().updateCRC(b, off, readLen);
+ }
+ return readLen;
+ }
+
+ /**
+ * Closes the input stream and releases any resources.
+ * This method also checks for the CRC of the extracted file.
+ * If CRC check has to be skipped use close(boolean skipCRCCheck) method
+ *
+ * @throws IOException
+ */
+ public void close() throws IOException {
+ close(false);
+ }
+
+ /**
+ * Closes the input stream and releases any resources.
+ * If skipCRCCheck flag is set to true, this method skips CRC Check
+ * of the extracted file
+ *
+ * @throws IOException
+ */
+ public void close(boolean skipCRCCheck) throws IOException {
+ try {
+ is.close();
+ if (!skipCRCCheck && is.getUnzipEngine() != null) {
+ is.getUnzipEngine().checkCRC();
+ }
+ } catch (ZipException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ public int available() throws IOException {
+ return is.available();
+ }
+
+ public long skip(long n) throws IOException {
+ return is.skip(n);
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/io/ZipOutputStream.java b/harmony/src/main/java/com/zip4j/io/ZipOutputStream.java
new file mode 100644
index 0000000..1aa7d2d
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/io/ZipOutputStream.java
@@ -0,0 +1,33 @@
+package com.zip4j.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import com.zip4j.model.ZipModel;
+
+public class ZipOutputStream extends DeflaterOutputStream {
+
+ public ZipOutputStream(OutputStream outputStream) {
+ this(outputStream, null);
+ }
+
+ public ZipOutputStream(OutputStream outputStream, ZipModel zipModel) {
+ super(outputStream, zipModel);
+ }
+
+ public void write(int bval) throws IOException {
+ byte[] b = new byte[1];
+ b[0] = (byte) bval;
+ write(b, 0, 1);
+ }
+
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ crc.update(b, off, len);
+ updateTotalBytesRead(len);
+ super.write(b, off, len);
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/model/AESExtraDataRecord.java b/harmony/src/main/java/com/zip4j/model/AESExtraDataRecord.java
new file mode 100644
index 0000000..47f30b6
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/AESExtraDataRecord.java
@@ -0,0 +1,97 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class AESExtraDataRecord {
+
+ private long signature;
+ private int dataSize;
+ private int versionNumber;
+ private String vendorID;
+ private int aesStrength;
+ private int compressionMethod;
+
+ public AESExtraDataRecord() {
+ signature = -1;
+ dataSize = -1;
+ versionNumber = -1;
+ vendorID = null;
+ aesStrength = -1;
+ compressionMethod = -1;
+ }
+
+
+ public long getSignature() {
+ return signature;
+ }
+
+
+ public void setSignature(long signature) {
+ this.signature = signature;
+ }
+
+
+ public int getDataSize() {
+ return dataSize;
+ }
+
+
+ public void setDataSize(int dataSize) {
+ this.dataSize = dataSize;
+ }
+
+
+ public int getVersionNumber() {
+ return versionNumber;
+ }
+
+
+ public void setVersionNumber(int versionNumber) {
+ this.versionNumber = versionNumber;
+ }
+
+
+ public String getVendorID() {
+ return vendorID;
+ }
+
+
+ public void setVendorID(String vendorID) {
+ this.vendorID = vendorID;
+ }
+
+
+ public int getAesStrength() {
+ return aesStrength;
+ }
+
+
+ public void setAesStrength(int aesStrength) {
+ this.aesStrength = aesStrength;
+ }
+
+
+ public int getCompressionMethod() {
+ return compressionMethod;
+ }
+
+
+ public void setCompressionMethod(int compressionMethod) {
+ this.compressionMethod = compressionMethod;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/ArchiveExtraDataRecord.java b/harmony/src/main/java/com/zip4j/model/ArchiveExtraDataRecord.java
new file mode 100644
index 0000000..8fc3cee
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/ArchiveExtraDataRecord.java
@@ -0,0 +1,51 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class ArchiveExtraDataRecord {
+
+ private int signature;
+
+ private int extraFieldLength;
+
+ private String extraFieldData;
+
+ public int getSignature() {
+ return signature;
+ }
+
+ public void setSignature(int signature) {
+ this.signature = signature;
+ }
+
+ public int getExtraFieldLength() {
+ return extraFieldLength;
+ }
+
+ public void setExtraFieldLength(int extraFieldLength) {
+ this.extraFieldLength = extraFieldLength;
+ }
+
+ public String getExtraFieldData() {
+ return extraFieldData;
+ }
+
+ public void setExtraFieldData(String extraFieldData) {
+ this.extraFieldData = extraFieldData;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/CentralDirectory.java b/harmony/src/main/java/com/zip4j/model/CentralDirectory.java
new file mode 100644
index 0000000..9e44d73
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/CentralDirectory.java
@@ -0,0 +1,44 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+import java.util.ArrayList;
+
+public class CentralDirectory {
+
+ private ArrayList fileHeaders;
+
+ private DigitalSignature digitalSignature;
+
+ public ArrayList getFileHeaders() {
+ return fileHeaders;
+ }
+
+ public void setFileHeaders(ArrayList fileHeaders) {
+ this.fileHeaders = fileHeaders;
+ }
+
+ public DigitalSignature getDigitalSignature() {
+ return digitalSignature;
+ }
+
+ public void setDigitalSignature(DigitalSignature digitalSignature) {
+ this.digitalSignature = digitalSignature;
+ }
+
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/DataDescriptor.java b/harmony/src/main/java/com/zip4j/model/DataDescriptor.java
new file mode 100644
index 0000000..9a609f9
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/DataDescriptor.java
@@ -0,0 +1,51 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class DataDescriptor {
+
+ private String crc32;
+
+ private int compressedSize;
+
+ private int uncompressedSize;
+
+ public String getCrc32() {
+ return crc32;
+ }
+
+ public void setCrc32(String crc32) {
+ this.crc32 = crc32;
+ }
+
+ public int getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(int compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public int getUncompressedSize() {
+ return uncompressedSize;
+ }
+
+ public void setUncompressedSize(int uncompressedSize) {
+ this.uncompressedSize = uncompressedSize;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/DigitalSignature.java b/harmony/src/main/java/com/zip4j/model/DigitalSignature.java
new file mode 100644
index 0000000..7bdd202
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/DigitalSignature.java
@@ -0,0 +1,51 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class DigitalSignature {
+
+ private int headerSignature;
+
+ private int sizeOfData;
+
+ private String signatureData;
+
+ public int getHeaderSignature() {
+ return headerSignature;
+ }
+
+ public void setHeaderSignature(int headerSignature) {
+ this.headerSignature = headerSignature;
+ }
+
+ public int getSizeOfData() {
+ return sizeOfData;
+ }
+
+ public void setSizeOfData(int sizeOfData) {
+ this.sizeOfData = sizeOfData;
+ }
+
+ public String getSignatureData() {
+ return signatureData;
+ }
+
+ public void setSignatureData(String signatureData) {
+ this.signatureData = signatureData;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/EndCentralDirRecord.java b/harmony/src/main/java/com/zip4j/model/EndCentralDirRecord.java
new file mode 100644
index 0000000..ef06efa
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/EndCentralDirRecord.java
@@ -0,0 +1,122 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class EndCentralDirRecord {
+
+ private long signature;
+
+ private int noOfThisDisk;
+
+ private int noOfThisDiskStartOfCentralDir;
+
+ private int totNoOfEntriesInCentralDirOnThisDisk;
+
+ private int totNoOfEntriesInCentralDir;
+
+ private int sizeOfCentralDir;
+
+ private long offsetOfStartOfCentralDir;
+
+ private int commentLength;
+
+ private String comment;
+
+ private byte[] commentBytes;
+
+ public long getSignature() {
+ return signature;
+ }
+
+ public void setSignature(long signature) {
+ this.signature = signature;
+ }
+
+ public int getNoOfThisDisk() {
+ return noOfThisDisk;
+ }
+
+ public void setNoOfThisDisk(int noOfThisDisk) {
+ this.noOfThisDisk = noOfThisDisk;
+ }
+
+ public int getNoOfThisDiskStartOfCentralDir() {
+ return noOfThisDiskStartOfCentralDir;
+ }
+
+ public void setNoOfThisDiskStartOfCentralDir(int noOfThisDiskStartOfCentralDir) {
+ this.noOfThisDiskStartOfCentralDir = noOfThisDiskStartOfCentralDir;
+ }
+
+ public int getTotNoOfEntriesInCentralDirOnThisDisk() {
+ return totNoOfEntriesInCentralDirOnThisDisk;
+ }
+
+ public void setTotNoOfEntriesInCentralDirOnThisDisk(
+ int totNoOfEntriesInCentralDirOnThisDisk) {
+ this.totNoOfEntriesInCentralDirOnThisDisk = totNoOfEntriesInCentralDirOnThisDisk;
+ }
+
+ public int getTotNoOfEntriesInCentralDir() {
+ return totNoOfEntriesInCentralDir;
+ }
+
+ public void setTotNoOfEntriesInCentralDir(int totNoOfEntrisInCentralDir) {
+ this.totNoOfEntriesInCentralDir = totNoOfEntrisInCentralDir;
+ }
+
+ public int getSizeOfCentralDir() {
+ return sizeOfCentralDir;
+ }
+
+ public void setSizeOfCentralDir(int sizeOfCentralDir) {
+ this.sizeOfCentralDir = sizeOfCentralDir;
+ }
+
+ public long getOffsetOfStartOfCentralDir() {
+ return offsetOfStartOfCentralDir;
+ }
+
+ public void setOffsetOfStartOfCentralDir(long offSetOfStartOfCentralDir) {
+ this.offsetOfStartOfCentralDir = offSetOfStartOfCentralDir;
+ }
+
+ public int getCommentLength() {
+ return commentLength;
+ }
+
+ public void setCommentLength(int commentLength) {
+ this.commentLength = commentLength;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+ public byte[] getCommentBytes() {
+ return commentBytes;
+ }
+
+ public void setCommentBytes(byte[] commentBytes) {
+ this.commentBytes = commentBytes;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/ExtraDataRecord.java b/harmony/src/main/java/com/zip4j/model/ExtraDataRecord.java
new file mode 100644
index 0000000..b80ea37
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/ExtraDataRecord.java
@@ -0,0 +1,51 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class ExtraDataRecord {
+
+ private long header;
+
+ private int sizeOfData;
+
+ private byte[] data;
+
+ public long getHeader() {
+ return header;
+ }
+
+ public void setHeader(long header) {
+ this.header = header;
+ }
+
+ public int getSizeOfData() {
+ return sizeOfData;
+ }
+
+ public void setSizeOfData(int sizeOfData) {
+ this.sizeOfData = sizeOfData;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public void setData(byte[] data) {
+ this.data = data;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/FileHeader.java b/harmony/src/main/java/com/zip4j/model/FileHeader.java
new file mode 100644
index 0000000..6298dea
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/FileHeader.java
@@ -0,0 +1,369 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+import java.util.ArrayList;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.progress.ProgressMonitor;
+import com.zip4j.unzip.Unzip;
+import com.zip4j.util.Zip4jUtil;
+
+public class FileHeader {
+
+ private int signature;
+
+ private int versionMadeBy;
+
+ private int versionNeededToExtract;
+
+ private byte[] generalPurposeFlag;
+
+ private int compressionMethod;
+
+ private int lastModFileTime;
+
+ private long crc32;
+
+ private byte[] crcBuff;
+
+ private long compressedSize;
+
+ private long uncompressedSize;
+
+ private int fileNameLength;
+
+ private int extraFieldLength;
+
+ private int fileCommentLength;
+
+ private int diskNumberStart;
+
+ private byte[] internalFileAttr;
+
+ private byte[] externalFileAttr;
+
+ private long offsetLocalHeader;
+
+ private String fileName;
+
+ private String fileComment;
+
+ private boolean isDirectory;
+
+ private boolean isEncrypted;
+
+ private int encryptionMethod;
+
+ private char[] password;
+
+ private boolean dataDescriptorExists;
+
+ private Zip64ExtendedInfo zip64ExtendedInfo;
+
+ private AESExtraDataRecord aesExtraDataRecord;
+
+ private ArrayList extraDataRecords;
+
+ private boolean fileNameUTF8Encoded;
+
+ public FileHeader() {
+ encryptionMethod = -1;
+ crc32 = 0;
+ uncompressedSize = 0;
+ }
+
+ public int getSignature() {
+ return signature;
+ }
+
+ public void setSignature(int signature) {
+ this.signature = signature;
+ }
+
+ public int getVersionMadeBy() {
+ return versionMadeBy;
+ }
+
+ public void setVersionMadeBy(int versionMadeBy) {
+ this.versionMadeBy = versionMadeBy;
+ }
+
+ public int getVersionNeededToExtract() {
+ return versionNeededToExtract;
+ }
+
+ public void setVersionNeededToExtract(int versionNeededToExtract) {
+ this.versionNeededToExtract = versionNeededToExtract;
+ }
+
+ public byte[] getGeneralPurposeFlag() {
+ return generalPurposeFlag;
+ }
+
+ public void setGeneralPurposeFlag(byte[] generalPurposeFlag) {
+ this.generalPurposeFlag = generalPurposeFlag;
+ }
+
+ public int getCompressionMethod() {
+ return compressionMethod;
+ }
+
+ public void setCompressionMethod(int compressionMethod) {
+ this.compressionMethod = compressionMethod;
+ }
+
+ public int getLastModFileTime() {
+ return lastModFileTime;
+ }
+
+ public void setLastModFileTime(int lastModFileTime) {
+ this.lastModFileTime = lastModFileTime;
+ }
+
+ public long getCrc32() {
+ return crc32 & 0xffffffffL;
+ }
+
+ public void setCrc32(long crc32) {
+ this.crc32 = crc32;
+ }
+
+ public long getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(long compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public long getUncompressedSize() {
+ return uncompressedSize;
+ }
+
+ public void setUncompressedSize(long uncompressedSize) {
+ this.uncompressedSize = uncompressedSize;
+ }
+
+ public int getFileNameLength() {
+ return fileNameLength;
+ }
+
+ public void setFileNameLength(int fileNameLength) {
+ this.fileNameLength = fileNameLength;
+ }
+
+ public int getExtraFieldLength() {
+ return extraFieldLength;
+ }
+
+ public void setExtraFieldLength(int extraFieldLength) {
+ this.extraFieldLength = extraFieldLength;
+ }
+
+ public int getFileCommentLength() {
+ return fileCommentLength;
+ }
+
+ public void setFileCommentLength(int fileCommentLength) {
+ this.fileCommentLength = fileCommentLength;
+ }
+
+ public int getDiskNumberStart() {
+ return diskNumberStart;
+ }
+
+ public void setDiskNumberStart(int diskNumberStart) {
+ this.diskNumberStart = diskNumberStart;
+ }
+
+ public byte[] getInternalFileAttr() {
+ return internalFileAttr;
+ }
+
+ public void setInternalFileAttr(byte[] internalFileAttr) {
+ this.internalFileAttr = internalFileAttr;
+ }
+
+ public byte[] getExternalFileAttr() {
+ return externalFileAttr;
+ }
+
+ public void setExternalFileAttr(byte[] externalFileAttr) {
+ this.externalFileAttr = externalFileAttr;
+ }
+
+ public long getOffsetLocalHeader() {
+ return offsetLocalHeader;
+ }
+
+ public void setOffsetLocalHeader(long offsetLocalHeader) {
+ this.offsetLocalHeader = offsetLocalHeader;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ public String getFileComment() {
+ return fileComment;
+ }
+
+ public void setFileComment(String fileComment) {
+ this.fileComment = fileComment;
+ }
+
+ public boolean isDirectory() {
+ return isDirectory;
+ }
+
+ public void setDirectory(boolean isDirectory) {
+ this.isDirectory = isDirectory;
+ }
+
+ /**
+ * Extracts file to the specified directory
+ * @param zipModel
+ * @param outPath
+ * @throws ZipException
+ */
+ public void extractFile(ZipModel zipModel, String outPath,
+ ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+ extractFile(zipModel, outPath, null, progressMonitor, runInThread);
+ }
+
+ /**
+ * Extracts file to the specified directory using any
+ * user defined parameters in UnzipParameters
+ * @param zipModel
+ * @param outPath
+ * @param unzipParameters
+ * @throws ZipException
+ */
+ public void extractFile(ZipModel zipModel, String outPath,
+ UnzipParameters unzipParameters, ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+ extractFile(zipModel, outPath, unzipParameters, null, progressMonitor, runInThread);
+ }
+
+ /**
+ * Extracts file to the specified directory using any
+ * user defined parameters in UnzipParameters. Output file name
+ * will be overwritten with the value in newFileName. If this
+ * parameter is null, then file name will be the same as in
+ * FileHeader.getFileName
+ * @param zipModel
+ * @param outPath
+ * @param unzipParameters
+ * @throws ZipException
+ */
+ public void extractFile(ZipModel zipModel, String outPath,
+ UnzipParameters unzipParameters, String newFileName,
+ ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("input zipModel is null");
+ }
+
+ if (!Zip4jUtil.checkOutputFolder(outPath)) {
+ throw new ZipException("Invalid output path");
+ }
+
+ if (this == null) {
+ throw new ZipException("invalid file header");
+ }
+ Unzip unzip = new Unzip(zipModel);
+ unzip.extractFile(this, outPath, unzipParameters, newFileName, progressMonitor, runInThread);
+ }
+
+ public boolean isEncrypted() {
+ return isEncrypted;
+ }
+
+ public void setEncrypted(boolean isEncrypted) {
+ this.isEncrypted = isEncrypted;
+ }
+
+ public int getEncryptionMethod() {
+ return encryptionMethod;
+ }
+
+ public void setEncryptionMethod(int encryptionMethod) {
+ this.encryptionMethod = encryptionMethod;
+ }
+
+ public char[] getPassword() {
+ return password;
+ }
+
+ public void setPassword(char[] password) {
+ this.password = password;
+ }
+
+ public byte[] getCrcBuff() {
+ return crcBuff;
+ }
+
+ public void setCrcBuff(byte[] crcBuff) {
+ this.crcBuff = crcBuff;
+ }
+
+ public ArrayList getExtraDataRecords() {
+ return extraDataRecords;
+ }
+
+ public void setExtraDataRecords(ArrayList extraDataRecords) {
+ this.extraDataRecords = extraDataRecords;
+ }
+
+ public boolean isDataDescriptorExists() {
+ return dataDescriptorExists;
+ }
+
+ public void setDataDescriptorExists(boolean dataDescriptorExists) {
+ this.dataDescriptorExists = dataDescriptorExists;
+ }
+
+ public Zip64ExtendedInfo getZip64ExtendedInfo() {
+ return zip64ExtendedInfo;
+ }
+
+ public void setZip64ExtendedInfo(Zip64ExtendedInfo zip64ExtendedInfo) {
+ this.zip64ExtendedInfo = zip64ExtendedInfo;
+ }
+
+ public AESExtraDataRecord getAesExtraDataRecord() {
+ return aesExtraDataRecord;
+ }
+
+ public void setAesExtraDataRecord(AESExtraDataRecord aesExtraDataRecord) {
+ this.aesExtraDataRecord = aesExtraDataRecord;
+ }
+
+ public boolean isFileNameUTF8Encoded() {
+ return fileNameUTF8Encoded;
+ }
+
+ public void setFileNameUTF8Encoded(boolean fileNameUTF8Encoded) {
+ this.fileNameUTF8Encoded = fileNameUTF8Encoded;
+ }
+
+
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/LocalFileHeader.java b/harmony/src/main/java/com/zip4j/model/LocalFileHeader.java
new file mode 100644
index 0000000..856d57e
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/LocalFileHeader.java
@@ -0,0 +1,261 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+import java.util.ArrayList;
+
+public class LocalFileHeader {
+
+ private int signature;
+
+ private int versionNeededToExtract;
+
+ private byte[] generalPurposeFlag;
+
+ private int compressionMethod;
+
+ private int lastModFileTime;
+
+ private long crc32;
+
+ private byte[] crcBuff;
+
+ private long compressedSize;
+
+ private long uncompressedSize;
+
+ private int fileNameLength;
+
+ private int extraFieldLength;
+
+ private String fileName;
+
+ private byte[] extraField;
+
+ private long offsetStartOfData;
+
+ private boolean isEncrypted;
+
+ private int encryptionMethod;
+
+ private char[] password;
+
+ private ArrayList extraDataRecords;
+
+ private Zip64ExtendedInfo zip64ExtendedInfo;
+
+ private AESExtraDataRecord aesExtraDataRecord;
+
+ private boolean dataDescriptorExists;
+
+ private boolean writeComprSizeInZip64ExtraRecord;
+
+ private boolean fileNameUTF8Encoded;
+
+ public LocalFileHeader() {
+ encryptionMethod = -1;
+ writeComprSizeInZip64ExtraRecord = false;
+ crc32 = 0;
+ uncompressedSize = 0;
+ }
+
+ public int getSignature() {
+ return signature;
+ }
+
+ public void setSignature(int signature) {
+ this.signature = signature;
+ }
+
+ public int getVersionNeededToExtract() {
+ return versionNeededToExtract;
+ }
+
+ public void setVersionNeededToExtract(int versionNeededToExtract) {
+ this.versionNeededToExtract = versionNeededToExtract;
+ }
+
+ public byte[] getGeneralPurposeFlag() {
+ return generalPurposeFlag;
+ }
+
+ public void setGeneralPurposeFlag(byte[] generalPurposeFlag) {
+ this.generalPurposeFlag = generalPurposeFlag;
+ }
+
+ public int getCompressionMethod() {
+ return compressionMethod;
+ }
+
+ public void setCompressionMethod(int compressionMethod) {
+ this.compressionMethod = compressionMethod;
+ }
+
+ public int getLastModFileTime() {
+ return lastModFileTime;
+ }
+
+ public void setLastModFileTime(int lastModFileTime) {
+ this.lastModFileTime = lastModFileTime;
+ }
+
+ public long getCrc32() {
+ return crc32;
+ }
+
+ public void setCrc32(long crc32) {
+ this.crc32 = crc32;
+ }
+
+ public long getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(long compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public long getUncompressedSize() {
+ return uncompressedSize;
+ }
+
+ public void setUncompressedSize(long uncompressedSize) {
+ this.uncompressedSize = uncompressedSize;
+ }
+
+ public int getFileNameLength() {
+ return fileNameLength;
+ }
+
+ public void setFileNameLength(int fileNameLength) {
+ this.fileNameLength = fileNameLength;
+ }
+
+ public int getExtraFieldLength() {
+ return extraFieldLength;
+ }
+
+ public void setExtraFieldLength(int extraFieldLength) {
+ this.extraFieldLength = extraFieldLength;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ public byte[] getExtraField() {
+ return extraField;
+ }
+
+ public void setExtraField(byte[] extraField) {
+ this.extraField = extraField;
+ }
+
+ public long getOffsetStartOfData() {
+ return offsetStartOfData;
+ }
+
+ public void setOffsetStartOfData(long offsetStartOfData) {
+ this.offsetStartOfData = offsetStartOfData;
+ }
+
+ public boolean isEncrypted() {
+ return isEncrypted;
+ }
+
+ public void setEncrypted(boolean isEncrypted) {
+ this.isEncrypted = isEncrypted;
+ }
+
+ public int getEncryptionMethod() {
+ return encryptionMethod;
+ }
+
+ public void setEncryptionMethod(int encryptionMethod) {
+ this.encryptionMethod = encryptionMethod;
+ }
+
+ public byte[] getCrcBuff() {
+ return crcBuff;
+ }
+
+ public void setCrcBuff(byte[] crcBuff) {
+ this.crcBuff = crcBuff;
+ }
+
+ public char[] getPassword() {
+ return password;
+ }
+
+ public void setPassword(char[] password) {
+ this.password = password;
+ }
+
+ public ArrayList getExtraDataRecords() {
+ return extraDataRecords;
+ }
+
+ public void setExtraDataRecords(ArrayList extraDataRecords) {
+ this.extraDataRecords = extraDataRecords;
+ }
+
+ public boolean isDataDescriptorExists() {
+ return dataDescriptorExists;
+ }
+
+ public void setDataDescriptorExists(boolean dataDescriptorExists) {
+ this.dataDescriptorExists = dataDescriptorExists;
+ }
+
+ public Zip64ExtendedInfo getZip64ExtendedInfo() {
+ return zip64ExtendedInfo;
+ }
+
+ public void setZip64ExtendedInfo(Zip64ExtendedInfo zip64ExtendedInfo) {
+ this.zip64ExtendedInfo = zip64ExtendedInfo;
+ }
+
+ public AESExtraDataRecord getAesExtraDataRecord() {
+ return aesExtraDataRecord;
+ }
+
+ public void setAesExtraDataRecord(AESExtraDataRecord aesExtraDataRecord) {
+ this.aesExtraDataRecord = aesExtraDataRecord;
+ }
+
+ public boolean isWriteComprSizeInZip64ExtraRecord() {
+ return writeComprSizeInZip64ExtraRecord;
+ }
+
+ public void setWriteComprSizeInZip64ExtraRecord(
+ boolean writeComprSizeInZip64ExtraRecord) {
+ this.writeComprSizeInZip64ExtraRecord = writeComprSizeInZip64ExtraRecord;
+ }
+
+ public boolean isFileNameUTF8Encoded() {
+ return fileNameUTF8Encoded;
+ }
+
+ public void setFileNameUTF8Encoded(boolean fileNameUTF8Encoded) {
+ this.fileNameUTF8Encoded = fileNameUTF8Encoded;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/UnzipEngineParameters.java b/harmony/src/main/java/com/zip4j/model/UnzipEngineParameters.java
new file mode 100644
index 0000000..d7d01f9
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/UnzipEngineParameters.java
@@ -0,0 +1,88 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+import java.io.FileOutputStream;
+
+import com.zip4j.crypto.IDecrypter;
+import com.zip4j.unzip.UnzipEngine;
+
+public class UnzipEngineParameters {
+
+ private ZipModel zipModel;
+
+ private FileHeader fileHeader;
+
+ private LocalFileHeader localFileHeader;
+
+ private IDecrypter iDecryptor;
+
+ private FileOutputStream outputStream;
+
+ private UnzipEngine unzipEngine;
+
+ public ZipModel getZipModel() {
+ return zipModel;
+ }
+
+ public void setZipModel(ZipModel zipModel) {
+ this.zipModel = zipModel;
+ }
+
+ public FileHeader getFileHeader() {
+ return fileHeader;
+ }
+
+ public void setFileHeader(FileHeader fileHeader) {
+ this.fileHeader = fileHeader;
+ }
+
+ public LocalFileHeader getLocalFileHeader() {
+ return localFileHeader;
+ }
+
+ public void setLocalFileHeader(LocalFileHeader localFileHeader) {
+ this.localFileHeader = localFileHeader;
+ }
+
+ public IDecrypter getIDecryptor() {
+ return iDecryptor;
+ }
+
+ public void setIDecryptor(IDecrypter decrypter) {
+ iDecryptor = decrypter;
+ }
+
+ public FileOutputStream getOutputStream() {
+ return outputStream;
+ }
+
+ public void setOutputStream(FileOutputStream outputStream) {
+ this.outputStream = outputStream;
+ }
+
+ public UnzipEngine getUnzipEngine() {
+ return unzipEngine;
+ }
+
+ public void setUnzipEngine(UnzipEngine unzipEngine) {
+ this.unzipEngine = unzipEngine;
+ }
+
+
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/UnzipParameters.java b/harmony/src/main/java/com/zip4j/model/UnzipParameters.java
new file mode 100644
index 0000000..09ac358
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/UnzipParameters.java
@@ -0,0 +1,77 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class UnzipParameters {
+
+ private boolean ignoreReadOnlyFileAttribute;
+ private boolean ignoreHiddenFileAttribute;
+ private boolean ignoreArchiveFileAttribute;
+ private boolean ignoreSystemFileAttribute;
+ private boolean ignoreAllFileAttributes;
+ private boolean ignoreDateTimeAttributes;
+
+ public boolean isIgnoreReadOnlyFileAttribute() {
+ return ignoreReadOnlyFileAttribute;
+ }
+
+ public void setIgnoreReadOnlyFileAttribute(boolean ignoreReadOnlyFileAttribute) {
+ this.ignoreReadOnlyFileAttribute = ignoreReadOnlyFileAttribute;
+ }
+
+ public boolean isIgnoreHiddenFileAttribute() {
+ return ignoreHiddenFileAttribute;
+ }
+
+ public void setIgnoreHiddenFileAttribute(boolean ignoreHiddenFileAttribute) {
+ this.ignoreHiddenFileAttribute = ignoreHiddenFileAttribute;
+ }
+
+ public boolean isIgnoreArchiveFileAttribute() {
+ return ignoreArchiveFileAttribute;
+ }
+
+ public void setIgnoreArchiveFileAttribute(boolean ignoreArchiveFileAttribute) {
+ this.ignoreArchiveFileAttribute = ignoreArchiveFileAttribute;
+ }
+
+ public boolean isIgnoreSystemFileAttribute() {
+ return ignoreSystemFileAttribute;
+ }
+
+ public void setIgnoreSystemFileAttribute(boolean ignoreSystemFileAttribute) {
+ this.ignoreSystemFileAttribute = ignoreSystemFileAttribute;
+ }
+
+ public boolean isIgnoreAllFileAttributes() {
+ return ignoreAllFileAttributes;
+ }
+
+ public void setIgnoreAllFileAttributes(boolean ignoreAllFileAttributes) {
+ this.ignoreAllFileAttributes = ignoreAllFileAttributes;
+ }
+
+ public boolean isIgnoreDateTimeAttributes() {
+ return ignoreDateTimeAttributes;
+ }
+
+ public void setIgnoreDateTimeAttributes(boolean ignoreDateTimeAttributes) {
+ this.ignoreDateTimeAttributes = ignoreDateTimeAttributes;
+ }
+
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/Zip64EndCentralDirLocator.java b/harmony/src/main/java/com/zip4j/model/Zip64EndCentralDirLocator.java
new file mode 100644
index 0000000..df2d584
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/Zip64EndCentralDirLocator.java
@@ -0,0 +1,64 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class Zip64EndCentralDirLocator {
+
+ private long signature;
+
+ private int noOfDiskStartOfZip64EndOfCentralDirRec;
+
+ private long offsetZip64EndOfCentralDirRec;
+
+ private int totNumberOfDiscs;
+
+ public long getSignature() {
+ return signature;
+ }
+
+ public void setSignature(long signature) {
+ this.signature = signature;
+ }
+
+ public int getNoOfDiskStartOfZip64EndOfCentralDirRec() {
+ return noOfDiskStartOfZip64EndOfCentralDirRec;
+ }
+
+ public void setNoOfDiskStartOfZip64EndOfCentralDirRec(
+ int noOfDiskStartOfZip64EndOfCentralDirRec) {
+ this.noOfDiskStartOfZip64EndOfCentralDirRec = noOfDiskStartOfZip64EndOfCentralDirRec;
+ }
+
+ public long getOffsetZip64EndOfCentralDirRec() {
+ return offsetZip64EndOfCentralDirRec;
+ }
+
+ public void setOffsetZip64EndOfCentralDirRec(long offsetZip64EndOfCentralDirRec) {
+ this.offsetZip64EndOfCentralDirRec = offsetZip64EndOfCentralDirRec;
+ }
+
+ public int getTotNumberOfDiscs() {
+ return totNumberOfDiscs;
+ }
+
+ public void setTotNumberOfDiscs(int totNumberOfDiscs) {
+ this.totNumberOfDiscs = totNumberOfDiscs;
+ }
+
+
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/Zip64EndCentralDirRecord.java b/harmony/src/main/java/com/zip4j/model/Zip64EndCentralDirRecord.java
new file mode 100644
index 0000000..7b6c5f9
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/Zip64EndCentralDirRecord.java
@@ -0,0 +1,135 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class Zip64EndCentralDirRecord {
+
+ private long signature;
+
+ private long sizeOfZip64EndCentralDirRec;
+
+ private int versionMadeBy;
+
+ private int versionNeededToExtract;
+
+ private int noOfThisDisk;
+
+ private int noOfThisDiskStartOfCentralDir;
+
+ private long totNoOfEntriesInCentralDirOnThisDisk;
+
+ private long totNoOfEntriesInCentralDir;
+
+ private long sizeOfCentralDir;
+
+ private long offsetStartCenDirWRTStartDiskNo;
+
+ private byte[] extensibleDataSector;
+
+ public long getSignature() {
+ return signature;
+ }
+
+ public void setSignature(long signature) {
+ this.signature = signature;
+ }
+
+ public long getSizeOfZip64EndCentralDirRec() {
+ return sizeOfZip64EndCentralDirRec;
+ }
+
+ public void setSizeOfZip64EndCentralDirRec(long sizeOfZip64EndCentralDirRec) {
+ this.sizeOfZip64EndCentralDirRec = sizeOfZip64EndCentralDirRec;
+ }
+
+ public int getVersionMadeBy() {
+ return versionMadeBy;
+ }
+
+ public void setVersionMadeBy(int versionMadeBy) {
+ this.versionMadeBy = versionMadeBy;
+ }
+
+ public int getVersionNeededToExtract() {
+ return versionNeededToExtract;
+ }
+
+ public void setVersionNeededToExtract(int versionNeededToExtract) {
+ this.versionNeededToExtract = versionNeededToExtract;
+ }
+
+ public int getNoOfThisDisk() {
+ return noOfThisDisk;
+ }
+
+ public void setNoOfThisDisk(int noOfThisDisk) {
+ this.noOfThisDisk = noOfThisDisk;
+ }
+
+ public int getNoOfThisDiskStartOfCentralDir() {
+ return noOfThisDiskStartOfCentralDir;
+ }
+
+ public void setNoOfThisDiskStartOfCentralDir(int noOfThisDiskStartOfCentralDir) {
+ this.noOfThisDiskStartOfCentralDir = noOfThisDiskStartOfCentralDir;
+ }
+
+ public long getTotNoOfEntriesInCentralDirOnThisDisk() {
+ return totNoOfEntriesInCentralDirOnThisDisk;
+ }
+
+ public void setTotNoOfEntriesInCentralDirOnThisDisk(
+ long totNoOfEntriesInCentralDirOnThisDisk) {
+ this.totNoOfEntriesInCentralDirOnThisDisk = totNoOfEntriesInCentralDirOnThisDisk;
+ }
+
+ public long getTotNoOfEntriesInCentralDir() {
+ return totNoOfEntriesInCentralDir;
+ }
+
+ public void setTotNoOfEntriesInCentralDir(long totNoOfEntriesInCentralDir) {
+ this.totNoOfEntriesInCentralDir = totNoOfEntriesInCentralDir;
+ }
+
+ public long getSizeOfCentralDir() {
+ return sizeOfCentralDir;
+ }
+
+ public void setSizeOfCentralDir(long sizeOfCentralDir) {
+ this.sizeOfCentralDir = sizeOfCentralDir;
+ }
+
+ public long getOffsetStartCenDirWRTStartDiskNo() {
+ return offsetStartCenDirWRTStartDiskNo;
+ }
+
+ public void setOffsetStartCenDirWRTStartDiskNo(
+ long offsetStartCenDirWRTStartDiskNo) {
+ this.offsetStartCenDirWRTStartDiskNo = offsetStartCenDirWRTStartDiskNo;
+ }
+
+ public byte[] getExtensibleDataSector() {
+ return extensibleDataSector;
+ }
+
+ public void setExtensibleDataSector(byte[] extensibleDataSector) {
+ this.extensibleDataSector = extensibleDataSector;
+ }
+
+
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/Zip64ExtendedInfo.java b/harmony/src/main/java/com/zip4j/model/Zip64ExtendedInfo.java
new file mode 100644
index 0000000..ddaf297
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/Zip64ExtendedInfo.java
@@ -0,0 +1,90 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+public class Zip64ExtendedInfo {
+
+ private int header;
+
+ private int size;
+
+ private long compressedSize;
+
+ private long unCompressedSize;
+
+ private long offsetLocalHeader;
+
+ private int diskNumberStart;
+
+ public Zip64ExtendedInfo() {
+ compressedSize = -1;
+ unCompressedSize = -1;
+ offsetLocalHeader = -1;
+ diskNumberStart = -1;
+ }
+
+ public int getHeader() {
+ return header;
+ }
+
+ public void setHeader(int header) {
+ this.header = header;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ public long getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(long compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public long getUnCompressedSize() {
+ return unCompressedSize;
+ }
+
+ public void setUnCompressedSize(long unCompressedSize) {
+ this.unCompressedSize = unCompressedSize;
+ }
+
+ public long getOffsetLocalHeader() {
+ return offsetLocalHeader;
+ }
+
+ public void setOffsetLocalHeader(long offsetLocalHeader) {
+ this.offsetLocalHeader = offsetLocalHeader;
+ }
+
+ public int getDiskNumberStart() {
+ return diskNumberStart;
+ }
+
+ public void setDiskNumberStart(int diskNumberStart) {
+ this.diskNumberStart = diskNumberStart;
+ }
+
+
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/ZipModel.java b/harmony/src/main/java/com/zip4j/model/ZipModel.java
new file mode 100644
index 0000000..fea0db7
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/ZipModel.java
@@ -0,0 +1,184 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+import java.util.List;
+
+public class ZipModel implements Cloneable {
+
+ private List localFileHeaderList;
+
+ private List dataDescriptorList;
+
+ private ArchiveExtraDataRecord archiveExtraDataRecord;
+
+ private CentralDirectory centralDirectory;
+
+ private EndCentralDirRecord endCentralDirRecord;
+
+ private Zip64EndCentralDirLocator zip64EndCentralDirLocator;
+
+ private Zip64EndCentralDirRecord zip64EndCentralDirRecord;
+
+ private boolean splitArchive;
+
+ private long splitLength;
+
+ private String zipFile;
+
+ private boolean isZip64Format;
+
+ private boolean isNestedZipFile;
+
+ private long start;
+
+ private long end;
+
+ private String fileNameCharset;
+
+ public ZipModel() {
+ splitLength = -1;
+ }
+
+ public List getLocalFileHeaderList() {
+ return localFileHeaderList;
+ }
+
+ public void setLocalFileHeaderList(List localFileHeaderList) {
+ this.localFileHeaderList = localFileHeaderList;
+ }
+
+ public List getDataDescriptorList() {
+ return dataDescriptorList;
+ }
+
+ public void setDataDescriptorList(List dataDescriptorList) {
+ this.dataDescriptorList = dataDescriptorList;
+ }
+
+ public CentralDirectory getCentralDirectory() {
+ return centralDirectory;
+ }
+
+ public void setCentralDirectory(CentralDirectory centralDirectory) {
+ this.centralDirectory = centralDirectory;
+ }
+
+ public EndCentralDirRecord getEndCentralDirRecord() {
+ return endCentralDirRecord;
+ }
+
+ public void setEndCentralDirRecord(EndCentralDirRecord endCentralDirRecord) {
+ this.endCentralDirRecord = endCentralDirRecord;
+ }
+
+ public ArchiveExtraDataRecord getArchiveExtraDataRecord() {
+ return archiveExtraDataRecord;
+ }
+
+ public void setArchiveExtraDataRecord(
+ ArchiveExtraDataRecord archiveExtraDataRecord) {
+ this.archiveExtraDataRecord = archiveExtraDataRecord;
+ }
+
+ public boolean isSplitArchive() {
+ return splitArchive;
+ }
+
+ public void setSplitArchive(boolean splitArchive) {
+ this.splitArchive = splitArchive;
+ }
+
+ public String getZipFile() {
+ return zipFile;
+ }
+
+ public void setZipFile(String zipFile) {
+ this.zipFile = zipFile;
+ }
+
+ public Zip64EndCentralDirLocator getZip64EndCentralDirLocator() {
+ return zip64EndCentralDirLocator;
+ }
+
+ public void setZip64EndCentralDirLocator(
+ Zip64EndCentralDirLocator zip64EndCentralDirLocator) {
+ this.zip64EndCentralDirLocator = zip64EndCentralDirLocator;
+ }
+
+ public Zip64EndCentralDirRecord getZip64EndCentralDirRecord() {
+ return zip64EndCentralDirRecord;
+ }
+
+ public void setZip64EndCentralDirRecord(
+ Zip64EndCentralDirRecord zip64EndCentralDirRecord) {
+ this.zip64EndCentralDirRecord = zip64EndCentralDirRecord;
+ }
+
+ public boolean isZip64Format() {
+ return isZip64Format;
+ }
+
+ public void setZip64Format(boolean isZip64Format) {
+ this.isZip64Format = isZip64Format;
+ }
+
+ public boolean isNestedZipFile() {
+ return isNestedZipFile;
+ }
+
+ public void setNestedZipFile(boolean isNestedZipFile) {
+ this.isNestedZipFile = isNestedZipFile;
+ }
+
+ public long getStart() {
+ return start;
+ }
+
+ public void setStart(long start) {
+ this.start = start;
+ }
+
+ public long getEnd() {
+ return end;
+ }
+
+ public void setEnd(long end) {
+ this.end = end;
+ }
+
+ public long getSplitLength() {
+ return splitLength;
+ }
+
+ public void setSplitLength(long splitLength) {
+ this.splitLength = splitLength;
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ public String getFileNameCharset() {
+ return fileNameCharset;
+ }
+
+ public void setFileNameCharset(String fileNameCharset) {
+ this.fileNameCharset = fileNameCharset;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/model/ZipParameters.java b/harmony/src/main/java/com/zip4j/model/ZipParameters.java
new file mode 100644
index 0000000..ef74504
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/model/ZipParameters.java
@@ -0,0 +1,195 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.model;
+
+import java.util.TimeZone;
+
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Zip4jConstants;
+import com.zip4j.util.Zip4jUtil;
+
+public class ZipParameters implements Cloneable {
+
+ private int compressionMethod;
+ private int compressionLevel;
+ private boolean encryptFiles;
+ private int encryptionMethod;
+ private boolean readHiddenFiles;
+ private char[] password;
+ private int aesKeyStrength;
+ private boolean includeRootFolder;
+ private String rootFolderInZip;
+ private TimeZone timeZone;
+ private int sourceFileCRC;
+ private String defaultFolderPath;
+ private String fileNameInZip;
+ private boolean isSourceExternalStream;
+
+ public ZipParameters() {
+ compressionMethod = Zip4jConstants.COMP_DEFLATE;
+ encryptFiles = false;
+ readHiddenFiles = true;
+ encryptionMethod = Zip4jConstants.ENC_NO_ENCRYPTION;
+ aesKeyStrength = -1;
+ includeRootFolder = true;
+ timeZone = TimeZone.getDefault();
+ }
+
+ public int getCompressionMethod() {
+ return compressionMethod;
+ }
+
+ public void setCompressionMethod(int compressionMethod) {
+ this.compressionMethod = compressionMethod;
+ }
+
+ public boolean isEncryptFiles() {
+ return encryptFiles;
+ }
+
+ public void setEncryptFiles(boolean encryptFiles) {
+ this.encryptFiles = encryptFiles;
+ }
+
+ public int getEncryptionMethod() {
+ return encryptionMethod;
+ }
+
+ public void setEncryptionMethod(int encryptionMethod) {
+ this.encryptionMethod = encryptionMethod;
+ }
+
+ public int getCompressionLevel() {
+ return compressionLevel;
+ }
+
+ public void setCompressionLevel(int compressionLevel) {
+ this.compressionLevel = compressionLevel;
+ }
+
+ public boolean isReadHiddenFiles() {
+ return readHiddenFiles;
+ }
+
+ public void setReadHiddenFiles(boolean readHiddenFiles) {
+ this.readHiddenFiles = readHiddenFiles;
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ public char[] getPassword() {
+ return password;
+ }
+
+ /**
+ * Sets the password for the zip file or the file being added
+ * Note: For security reasons, usage of this method is discouraged. Use
+ * setPassword(char[]) instead. As strings are immutable, they cannot be wiped
+ * out from memory explicitly after usage. Therefore, usage of Strings to store
+ * passwords is discouraged. More info here:
+ * http://docs.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#PBEEx
+ * @param password
+ */
+ public void setPassword(String password) {
+ if (password == null) return;
+ setPassword(password.toCharArray());
+ }
+
+ public void setPassword(char[] password) {
+ this.password = password;
+ }
+
+ public int getAesKeyStrength() {
+ return aesKeyStrength;
+ }
+
+ public void setAesKeyStrength(int aesKeyStrength) {
+ this.aesKeyStrength = aesKeyStrength;
+ }
+
+ public boolean isIncludeRootFolder() {
+ return includeRootFolder;
+ }
+
+ public void setIncludeRootFolder(boolean includeRootFolder) {
+ this.includeRootFolder = includeRootFolder;
+ }
+
+ public String getRootFolderInZip() {
+ return rootFolderInZip;
+ }
+
+ public void setRootFolderInZip(String rootFolderInZip) {
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(rootFolderInZip)) {
+
+ if (!rootFolderInZip.endsWith("\\") && !rootFolderInZip.endsWith("/")) {
+ rootFolderInZip = rootFolderInZip + InternalZipConstants.FILE_SEPARATOR;
+ }
+
+ rootFolderInZip = rootFolderInZip.replaceAll("\\\\", "/");
+
+// if (rootFolderInZip.endsWith("/")) {
+// rootFolderInZip = rootFolderInZip.substring(0, rootFolderInZip.length() - 1);
+// rootFolderInZip = rootFolderInZip + "\\";
+// }
+ }
+ this.rootFolderInZip = rootFolderInZip;
+ }
+
+ public TimeZone getTimeZone() {
+ return timeZone;
+ }
+
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ public int getSourceFileCRC() {
+ return sourceFileCRC;
+ }
+
+ public void setSourceFileCRC(int sourceFileCRC) {
+ this.sourceFileCRC = sourceFileCRC;
+ }
+
+ public String getDefaultFolderPath() {
+ return defaultFolderPath;
+ }
+
+ public void setDefaultFolderPath(String defaultFolderPath) {
+ this.defaultFolderPath = defaultFolderPath;
+ }
+
+ public String getFileNameInZip() {
+ return fileNameInZip;
+ }
+
+ public void setFileNameInZip(String fileNameInZip) {
+ this.fileNameInZip = fileNameInZip;
+ }
+
+ public boolean isSourceExternalStream() {
+ return isSourceExternalStream;
+ }
+
+ public void setSourceExternalStream(boolean isSourceExternalStream) {
+ this.isSourceExternalStream = isSourceExternalStream;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/progress/ProgressMonitor.java b/harmony/src/main/java/com/zip4j/progress/ProgressMonitor.java
new file mode 100644
index 0000000..8ee6777
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/progress/ProgressMonitor.java
@@ -0,0 +1,177 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.progress;
+
+import com.zip4j.exception.ZipException;
+
+/**
+ * If Zip4j is set to run in thread mode, this class helps retrieve current progress
+ *
+ */
+public class ProgressMonitor {
+
+ private int state;
+ private long totalWork;
+ private long workCompleted;
+ private int percentDone;
+ private int currentOperation;
+ private String fileName;
+ private int result;
+ private Throwable exception;
+ private boolean cancelAllTasks;
+ private boolean pause;
+
+ //Progress monitor States
+ public static final int STATE_READY = 0;
+ public static final int STATE_BUSY = 1;
+
+ //Progress monitor result codes
+ public static final int RESULT_SUCCESS = 0;
+ public static final int RESULT_WORKING = 1;
+ public static final int RESULT_ERROR = 2;
+ public static final int RESULT_CANCELLED = 3;
+
+ //Operation Types
+ public static final int OPERATION_NONE = -1;
+ public static final int OPERATION_ADD = 0;
+ public static final int OPERATION_EXTRACT = 1;
+ public static final int OPERATION_REMOVE = 2;
+ public static final int OPERATION_CALC_CRC = 3;
+ public static final int OPERATION_MERGE = 4;
+
+ public ProgressMonitor() {
+ reset();
+ percentDone = 0;
+ }
+
+ public int getState() {
+ return state;
+ }
+
+ public void setState(int state) {
+ this.state = state;
+ }
+
+ public long getTotalWork() {
+ return totalWork;
+ }
+
+ public void setTotalWork(long totalWork) {
+ this.totalWork = totalWork;
+ }
+
+ public long getWorkCompleted() {
+ return workCompleted;
+ }
+
+ public void updateWorkCompleted(long workCompleted) {
+ this.workCompleted += workCompleted;
+ percentDone = (int)((this.workCompleted*100/totalWork));
+ if (percentDone > 100) {
+ percentDone = 100;
+ }
+ while (pause) {
+ try {
+ Thread.sleep(150);
+ } catch (InterruptedException e) {
+ //Do nothing
+ }
+ }
+ }
+
+ public int getPercentDone() {
+ return percentDone;
+ }
+
+ public void setPercentDone(int percentDone) {
+ this.percentDone = percentDone;
+ }
+
+ public int getResult() {
+ return result;
+ }
+
+ public void setResult(int result) {
+ this.result = result;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ public int getCurrentOperation() {
+ return currentOperation;
+ }
+
+ public void setCurrentOperation(int currentOperation) {
+ this.currentOperation = currentOperation;
+ }
+
+ public Throwable getException() {
+ return exception;
+ }
+
+ public void setException(Throwable exception) {
+ this.exception = exception;
+ }
+
+ public void endProgressMonitorSuccess() throws ZipException {
+ reset();
+ result = ProgressMonitor.RESULT_SUCCESS;
+ }
+
+ public void endProgressMonitorError(Throwable e) throws ZipException {
+ reset();
+ result = ProgressMonitor.RESULT_ERROR;
+ exception = e;
+ }
+
+ public void reset() {
+ currentOperation = OPERATION_NONE;
+ state = STATE_READY;
+ fileName = null;
+ totalWork = 0;
+ workCompleted = 0;
+ }
+
+ public void fullReset() {
+ reset();
+ exception = null;
+ result = RESULT_SUCCESS;
+ percentDone = 0;
+ }
+
+ public boolean isCancelAllTasks() {
+ return cancelAllTasks;
+ }
+
+ public void cancelAllTasks() {
+ this.cancelAllTasks = true;
+ }
+
+ public boolean isPause() {
+ return pause;
+ }
+
+ public void setPause(boolean pause) {
+ this.pause = pause;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/unzip/Unzip.java b/harmony/src/main/java/com/zip4j/unzip/Unzip.java
new file mode 100644
index 0000000..4fc6b38
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/unzip/Unzip.java
@@ -0,0 +1,232 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.unzip;
+
+import com.zip4j.model.CentralDirectory;
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.UnzipParameters;
+import com.zip4j.model.ZipModel;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.io.ZipInputStream;
+import com.zip4j.progress.ProgressMonitor;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Zip4jUtil;
+
+public class Unzip {
+
+ private ZipModel zipModel;
+
+ public Unzip(ZipModel zipModel) throws ZipException {
+
+ if (zipModel == null) {
+ throw new ZipException("ZipModel is null");
+ }
+
+ this.zipModel = zipModel;
+ }
+
+ public void extractAll(final UnzipParameters unzipParameters, final String outPath,
+ final ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+
+ CentralDirectory centralDirectory = zipModel.getCentralDirectory();
+
+ if (centralDirectory == null ||
+ centralDirectory.getFileHeaders() == null) {
+ throw new ZipException("invalid central directory in zipModel");
+ }
+
+ final ArrayList fileHeaders = centralDirectory.getFileHeaders();
+
+ progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_EXTRACT);
+ progressMonitor.setTotalWork(calculateTotalWork(fileHeaders));
+ progressMonitor.setState(ProgressMonitor.STATE_BUSY);
+
+ if (runInThread) {
+ Thread thread = new Thread(InternalZipConstants.THREAD_NAME) {
+ public void run() {
+ try {
+ initExtractAll(fileHeaders, unzipParameters, progressMonitor, outPath);
+ progressMonitor.endProgressMonitorSuccess();
+ } catch (ZipException e) {
+ }
+ }
+ };
+ thread.start();
+ } else {
+ initExtractAll(fileHeaders, unzipParameters, progressMonitor, outPath);
+ }
+
+ }
+
+ private void initExtractAll(ArrayList fileHeaders, UnzipParameters unzipParameters,
+ ProgressMonitor progressMonitor, String outPath) throws ZipException {
+
+ for (int i = 0; i < fileHeaders.size(); i++) {
+ FileHeader fileHeader = (FileHeader)fileHeaders.get(i);
+ initExtractFile(fileHeader, outPath, unzipParameters, null, progressMonitor);
+ if (progressMonitor.isCancelAllTasks()) {
+ progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
+ progressMonitor.setState(ProgressMonitor.STATE_READY);
+ return;
+ }
+ }
+ }
+
+ public void extractFile(final FileHeader fileHeader, final String outPath,
+ final UnzipParameters unzipParameters, final String newFileName,
+ final ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+ if (fileHeader == null) {
+ throw new ZipException("fileHeader is null");
+ }
+
+ progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_EXTRACT);
+ progressMonitor.setTotalWork(fileHeader.getCompressedSize());
+ progressMonitor.setState(ProgressMonitor.STATE_BUSY);
+ progressMonitor.setPercentDone(0);
+ progressMonitor.setFileName(fileHeader.getFileName());
+
+ if (runInThread) {
+ Thread thread = new Thread(InternalZipConstants.THREAD_NAME) {
+ public void run() {
+ try {
+ initExtractFile(fileHeader, outPath, unzipParameters, newFileName, progressMonitor);
+ progressMonitor.endProgressMonitorSuccess();
+ } catch (ZipException e) {
+ }
+ }
+ };
+ thread.start();
+ } else {
+ initExtractFile(fileHeader, outPath, unzipParameters, newFileName, progressMonitor);
+ progressMonitor.endProgressMonitorSuccess();
+ }
+
+ }
+
+ private void initExtractFile(FileHeader fileHeader, String outPath,
+ UnzipParameters unzipParameters, String newFileName, ProgressMonitor progressMonitor) throws ZipException {
+
+ if (fileHeader == null) {
+ throw new ZipException("fileHeader is null");
+ }
+
+ try {
+ progressMonitor.setFileName(fileHeader.getFileName());
+
+ if (!outPath.endsWith(InternalZipConstants.FILE_SEPARATOR)) {
+ outPath += InternalZipConstants.FILE_SEPARATOR;
+ }
+
+ // If file header is a directory, then check if the directory exists
+ // If not then create a directory and return
+ if (fileHeader.isDirectory()) {
+ try {
+ String fileName = fileHeader.getFileName();
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(fileName)) {
+ return;
+ }
+ String completePath = outPath + fileName;
+ File file = new File(completePath);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ } catch (Exception e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw new ZipException(e);
+ }
+ } else {
+ //Create Directories
+ checkOutputDirectoryStructure(fileHeader, outPath, newFileName);
+
+ UnzipEngine unzipEngine = new UnzipEngine(zipModel, fileHeader);
+ try {
+ unzipEngine.unzipFile(progressMonitor, outPath, newFileName, unzipParameters);
+ } catch (Exception e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw new ZipException(e);
+ }
+ }
+ } catch (ZipException e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw e;
+ } catch (Exception e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw new ZipException(e);
+ }
+ }
+
+ public ZipInputStream getInputStream(FileHeader fileHeader) throws ZipException {
+ UnzipEngine unzipEngine = new UnzipEngine(zipModel, fileHeader);
+ return unzipEngine.getInputStream();
+ }
+
+ private void checkOutputDirectoryStructure(FileHeader fileHeader, String outPath, String newFileName) throws ZipException {
+ if (fileHeader == null || !Zip4jUtil.isStringNotNullAndNotEmpty(outPath)) {
+ throw new ZipException("Cannot check output directory structure...one of the parameters was null");
+ }
+
+ String fileName = fileHeader.getFileName();
+
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(newFileName)) {
+ fileName = newFileName;
+ }
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(fileName)) {
+ // Do nothing
+ return;
+ }
+
+ String compOutPath = outPath + fileName;
+ try {
+ File file = new File(compOutPath);
+ String parentDir = file.getParent();
+ File parentDirFile = new File(parentDir);
+ if (!parentDirFile.exists()) {
+ parentDirFile.mkdirs();
+ }
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private long calculateTotalWork(ArrayList fileHeaders) throws ZipException {
+
+ if (fileHeaders == null) {
+ throw new ZipException("fileHeaders is null, cannot calculate total work");
+ }
+
+ long totalWork = 0;
+
+ for (int i = 0; i < fileHeaders.size(); i++) {
+ FileHeader fileHeader = (FileHeader)fileHeaders.get(i);
+ if (fileHeader.getZip64ExtendedInfo() != null &&
+ fileHeader.getZip64ExtendedInfo().getUnCompressedSize() > 0) {
+ totalWork += fileHeader.getZip64ExtendedInfo().getCompressedSize();
+ } else {
+ totalWork += fileHeader.getCompressedSize();
+ }
+
+ }
+
+ return totalWork;
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/unzip/UnzipEngine.java b/harmony/src/main/java/com/zip4j/unzip/UnzipEngine.java
new file mode 100644
index 0000000..2846a44
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/unzip/UnzipEngine.java
@@ -0,0 +1,522 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.unzip;
+
+import com.zip4j.core.HeaderReader;
+import com.zip4j.io.InflaterInputStream;
+import com.zip4j.io.PartInputStream;
+import com.zip4j.model.AESExtraDataRecord;
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.LocalFileHeader;
+import com.zip4j.model.UnzipParameters;
+import com.zip4j.model.ZipModel;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.zip.CRC32;
+
+import com.zip4j.crypto.AESDecrypter;
+import com.zip4j.crypto.IDecrypter;
+import com.zip4j.crypto.StandardDecrypter;
+import com.zip4j.exception.ZipException;
+import com.zip4j.io.ZipInputStream;
+import com.zip4j.progress.ProgressMonitor;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Raw;
+import com.zip4j.util.Zip4jConstants;
+import com.zip4j.util.Zip4jUtil;
+
+public class UnzipEngine {
+
+ private ZipModel zipModel;
+ private FileHeader fileHeader;
+ private int currSplitFileCounter = 0;
+ private LocalFileHeader localFileHeader;
+ private IDecrypter decrypter;
+ private CRC32 crc;
+
+ public UnzipEngine(ZipModel zipModel, FileHeader fileHeader) throws ZipException {
+ if (zipModel == null || fileHeader == null) {
+ throw new ZipException("Invalid parameters passed to StoreUnzip. One or more of the parameters were null");
+ }
+
+ this.zipModel = zipModel;
+ this.fileHeader = fileHeader;
+ this.crc = new CRC32();
+ }
+
+ public void unzipFile(ProgressMonitor progressMonitor,
+ String outPath, String newFileName, UnzipParameters unzipParameters) throws ZipException {
+ if (zipModel == null || fileHeader == null || !Zip4jUtil.isStringNotNullAndNotEmpty(outPath)) {
+ throw new ZipException("Invalid parameters passed during unzipping file. One or more of the parameters were null");
+ }
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ byte[] buff = new byte[InternalZipConstants.BUFF_SIZE];
+ int readLength = -1;
+
+ is = getInputStream();
+ os = getOutputStream(outPath, newFileName);
+
+ while ((readLength = is.read(buff)) != -1) {
+ os.write(buff, 0, readLength);
+ progressMonitor.updateWorkCompleted(readLength);
+ if (progressMonitor.isCancelAllTasks()) {
+ progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
+ progressMonitor.setState(ProgressMonitor.STATE_READY);
+ return;
+ }
+ }
+
+ closeStreams(is, os);
+
+ UnzipUtil.applyFileAttributes(fileHeader, new File(getOutputFileNameWithPath(outPath, newFileName)), unzipParameters);
+
+ } catch (IOException e) {
+ throw new ZipException(e);
+ } catch (Exception e) {
+ throw new ZipException(e);
+ } finally {
+ closeStreams(is, os);
+ }
+ }
+
+ public ZipInputStream getInputStream() throws ZipException {
+ if (fileHeader == null) {
+ throw new ZipException("file header is null, cannot get inputstream");
+ }
+
+ RandomAccessFile raf = null;
+ try {
+ raf = createFileHandler(InternalZipConstants.READ_MODE);
+ String errMsg = "local header and file header do not match";
+ //checkSplitFile();
+
+ if (!checkLocalHeader())
+ throw new ZipException(errMsg);
+
+ init(raf);
+
+ long comprSize = localFileHeader.getCompressedSize();
+ long offsetStartOfData = localFileHeader.getOffsetStartOfData();
+
+ if (localFileHeader.isEncrypted()) {
+ if (localFileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ if (decrypter instanceof AESDecrypter) {
+ comprSize -= (((AESDecrypter)decrypter).getSaltLength() +
+ ((AESDecrypter)decrypter).getPasswordVerifierLength() + 10);
+ offsetStartOfData += (((AESDecrypter)decrypter).getSaltLength() +
+ ((AESDecrypter)decrypter).getPasswordVerifierLength());
+ } else {
+ throw new ZipException("invalid decryptor when trying to calculate " +
+ "compressed size for AES encrypted file: " + fileHeader.getFileName());
+ }
+ } else if (localFileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ comprSize -= InternalZipConstants.STD_DEC_HDR_SIZE;
+ offsetStartOfData += InternalZipConstants.STD_DEC_HDR_SIZE;
+ }
+ }
+
+ int compressionMethod = fileHeader.getCompressionMethod();
+ if (fileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ if (fileHeader.getAesExtraDataRecord() != null) {
+ compressionMethod = fileHeader.getAesExtraDataRecord().getCompressionMethod();
+ } else {
+ throw new ZipException("AESExtraDataRecord does not exist for AES encrypted file: " + fileHeader.getFileName());
+ }
+ }
+ raf.seek(offsetStartOfData);
+ switch (compressionMethod) {
+ case Zip4jConstants.COMP_STORE:
+ return new ZipInputStream(new PartInputStream(raf, offsetStartOfData, comprSize, this));
+ case Zip4jConstants.COMP_DEFLATE:
+ return new ZipInputStream(new InflaterInputStream(raf, offsetStartOfData, comprSize, this));
+ default:
+ throw new ZipException("compression type not supported");
+ }
+ } catch (ZipException e) {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e1) {
+ //ignore
+ }
+ }
+ throw e;
+ } catch (Exception e) {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e1) {
+ }
+ }
+ throw new ZipException(e);
+ }
+
+ }
+
+ private void init(RandomAccessFile raf) throws ZipException {
+
+ if (localFileHeader == null) {
+ throw new ZipException("local file header is null, cannot initialize input stream");
+ }
+
+ try {
+ initDecrypter(raf);
+ } catch (ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void initDecrypter(RandomAccessFile raf) throws ZipException {
+ if (localFileHeader == null) {
+ throw new ZipException("local file header is null, cannot init decrypter");
+ }
+
+ if (localFileHeader.isEncrypted()) {
+ if (localFileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ decrypter = new StandardDecrypter(fileHeader, getStandardDecrypterHeaderBytes(raf));
+ } else if (localFileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ decrypter = new AESDecrypter(localFileHeader, getAESSalt(raf), getAESPasswordVerifier(raf));
+ } else {
+ throw new ZipException("unsupported encryption method");
+ }
+ }
+ }
+
+ private byte[] getStandardDecrypterHeaderBytes(RandomAccessFile raf) throws ZipException {
+ try {
+ byte[] headerBytes = new byte[InternalZipConstants.STD_DEC_HDR_SIZE];
+ raf.seek(localFileHeader.getOffsetStartOfData());
+ raf.read(headerBytes, 0, 12);
+ return headerBytes;
+ } catch (IOException e) {
+ throw new ZipException(e);
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private byte[] getAESSalt(RandomAccessFile raf) throws ZipException {
+ if (localFileHeader.getAesExtraDataRecord() == null)
+ return null;
+
+ try {
+ AESExtraDataRecord aesExtraDataRecord = localFileHeader.getAesExtraDataRecord();
+ byte[] saltBytes = new byte[calculateAESSaltLength(aesExtraDataRecord)];
+ raf.seek(localFileHeader.getOffsetStartOfData());
+ raf.read(saltBytes);
+ return saltBytes;
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private byte[] getAESPasswordVerifier(RandomAccessFile raf) throws ZipException {
+ try {
+ byte[] pvBytes = new byte[2];
+ raf.read(pvBytes);
+ return pvBytes;
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private int calculateAESSaltLength(AESExtraDataRecord aesExtraDataRecord) throws ZipException {
+ if (aesExtraDataRecord == null) {
+ throw new ZipException("unable to determine salt length: AESExtraDataRecord is null");
+ }
+ switch (aesExtraDataRecord.getAesStrength()) {
+ case Zip4jConstants.AES_STRENGTH_128:
+ return 8;
+ case Zip4jConstants.AES_STRENGTH_192:
+ return 12;
+ case Zip4jConstants.AES_STRENGTH_256:
+ return 16;
+ default:
+ throw new ZipException("unable to determine salt length: invalid aes key strength");
+ }
+ }
+
+ public void checkCRC() throws ZipException {
+ if (fileHeader != null) {
+ if (fileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+ if (decrypter != null && decrypter instanceof AESDecrypter) {
+ byte[] tmpMacBytes = ((AESDecrypter)decrypter).getCalculatedAuthenticationBytes();
+ byte[] storedMac = ((AESDecrypter)decrypter).getStoredMac();
+ byte[] calculatedMac = new byte[InternalZipConstants.AES_AUTH_LENGTH];
+
+ if (calculatedMac == null || storedMac == null) {
+ throw new ZipException("CRC (MAC) check failed for " + fileHeader.getFileName());
+ }
+
+ System.arraycopy(tmpMacBytes, 0, calculatedMac, 0, InternalZipConstants.AES_AUTH_LENGTH);
+
+ if (!Arrays.equals(calculatedMac, storedMac)) {
+ throw new ZipException("invalid CRC (MAC) for file: " + fileHeader.getFileName());
+ }
+ }
+ } else {
+ long calculatedCRC = crc.getValue() & 0xffffffffL;
+ if (calculatedCRC != fileHeader.getCrc32()) {
+ String errMsg = "invalid CRC for file: " + fileHeader.getFileName();
+ if (localFileHeader.isEncrypted() &&
+ localFileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ errMsg += " - Wrong Password?";
+ }
+ throw new ZipException(errMsg);
+ }
+ }
+ }
+ }
+
+// private void checkCRC() throws ZipException {
+// if (fileHeader != null) {
+// if (fileHeader.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_AES) {
+// if (decrypter != null && decrypter instanceof AESDecrypter) {
+// byte[] tmpMacBytes = ((AESDecrypter)decrypter).getCalculatedAuthenticationBytes();
+// byte[] actualMacBytes = ((AESDecrypter)decrypter).getStoredMac();
+// if (tmpMacBytes == null || actualMacBytes == null) {
+// throw new ZipException("null mac value for AES encrypted file: " + fileHeader.getFileName());
+// }
+// byte[] calcMacBytes = new byte[10];
+// System.arraycopy(tmpMacBytes, 0, calcMacBytes, 0, 10);
+// if (!Arrays.equals(calcMacBytes, actualMacBytes)) {
+// throw new ZipException("invalid CRC(mac) for file: " + fileHeader.getFileName());
+// }
+// } else {
+// throw new ZipException("invalid decryptor...cannot calculate mac value for file: "
+// + fileHeader.getFileName());
+// }
+// } else if (unzipEngine != null) {
+// long calculatedCRC = unzipEngine.getCRC();
+// long actualCRC = fileHeader.getCrc32();
+// if (calculatedCRC != actualCRC) {
+// throw new ZipException("invalid CRC for file: " + fileHeader.getFileName());
+// }
+// }
+// }
+// }
+
+ private boolean checkLocalHeader() throws ZipException {
+ RandomAccessFile rafForLH = null;
+ try {
+ rafForLH = checkSplitFile();
+
+ if (rafForLH == null) {
+ rafForLH = new RandomAccessFile(new File(this.zipModel.getZipFile()), InternalZipConstants.READ_MODE);
+ }
+
+ HeaderReader headerReader = new HeaderReader(rafForLH);
+ this.localFileHeader = headerReader.readLocalFileHeader(fileHeader);
+
+ if (localFileHeader == null) {
+ throw new ZipException("error reading local file header. Is this a valid zip file?");
+ }
+
+ //TODO Add more comparision later
+ if (localFileHeader.getCompressionMethod() != fileHeader.getCompressionMethod()) {
+ return false;
+ }
+
+ return true;
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ } finally {
+ if (rafForLH != null) {
+ try {
+ rafForLH.close();
+ } catch (IOException e) {
+ // Ignore this
+ } catch (Exception e) {
+ //Ignore this
+ }
+ }
+ }
+ }
+
+ private RandomAccessFile checkSplitFile() throws ZipException {
+ if (zipModel.isSplitArchive()) {
+ int diskNumberStartOfFile = fileHeader.getDiskNumberStart();
+ currSplitFileCounter = diskNumberStartOfFile + 1;
+ String curZipFile = zipModel.getZipFile();
+ String partFile = null;
+ if (diskNumberStartOfFile == zipModel.getEndCentralDirRecord().getNoOfThisDisk()) {
+ partFile = zipModel.getZipFile();
+ } else {
+ if (diskNumberStartOfFile >= 9) {
+ partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z" + (diskNumberStartOfFile+ 1);
+ } else{
+ partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z0" + (diskNumberStartOfFile+ 1);
+ }
+ }
+
+ try {
+ RandomAccessFile raf = new RandomAccessFile(partFile, InternalZipConstants.READ_MODE);
+
+ if (currSplitFileCounter == 1) {
+ byte[] splitSig = new byte[4];
+ raf.read(splitSig);
+ if (Raw.readIntLittleEndian(splitSig, 0) != InternalZipConstants.SPLITSIG) {
+ throw new ZipException("invalid first part split file signature");
+ }
+ }
+ return raf;
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ }
+ return null;
+ }
+
+ private RandomAccessFile createFileHandler(String mode) throws ZipException {
+ if (this.zipModel == null || !Zip4jUtil.isStringNotNullAndNotEmpty(this.zipModel.getZipFile())) {
+ throw new ZipException("input parameter is null in getFilePointer");
+ }
+
+ try {
+ RandomAccessFile raf = null;
+ if (zipModel.isSplitArchive()) {
+ raf = checkSplitFile();
+ } else {
+ raf = new RandomAccessFile(new File(this.zipModel.getZipFile()), mode);
+ }
+ return raf;
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private FileOutputStream getOutputStream(String outPath, String newFileName) throws ZipException {
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(outPath)) {
+ throw new ZipException("invalid output path");
+ }
+
+ try {
+ File file = new File(getOutputFileNameWithPath(outPath, newFileName));
+
+ if (!file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+
+ if (file.exists()) {
+ file.delete();
+ }
+
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ return fileOutputStream;
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private String getOutputFileNameWithPath(String outPath, String newFileName) throws ZipException {
+ String fileName = null;
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(newFileName)) {
+ fileName = newFileName;
+ } else {
+ fileName = fileHeader.getFileName();
+ }
+ return outPath + System.getProperty("file.separator") + fileName;
+ }
+
+ public RandomAccessFile startNextSplitFile() throws IOException, FileNotFoundException {
+ String currZipFile = zipModel.getZipFile();
+ String partFile = null;
+ if (currSplitFileCounter == zipModel.getEndCentralDirRecord().getNoOfThisDisk()) {
+ partFile = zipModel.getZipFile();
+ } else {
+ if (currSplitFileCounter >= 9) {
+ partFile = currZipFile.substring(0, currZipFile.lastIndexOf(".")) + ".z" + (currSplitFileCounter + 1);
+ } else {
+ partFile = currZipFile.substring(0, currZipFile.lastIndexOf(".")) + ".z0" + (currSplitFileCounter + 1);
+ }
+ }
+ currSplitFileCounter++;
+ try {
+ if(!Zip4jUtil.checkFileExists(partFile)) {
+ throw new IOException("zip split file does not exist: " + partFile);
+ }
+ } catch (ZipException e) {
+ throw new IOException(e.getMessage());
+ }
+ return new RandomAccessFile(partFile, InternalZipConstants.READ_MODE);
+ }
+
+ private void closeStreams(InputStream is, OutputStream os) throws ZipException {
+ try {
+ if (is != null) {
+ is.close();
+ is = null;
+ }
+ } catch (IOException e) {
+ if (e != null && Zip4jUtil.isStringNotNullAndNotEmpty(e.getMessage())) {
+ if (e.getMessage().indexOf(" - Wrong Password?") >= 0) {
+ throw new ZipException(e.getMessage());
+ }
+ }
+ } finally {
+ try {
+ if (os != null) {
+ os.close();
+ os = null;
+ }
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+ }
+
+ public void updateCRC(int b) {
+ crc.update(b);
+ }
+
+ public void updateCRC(byte[] buff, int offset, int len) {
+ if (buff != null) {
+ crc.update(buff, offset, len);
+ }
+ }
+
+ public FileHeader getFileHeader() {
+ return fileHeader;
+ }
+
+ public IDecrypter getDecrypter() {
+ return decrypter;
+ }
+
+ public ZipModel getZipModel() {
+ return zipModel;
+ }
+
+ public LocalFileHeader getLocalFileHeader() {
+ return localFileHeader;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/unzip/UnzipUtil.java b/harmony/src/main/java/com/zip4j/unzip/UnzipUtil.java
new file mode 100644
index 0000000..96094cb
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/unzip/UnzipUtil.java
@@ -0,0 +1,114 @@
+package com.zip4j.unzip;
+
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.UnzipParameters;
+
+import java.io.File;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Zip4jUtil;
+
+public class UnzipUtil {
+
+ public static void applyFileAttributes(FileHeader fileHeader, File file) throws ZipException {
+ applyFileAttributes(fileHeader, file, null);
+ }
+
+ public static void applyFileAttributes(FileHeader fileHeader, File file,
+ UnzipParameters unzipParameters) throws ZipException{
+
+ if (fileHeader == null) {
+ throw new ZipException("cannot set file properties: file header is null");
+ }
+
+ if (file == null) {
+ throw new ZipException("cannot set file properties: output file is null");
+ }
+
+ if (!Zip4jUtil.checkFileExists(file)) {
+ throw new ZipException("cannot set file properties: file doesnot exist");
+ }
+
+ if (unzipParameters == null || !unzipParameters.isIgnoreDateTimeAttributes()) {
+ setFileLastModifiedTime(fileHeader, file);
+ }
+
+ if (unzipParameters == null) {
+ setFileAttributes(fileHeader, file, true, true, true, true);
+ } else {
+ if (unzipParameters.isIgnoreAllFileAttributes()) {
+ setFileAttributes(fileHeader, file, false, false, false, false);
+ } else {
+ setFileAttributes(fileHeader, file, !unzipParameters.isIgnoreReadOnlyFileAttribute(),
+ !unzipParameters.isIgnoreHiddenFileAttribute(),
+ !unzipParameters.isIgnoreArchiveFileAttribute(),
+ !unzipParameters.isIgnoreSystemFileAttribute());
+ }
+ }
+ }
+
+ private static void setFileAttributes(FileHeader fileHeader, File file, boolean setReadOnly,
+ boolean setHidden, boolean setArchive, boolean setSystem) throws ZipException {
+ if (fileHeader == null) {
+ throw new ZipException("invalid file header. cannot set file attributes");
+ }
+
+ byte[] externalAttrbs = fileHeader.getExternalFileAttr();
+ if (externalAttrbs == null) {
+ return;
+ }
+
+ int atrrib = externalAttrbs[0];
+ switch (atrrib) {
+ case InternalZipConstants.FILE_MODE_READ_ONLY:
+ if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
+ break;
+ case InternalZipConstants.FILE_MODE_HIDDEN:
+ case InternalZipConstants.FOLDER_MODE_HIDDEN:
+ if (setHidden) Zip4jUtil.setFileHidden(file);
+ break;
+ case InternalZipConstants.FILE_MODE_ARCHIVE:
+ case InternalZipConstants.FOLDER_MODE_ARCHIVE:
+ if (setArchive) Zip4jUtil.setFileArchive(file);
+ break;
+ case InternalZipConstants.FILE_MODE_READ_ONLY_HIDDEN:
+ if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
+ if (setHidden) Zip4jUtil.setFileHidden(file);
+ break;
+ case InternalZipConstants.FILE_MODE_READ_ONLY_ARCHIVE:
+ if (setArchive) Zip4jUtil.setFileArchive(file);
+ if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
+ break;
+ case InternalZipConstants.FILE_MODE_HIDDEN_ARCHIVE:
+ case InternalZipConstants.FOLDER_MODE_HIDDEN_ARCHIVE:
+ if (setArchive) Zip4jUtil.setFileArchive(file);
+ if (setHidden) Zip4jUtil.setFileHidden(file);
+ break;
+ case InternalZipConstants.FILE_MODE_READ_ONLY_HIDDEN_ARCHIVE:
+ if (setArchive) Zip4jUtil.setFileArchive(file);
+ if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
+ if (setHidden) Zip4jUtil.setFileHidden(file);
+ break;
+ case InternalZipConstants.FILE_MODE_SYSTEM:
+ if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
+ if (setHidden) Zip4jUtil.setFileHidden(file);
+ if (setSystem) Zip4jUtil.setFileSystemMode(file);
+ break;
+ default:
+ //do nothing
+ break;
+ }
+ }
+
+ private static void setFileLastModifiedTime(FileHeader fileHeader, File file) throws ZipException {
+ if (fileHeader.getLastModFileTime() <= 0) {
+ return;
+ }
+
+ if (file.exists()) {
+ file.setLastModified(Zip4jUtil.dosToJavaTme(fileHeader.getLastModFileTime()));
+ }
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/util/ArchiveMaintainer.java b/harmony/src/main/java/com/zip4j/util/ArchiveMaintainer.java
new file mode 100644
index 0000000..37196ed
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/util/ArchiveMaintainer.java
@@ -0,0 +1,727 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.util;
+
+import com.zip4j.core.HeaderReader;
+import com.zip4j.core.HeaderWriter;
+import com.zip4j.exception.ZipException;
+import com.zip4j.io.SplitOutputStream;
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.LocalFileHeader;
+import com.zip4j.model.Zip64EndCentralDirLocator;
+import com.zip4j.model.Zip64EndCentralDirRecord;
+import com.zip4j.model.ZipModel;
+import com.zip4j.progress.ProgressMonitor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class ArchiveMaintainer {
+
+ public ArchiveMaintainer() {
+ }
+
+ public HashMap removeZipFile(final ZipModel zipModel,
+ final FileHeader fileHeader, final ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+
+ if (runInThread) {
+ Thread thread = new Thread(InternalZipConstants.THREAD_NAME) {
+ public void run() {
+ try {
+ initRemoveZipFile(zipModel, fileHeader, progressMonitor);
+ progressMonitor.endProgressMonitorSuccess();
+ } catch (ZipException e) {
+ }
+ }
+ };
+ thread.start();
+ return null;
+ } else {
+ HashMap retMap = initRemoveZipFile(zipModel, fileHeader, progressMonitor);
+ progressMonitor.endProgressMonitorSuccess();
+ return retMap;
+ }
+
+ }
+
+ public HashMap initRemoveZipFile(ZipModel zipModel,
+ FileHeader fileHeader, ProgressMonitor progressMonitor) throws ZipException {
+
+ if (fileHeader == null || zipModel == null) {
+ throw new ZipException("input parameters is null in maintain zip file, cannot remove file from archive");
+ }
+
+ OutputStream outputStream = null;
+ File zipFile = null;
+ RandomAccessFile inputStream = null;
+ boolean successFlag = false;
+ String tmpZipFileName = null;
+ HashMap retMap = new HashMap();
+
+ try {
+ int indexOfFileHeader = Zip4jUtil.getIndexOfFileHeader(zipModel, fileHeader);
+
+ if (indexOfFileHeader < 0) {
+ throw new ZipException("file header not found in zip model, cannot remove file");
+ }
+
+ if (zipModel.isSplitArchive()) {
+ throw new ZipException("This is a split archive. Zip file format does not allow updating split/spanned files");
+ }
+
+ long currTime = System.currentTimeMillis();
+ tmpZipFileName = zipModel.getZipFile() + currTime%1000;
+ File tmpFile = new File(tmpZipFileName);
+
+ while (tmpFile.exists()) {
+ currTime = System.currentTimeMillis();
+ tmpZipFileName = zipModel.getZipFile() + currTime%1000;
+ tmpFile = new File(tmpZipFileName);
+ }
+
+ try {
+ outputStream = new SplitOutputStream(new File(tmpZipFileName));
+ } catch (FileNotFoundException e1) {
+ throw new ZipException(e1);
+ }
+
+ zipFile = new File(zipModel.getZipFile());
+
+ inputStream = createFileHandler(zipModel, InternalZipConstants.READ_MODE);
+
+ HeaderReader headerReader = new HeaderReader(inputStream);
+ LocalFileHeader localFileHeader = headerReader.readLocalFileHeader(fileHeader);
+ if (localFileHeader == null) {
+ throw new ZipException("invalid local file header, cannot remove file from archive");
+ }
+
+ long offsetLocalFileHeader = fileHeader.getOffsetLocalHeader();
+
+ if (fileHeader.getZip64ExtendedInfo() != null &&
+ fileHeader.getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {
+ offsetLocalFileHeader = fileHeader.getZip64ExtendedInfo().getOffsetLocalHeader();
+ }
+
+ long offsetEndOfCompressedFile = -1;
+
+ long offsetStartCentralDir = zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir();
+ if (zipModel.isZip64Format()) {
+ if (zipModel.getZip64EndCentralDirRecord() != null) {
+ offsetStartCentralDir = zipModel.getZip64EndCentralDirRecord().getOffsetStartCenDirWRTStartDiskNo();
+ }
+ }
+
+ ArrayList fileHeaderList = zipModel.getCentralDirectory().getFileHeaders();
+
+ if (indexOfFileHeader == fileHeaderList.size() - 1) {
+ offsetEndOfCompressedFile = offsetStartCentralDir - 1;
+ } else {
+ FileHeader nextFileHeader = (FileHeader)fileHeaderList.get(indexOfFileHeader + 1);
+ if (nextFileHeader != null) {
+ offsetEndOfCompressedFile = nextFileHeader.getOffsetLocalHeader() - 1;
+ if (nextFileHeader.getZip64ExtendedInfo() != null &&
+ nextFileHeader.getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {
+ offsetEndOfCompressedFile = nextFileHeader.getZip64ExtendedInfo().getOffsetLocalHeader() - 1;
+ }
+ }
+ }
+
+ if (offsetLocalFileHeader < 0 || offsetEndOfCompressedFile < 0) {
+ throw new ZipException("invalid offset for start and end of local file, cannot remove file");
+ }
+
+ if(indexOfFileHeader == 0) {
+ if (zipModel.getCentralDirectory().getFileHeaders().size() > 1) {
+ // if this is the only file and it is deleted then no need to do this
+ copyFile(inputStream, outputStream, offsetEndOfCompressedFile + 1, offsetStartCentralDir, progressMonitor);
+ }
+ } else if (indexOfFileHeader == fileHeaderList.size() - 1) {
+ copyFile(inputStream, outputStream, 0, offsetLocalFileHeader, progressMonitor);
+ } else {
+ copyFile(inputStream, outputStream, 0, offsetLocalFileHeader, progressMonitor);
+ copyFile(inputStream, outputStream, offsetEndOfCompressedFile + 1, offsetStartCentralDir, progressMonitor);
+ }
+
+ if (progressMonitor.isCancelAllTasks()) {
+ progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
+ progressMonitor.setState(ProgressMonitor.STATE_READY);
+ return null;
+ }
+
+ zipModel.getEndCentralDirRecord().setOffsetOfStartOfCentralDir(((SplitOutputStream)outputStream).getFilePointer());
+ zipModel.getEndCentralDirRecord().setTotNoOfEntriesInCentralDir(
+ zipModel.getEndCentralDirRecord().getTotNoOfEntriesInCentralDir() - 1);
+ zipModel.getEndCentralDirRecord().setTotNoOfEntriesInCentralDirOnThisDisk(
+ zipModel.getEndCentralDirRecord().getTotNoOfEntriesInCentralDirOnThisDisk() - 1);
+
+ zipModel.getCentralDirectory().getFileHeaders().remove(indexOfFileHeader);
+
+ for (int i = indexOfFileHeader; i < zipModel.getCentralDirectory().getFileHeaders().size(); i++) {
+ long offsetLocalHdr = ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getOffsetLocalHeader();
+ if (((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getZip64ExtendedInfo() != null &&
+ ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {
+ offsetLocalHdr = ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getZip64ExtendedInfo().getOffsetLocalHeader();
+ }
+
+ ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).setOffsetLocalHeader(
+ offsetLocalHdr - (offsetEndOfCompressedFile - offsetLocalFileHeader) - 1);
+ }
+
+ HeaderWriter headerWriter = new HeaderWriter();
+ headerWriter.finalizeZipFile(zipModel, outputStream);
+
+ successFlag = true;
+
+ retMap.put(InternalZipConstants.OFFSET_CENTRAL_DIR,
+ Long.toString(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir()));
+
+ } catch (ZipException e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw e;
+ } catch (Exception e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw new ZipException(e);
+ } finally {
+ try {
+ if (inputStream != null)
+ inputStream.close();
+ if (outputStream != null)
+ outputStream.close();
+ } catch (IOException e) {
+ throw new ZipException("cannot close input stream or output stream when trying to delete a file from zip file");
+ }
+
+ if (successFlag) {
+ restoreFileName(zipFile, tmpZipFileName);
+ } else {
+ File newZipFile = new File(tmpZipFileName);
+ newZipFile.delete();
+ }
+ }
+
+ return retMap;
+ }
+
+ private void restoreFileName(File zipFile, String tmpZipFileName) throws ZipException {
+ if (zipFile.delete())
+ {
+ File newZipFile = new File(tmpZipFileName);
+ if (!newZipFile.renameTo(zipFile)) {
+ throw new ZipException("cannot rename modified zip file");
+ }
+ } else {
+ throw new ZipException("cannot delete old zip file");
+ }
+ }
+
+ private void copyFile(RandomAccessFile inputStream,
+ OutputStream outputStream, long start, long end, ProgressMonitor progressMonitor) throws ZipException {
+
+ if (inputStream == null || outputStream == null) {
+ throw new ZipException("input or output stream is null, cannot copy file");
+ }
+
+ if (start < 0) {
+ throw new ZipException("starting offset is negative, cannot copy file");
+ }
+
+ if (end < 0) {
+ throw new ZipException("end offset is negative, cannot copy file");
+ }
+
+ if (start > end) {
+ throw new ZipException("start offset is greater than end offset, cannot copy file");
+ }
+
+ if (start == end) {
+ return;
+ }
+
+ if (progressMonitor.isCancelAllTasks()) {
+ progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
+ progressMonitor.setState(ProgressMonitor.STATE_READY);
+ return;
+ }
+
+ try {
+ inputStream.seek(start);
+
+ int readLen = -2;
+ byte[] buff;
+ long bytesRead = 0;
+ long bytesToRead = end - start;
+
+ if ((end - start) < InternalZipConstants.BUFF_SIZE) {
+ buff = new byte[(int)(end - start)];
+ } else {
+ buff = new byte[InternalZipConstants.BUFF_SIZE];
+ }
+
+ while ((readLen = inputStream.read(buff)) != -1) {
+ outputStream.write(buff, 0, readLen);
+
+ progressMonitor.updateWorkCompleted(readLen);
+ if (progressMonitor.isCancelAllTasks()) {
+ progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
+ return;
+ }
+
+ bytesRead += readLen;
+
+ if(bytesRead == bytesToRead) {
+ break;
+ } else if (bytesRead + buff.length > bytesToRead) {
+ buff = new byte[(int)(bytesToRead - bytesRead)];
+ }
+ }
+
+ } catch (IOException e) {
+ throw new ZipException(e);
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private RandomAccessFile createFileHandler(ZipModel zipModel, String mode) throws ZipException {
+ if (zipModel == null || !Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getZipFile())) {
+ throw new ZipException("input parameter is null in getFilePointer, cannot create file handler to remove file");
+ }
+
+ try {
+ return new RandomAccessFile(new File(zipModel.getZipFile()), mode);
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Merges split Zip files into a single Zip file
+ * @param zipModel
+ * @throws ZipException
+ */
+ public void mergeSplitZipFiles(final ZipModel zipModel, final File outputZipFile,
+ final ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+ if (runInThread) {
+ Thread thread = new Thread(InternalZipConstants.THREAD_NAME) {
+ public void run() {
+ try {
+ initMergeSplitZipFile(zipModel, outputZipFile, progressMonitor);
+ } catch (ZipException e) {
+ }
+ }
+ };
+ thread.start();
+ } else {
+ initMergeSplitZipFile(zipModel, outputZipFile, progressMonitor);
+ }
+ }
+
+ private void initMergeSplitZipFile(ZipModel zipModel, File outputZipFile,
+ ProgressMonitor progressMonitor) throws ZipException {
+ if (zipModel == null) {
+ ZipException e = new ZipException("one of the input parameters is null, cannot merge split zip file");
+ progressMonitor.endProgressMonitorError(e);
+ throw e;
+ }
+
+ if (!zipModel.isSplitArchive()) {
+ ZipException e = new ZipException("archive not a split zip file");
+ progressMonitor.endProgressMonitorError(e);
+ throw e;
+ }
+
+ OutputStream outputStream = null;
+ RandomAccessFile inputStream = null;
+ ArrayList fileSizeList = new ArrayList();
+ long totBytesWritten = 0;
+ boolean splitSigRemoved = false;
+ try {
+
+ int totNoOfSplitFiles = zipModel.getEndCentralDirRecord().getNoOfThisDisk();
+
+ if (totNoOfSplitFiles <= 0) {
+ throw new ZipException("corrupt zip model, archive not a split zip file");
+ }
+
+ outputStream = prepareOutputStreamForMerge(outputZipFile);
+ for (int i = 0; i <= totNoOfSplitFiles; i++) {
+ inputStream = createSplitZipFileHandler(zipModel, i);
+
+ int start = 0;
+ Long end = new Long(inputStream.length());
+
+ if (i == 0) {
+ if (zipModel.getCentralDirectory() != null &&
+ zipModel.getCentralDirectory().getFileHeaders() != null &&
+ zipModel.getCentralDirectory().getFileHeaders().size() > 0) {
+ byte[] buff = new byte[4];
+ inputStream.seek(0);
+ inputStream.read(buff);
+ if (Raw.readIntLittleEndian(buff, 0) == InternalZipConstants.SPLITSIG) {
+ start = 4;
+ splitSigRemoved = true;
+ }
+ }
+ }
+
+ if (i == totNoOfSplitFiles) {
+ end = new Long(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir());
+ }
+
+ copyFile(inputStream, outputStream, start, end.longValue(), progressMonitor);
+ totBytesWritten += (end.longValue() - start);
+ if (progressMonitor.isCancelAllTasks()) {
+ progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
+ progressMonitor.setState(ProgressMonitor.STATE_READY);
+ return;
+ }
+
+ fileSizeList.add(end);
+
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+
+ ZipModel newZipModel = (ZipModel)zipModel.clone();
+ newZipModel.getEndCentralDirRecord().setOffsetOfStartOfCentralDir(totBytesWritten);
+
+ updateSplitZipModel(newZipModel, fileSizeList, splitSigRemoved);
+
+ HeaderWriter headerWriter = new HeaderWriter();
+ headerWriter.finalizeZipFileWithoutValidations(newZipModel, outputStream);
+
+ progressMonitor.endProgressMonitorSuccess();
+
+ } catch (IOException e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw new ZipException(e);
+ } catch (Exception e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw new ZipException(e);
+ } finally {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates an input stream for the split part of the zip file
+ * @return Zip4jInputStream
+ * @throws ZipException
+ */
+
+ private RandomAccessFile createSplitZipFileHandler(ZipModel zipModel, int partNumber) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("zip model is null, cannot create split file handler");
+ }
+
+ if (partNumber < 0) {
+ throw new ZipException("invlaid part number, cannot create split file handler");
+ }
+
+ try {
+ String curZipFile = zipModel.getZipFile();
+ String partFile = null;
+ if (partNumber == zipModel.getEndCentralDirRecord().getNoOfThisDisk()) {
+ partFile = zipModel.getZipFile();
+ } else {
+ if (partNumber >= 9) {
+ partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z" + (partNumber+ 1);
+ } else{
+ partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z0" + (partNumber+ 1);
+ }
+ }
+ File tmpFile = new File(partFile);
+
+ if (!Zip4jUtil.checkFileExists(tmpFile)) {
+ throw new ZipException("split file does not exist: " + partFile);
+ }
+
+ return new RandomAccessFile(tmpFile, InternalZipConstants.READ_MODE);
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+
+ }
+
+ private OutputStream prepareOutputStreamForMerge(File outFile) throws ZipException {
+ if (outFile == null) {
+ throw new ZipException("outFile is null, cannot create outputstream");
+ }
+
+ try {
+ return new FileOutputStream(outFile);
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void updateSplitZipModel(ZipModel zipModel, ArrayList fileSizeList, boolean splitSigRemoved) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("zip model is null, cannot update split zip model");
+ }
+
+ zipModel.setSplitArchive(false);
+ updateSplitFileHeader(zipModel, fileSizeList, splitSigRemoved);
+ updateSplitEndCentralDirectory(zipModel);
+ if (zipModel.isZip64Format()) {
+ updateSplitZip64EndCentralDirLocator(zipModel, fileSizeList);
+ updateSplitZip64EndCentralDirRec(zipModel, fileSizeList);
+ }
+ }
+
+ private void updateSplitFileHeader(ZipModel zipModel, ArrayList fileSizeList, boolean splitSigRemoved) throws ZipException {
+ try {
+
+ if (zipModel.getCentralDirectory()== null) {
+ throw new ZipException("corrupt zip model - getCentralDirectory, cannot update split zip model");
+ }
+
+ int fileHeaderCount = zipModel.getCentralDirectory().getFileHeaders().size();
+ int splitSigOverhead = 0;
+ if (splitSigRemoved)
+ splitSigOverhead = 4;
+
+ for (int i = 0; i < fileHeaderCount; i++) {
+ long offsetLHToAdd = 0;
+
+ for (int j = 0; j < ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getDiskNumberStart(); j++) {
+ offsetLHToAdd += ((Long)fileSizeList.get(j)).longValue();
+ }
+ ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).setOffsetLocalHeader(
+ ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getOffsetLocalHeader() +
+ offsetLHToAdd - splitSigOverhead);
+ ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).setDiskNumberStart(0);
+ }
+
+ } catch (ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void updateSplitEndCentralDirectory(ZipModel zipModel) throws ZipException {
+ try {
+ if (zipModel == null) {
+ throw new ZipException("zip model is null - cannot update end of central directory for split zip model");
+ }
+
+ if (zipModel.getCentralDirectory()== null) {
+ throw new ZipException("corrupt zip model - getCentralDirectory, cannot update split zip model");
+ }
+
+ zipModel.getEndCentralDirRecord().setNoOfThisDisk(0);
+ zipModel.getEndCentralDirRecord().setNoOfThisDiskStartOfCentralDir(0);
+ zipModel.getEndCentralDirRecord().setTotNoOfEntriesInCentralDir(
+ zipModel.getCentralDirectory().getFileHeaders().size());
+ zipModel.getEndCentralDirRecord().setTotNoOfEntriesInCentralDirOnThisDisk(
+ zipModel.getCentralDirectory().getFileHeaders().size());
+
+ } catch (ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private void updateSplitZip64EndCentralDirLocator(ZipModel zipModel, ArrayList fileSizeList) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("zip model is null, cannot update split Zip64 end of central directory locator");
+ }
+
+ if (zipModel.getZip64EndCentralDirLocator() == null) {
+ return;
+ }
+
+ zipModel.getZip64EndCentralDirLocator().setNoOfDiskStartOfZip64EndOfCentralDirRec(0);
+ long offsetZip64EndCentralDirRec = 0;
+
+ for (int i = 0; i < fileSizeList.size(); i++) {
+ offsetZip64EndCentralDirRec += ((Long)fileSizeList.get(i)).longValue();
+ }
+ zipModel.getZip64EndCentralDirLocator().setOffsetZip64EndOfCentralDirRec(
+ ((Zip64EndCentralDirLocator)zipModel.getZip64EndCentralDirLocator()).getOffsetZip64EndOfCentralDirRec() +
+ offsetZip64EndCentralDirRec);
+ zipModel.getZip64EndCentralDirLocator().setTotNumberOfDiscs(1);
+ }
+
+ private void updateSplitZip64EndCentralDirRec(ZipModel zipModel, ArrayList fileSizeList) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("zip model is null, cannot update split Zip64 end of central directory record");
+ }
+
+ if (zipModel.getZip64EndCentralDirRecord() == null) {
+ return;
+ }
+
+ zipModel.getZip64EndCentralDirRecord().setNoOfThisDisk(0);
+ zipModel.getZip64EndCentralDirRecord().setNoOfThisDiskStartOfCentralDir(0);
+ zipModel.getZip64EndCentralDirRecord().setTotNoOfEntriesInCentralDirOnThisDisk(
+ zipModel.getEndCentralDirRecord().getTotNoOfEntriesInCentralDir());
+
+ long offsetStartCenDirWRTStartDiskNo = 0;
+
+ for (int i = 0; i < fileSizeList.size(); i++) {
+ offsetStartCenDirWRTStartDiskNo += ((Long)fileSizeList.get(i)).longValue();
+ }
+
+ zipModel.getZip64EndCentralDirRecord().setOffsetStartCenDirWRTStartDiskNo(
+ ((Zip64EndCentralDirRecord)zipModel.getZip64EndCentralDirRecord()).getOffsetStartCenDirWRTStartDiskNo() +
+ offsetStartCenDirWRTStartDiskNo);
+ }
+
+ public void setComment(ZipModel zipModel, String comment) throws ZipException {
+ if (comment == null) {
+ throw new ZipException("comment is null, cannot update Zip file with comment");
+ }
+
+ if (zipModel == null) {
+ throw new ZipException("zipModel is null, cannot update Zip file with comment");
+ }
+
+ String encodedComment = comment;
+ byte[] commentBytes = comment.getBytes();
+ int commentLength = comment.length();
+
+ if (Zip4jUtil.isSupportedCharset(InternalZipConstants.CHARSET_COMMENTS_DEFAULT)) {
+ try {
+ encodedComment = new String(comment.getBytes(InternalZipConstants.CHARSET_COMMENTS_DEFAULT), InternalZipConstants.CHARSET_COMMENTS_DEFAULT);
+ commentBytes = encodedComment.getBytes(InternalZipConstants.CHARSET_COMMENTS_DEFAULT);
+ commentLength = encodedComment.length();
+ } catch (UnsupportedEncodingException e) {
+ encodedComment = comment;
+ commentBytes = comment.getBytes();
+ commentLength = comment.length();
+ }
+ }
+
+ if (commentLength > InternalZipConstants.MAX_ALLOWED_ZIP_COMMENT_LENGTH) {
+ throw new ZipException("comment length exceeds maximum length");
+ }
+
+ zipModel.getEndCentralDirRecord().setComment(encodedComment);
+ zipModel.getEndCentralDirRecord().setCommentBytes(commentBytes);
+ zipModel.getEndCentralDirRecord().setCommentLength(commentLength);
+
+ SplitOutputStream outputStream = null;
+
+ try {
+ HeaderWriter headerWriter = new HeaderWriter();
+ outputStream = new SplitOutputStream(zipModel.getZipFile());
+
+ if (zipModel.isZip64Format()) {
+ outputStream.seek(zipModel.getZip64EndCentralDirRecord().getOffsetStartCenDirWRTStartDiskNo());
+ } else {
+ outputStream.seek(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir());
+ }
+
+ headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream);
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ } catch (IOException e) {
+ throw new ZipException(e);
+ } finally {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ }
+ }
+
+ public void initProgressMonitorForRemoveOp(ZipModel zipModel,
+ FileHeader fileHeader, ProgressMonitor progressMonitor) throws ZipException {
+ if (zipModel == null || fileHeader == null || progressMonitor == null) {
+ throw new ZipException("one of the input parameters is null, cannot calculate total work");
+ }
+
+ progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_REMOVE);
+ progressMonitor.setFileName(fileHeader.getFileName());
+ progressMonitor.setTotalWork(calculateTotalWorkForRemoveOp(zipModel, fileHeader));
+ progressMonitor.setState(ProgressMonitor.STATE_BUSY);
+ }
+
+ private long calculateTotalWorkForRemoveOp(ZipModel zipModel, FileHeader fileHeader) throws ZipException {
+ return Zip4jUtil.getFileLengh(new File(zipModel.getZipFile())) - fileHeader.getCompressedSize();
+ }
+
+ public void initProgressMonitorForMergeOp(ZipModel zipModel, ProgressMonitor progressMonitor) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("zip model is null, cannot calculate total work for merge op");
+ }
+
+ progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_MERGE);
+ progressMonitor.setFileName(zipModel.getZipFile());
+ progressMonitor.setTotalWork(calculateTotalWorkForMergeOp(zipModel));
+ progressMonitor.setState(ProgressMonitor.STATE_BUSY);
+ }
+
+ private long calculateTotalWorkForMergeOp(ZipModel zipModel) throws ZipException {
+ long totSize = 0;
+ if (zipModel.isSplitArchive()) {
+ int totNoOfSplitFiles = zipModel.getEndCentralDirRecord().getNoOfThisDisk();
+ String partFile = null;
+ String curZipFile = zipModel.getZipFile();
+ int partNumber = 0;
+ for (int i = 0; i <= totNoOfSplitFiles; i++) {
+ if (partNumber == zipModel.getEndCentralDirRecord().getNoOfThisDisk()) {
+ partFile = zipModel.getZipFile();
+ } else {
+ if (partNumber >= 9) {
+ partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z" + (partNumber+ 1);
+ } else{
+ partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z0" + (partNumber+ 1);
+ }
+ }
+
+ totSize += Zip4jUtil.getFileLengh(new File(partFile));
+ }
+
+ }
+ return totSize;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/util/CRCUtil.java b/harmony/src/main/java/com/zip4j/util/CRCUtil.java
new file mode 100644
index 0000000..7462f68
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/util/CRCUtil.java
@@ -0,0 +1,86 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.util;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.progress.ProgressMonitor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.CRC32;
+
+public class CRCUtil {
+
+ private static final int BUF_SIZE = 1 << 14; //16384
+
+ public static long computeFileCRC(String inputFile) throws ZipException {
+ return computeFileCRC(inputFile, null);
+ }
+
+ /**
+ * Calculates CRC of a file
+ * @param inputFile - file for which crc has to be calculated
+ * @return crc of the file
+ * @throws ZipException
+ */
+ public static long computeFileCRC(String inputFile, ProgressMonitor progressMonitor) throws ZipException {
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(inputFile)) {
+ throw new ZipException("input file is null or empty, cannot calculate CRC for the file");
+ }
+ InputStream inputStream = null;
+ try {
+ Zip4jUtil.checkFileReadAccess(inputFile);
+
+ inputStream = new FileInputStream(new File(inputFile));
+
+ byte[] buff = new byte[BUF_SIZE];
+ int readLen = -2;
+ CRC32 crc32 = new CRC32();
+
+ while ((readLen = inputStream.read(buff)) != -1) {
+ crc32.update(buff, 0, readLen);
+ if (progressMonitor != null) {
+ progressMonitor.updateWorkCompleted(readLen);
+ if (progressMonitor.isCancelAllTasks()) {
+ progressMonitor
+ .setResult(ProgressMonitor.RESULT_CANCELLED);
+ progressMonitor.setState(ProgressMonitor.STATE_READY);
+ return 0;
+ }
+ }
+ }
+
+ return crc32.getValue();
+ } catch (IOException e) {
+ throw new ZipException(e);
+ } catch (Exception e) {
+ throw new ZipException(e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ throw new ZipException("error while closing the file after calculating crc");
+ }
+ }
+ }
+ }
+
+}
diff --git a/harmony/src/main/java/com/zip4j/util/InternalZipConstants.java b/harmony/src/main/java/com/zip4j/util/InternalZipConstants.java
new file mode 100644
index 0000000..9449c7a
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/util/InternalZipConstants.java
@@ -0,0 +1,174 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.util;
+
+public interface InternalZipConstants {
+
+ /*
+ * Header signatures
+ */
+ // Whenever a new Signature is added here, make sure to add it
+ // in Zip4jUtil.getAllHeaderSignatures()
+ static long LOCSIG = 0x04034b50L; // "PK\003\004"
+ static long EXTSIG = 0x08074b50L; // "PK\007\008"
+ static long CENSIG = 0x02014b50L; // "PK\001\002"
+ static long ENDSIG = 0x06054b50L; // "PK\005\006"
+ static long DIGSIG = 0x05054b50L;
+ static long ARCEXTDATREC = 0x08064b50L;
+ static long SPLITSIG = 0x08074b50L;
+ static long ZIP64ENDCENDIRLOC = 0x07064b50L;
+ static long ZIP64ENDCENDIRREC = 0x06064b50;
+ static int EXTRAFIELDZIP64LENGTH = 0x0001;
+ static int AESSIG = 0x9901;
+
+ /*
+ * Header sizes in bytes (including signatures)
+ */
+ static final int LOCHDR = 30; // LOC header size
+ static final int EXTHDR = 16; // EXT header size
+ static final int CENHDR = 46; // CEN header size
+ static final int ENDHDR = 22; // END header size
+
+ /*
+ * Local file (LOC) header field offsets
+ */
+ static final int LOCVER = 4; // version needed to extract
+ static final int LOCFLG = 6; // general purpose bit flag
+ static final int LOCHOW = 8; // compression method
+ static final int LOCTIM = 10; // modification time
+ static final int LOCCRC = 14; // uncompressed file crc-32 value
+ static final int LOCSIZ = 18; // compressed size
+ static final int LOCLEN = 22; // uncompressed size
+ static final int LOCNAM = 26; // filename length
+ static final int LOCEXT = 28; // extra field length
+
+ /*
+ * Extra local (EXT) header field offsets
+ */
+ static final int EXTCRC = 4; // uncompressed file crc-32 value
+ static final int EXTSIZ = 8; // compressed size
+ static final int EXTLEN = 12; // uncompressed size
+
+ /*
+ * Central directory (CEN) header field offsets
+ */
+ static final int CENVEM = 4; // version made by
+ static final int CENVER = 6; // version needed to extract
+ static final int CENFLG = 8; // encrypt, decrypt flags
+ static final int CENHOW = 10; // compression method
+ static final int CENTIM = 12; // modification time
+ static final int CENCRC = 16; // uncompressed file crc-32 value
+ static final int CENSIZ = 20; // compressed size
+ static final int CENLEN = 24; // uncompressed size
+ static final int CENNAM = 28; // filename length
+ static final int CENEXT = 30; // extra field length
+ static final int CENCOM = 32; // comment length
+ static final int CENDSK = 34; // disk number start
+ static final int CENATT = 36; // internal file attributes
+ static final int CENATX = 38; // external file attributes
+ static final int CENOFF = 42; // LOC header offset
+
+ /*
+ * End of central directory (END) header field offsets
+ */
+ static final int ENDSUB = 8; // number of entries on this disk
+ static final int ENDTOT = 10; // total number of entries
+ static final int ENDSIZ = 12; // central directory size in bytes
+ static final int ENDOFF = 16; // offset of first CEN header
+ static final int ENDCOM = 20; // zip file comment length
+
+ static final int STD_DEC_HDR_SIZE = 12;
+
+ //AES Constants
+ static final int AES_AUTH_LENGTH = 10;
+ static final int AES_BLOCK_SIZE = 16;
+
+ static final int MIN_SPLIT_LENGTH = 65536;
+
+ static final long ZIP_64_LIMIT = 4294967295L;
+
+ public static String OFFSET_CENTRAL_DIR = "offsetCentralDir";
+
+ public static final String VERSION = "1.3.1";
+
+ public static final int MODE_ZIP = 1;
+
+ public static final int MODE_UNZIP = 2;
+
+ public static final String WRITE_MODE = "rw";
+
+ public static final String READ_MODE = "r";
+
+ public static final int BUFF_SIZE = 1024 * 4;
+
+ public static final int FILE_MODE_NONE = 0;
+
+ public static final int FILE_MODE_READ_ONLY = 1;
+
+ public static final int FILE_MODE_HIDDEN = 2;
+
+ public static final int FILE_MODE_ARCHIVE = 32;
+
+ public static final int FILE_MODE_READ_ONLY_HIDDEN = 3;
+
+ public static final int FILE_MODE_READ_ONLY_ARCHIVE = 33;
+
+ public static final int FILE_MODE_HIDDEN_ARCHIVE = 34;
+
+ public static final int FILE_MODE_READ_ONLY_HIDDEN_ARCHIVE = 35;
+
+ public static final int FILE_MODE_SYSTEM = 38;
+
+ public static final int FOLDER_MODE_NONE = 16;
+
+ public static final int FOLDER_MODE_HIDDEN = 18;
+
+ public static final int FOLDER_MODE_ARCHIVE = 48;
+
+ public static final int FOLDER_MODE_HIDDEN_ARCHIVE = 50;
+
+ // Update local file header constants
+ // This value holds the number of bytes to skip from
+ // the offset of start of local header
+ public static final int UPDATE_LFH_CRC = 14;
+
+ public static final int UPDATE_LFH_COMP_SIZE = 18;
+
+ public static final int UPDATE_LFH_UNCOMP_SIZE = 22;
+
+ public static final int LIST_TYPE_FILE = 1;
+
+ public static final int LIST_TYPE_STRING = 2;
+
+ public static final int UFT8_NAMES_FLAG = 1 << 11;
+
+ public static final String CHARSET_UTF8 = "UTF8";
+
+ public static final String CHARSET_CP850 = "Cp850";
+
+ public static final String CHARSET_COMMENTS_DEFAULT = "windows-1254";
+
+ public static final String CHARSET_DEFAULT = System.getProperty("file.encoding");
+
+ public static final String FILE_SEPARATOR = System.getProperty("file.separator");
+
+ public static final String ZIP_FILE_SEPARATOR = "/";
+
+ public static final String THREAD_NAME = "Zip4j";
+
+ public static final int MAX_ALLOWED_ZIP_COMMENT_LENGTH = 0xFFFF;
+}
diff --git a/harmony/src/main/java/com/zip4j/util/Raw.java b/harmony/src/main/java/com/zip4j/util/Raw.java
new file mode 100644
index 0000000..129d50c
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/util/Raw.java
@@ -0,0 +1,184 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.util;
+
+import com.zip4j.exception.ZipException;
+
+import java.io.DataInput;
+import java.io.IOException;
+
+public class Raw
+{
+ public static long readLongLittleEndian(byte[] array,int pos){
+ long temp = 0;
+ temp |= array[pos+7]&0xff;
+ temp <<=8;
+ temp |= array[pos+6]&0xff;
+ temp <<=8;
+ temp |= array[pos+5]&0xff;
+ temp <<=8;
+ temp |= array[pos+4]&0xff;
+ temp <<=8;
+ temp |= array[pos+3]&0xff;
+ temp <<=8;
+ temp |= array[pos+2]&0xff;
+ temp <<=8;
+ temp |= array[pos+1]&0xff;
+ temp <<=8;
+ temp |= array[pos]&0xff;
+ return temp;
+ }
+
+ public static int readLeInt(DataInput di, byte[] b) throws ZipException {
+ try {
+ di.readFully(b, 0, 4);
+ } catch (IOException e) {
+ throw new ZipException(e);
+ }
+ return ((b[0] & 0xff) | (b[1] & 0xff) << 8)
+ | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;
+ }
+
+ public static int readShortLittleEndian(byte[] b, int off){
+ return (b[off] & 0xff) | (b[off+1] & 0xff) << 8;
+ }
+
+ public static final short readShortBigEndian(byte[] array, int pos) {
+ short temp = 0;
+ temp |= array[pos] & 0xff;
+ temp <<= 8;
+ temp |= array[pos + 1] & 0xff;
+ return temp;
+ }
+
+ public static int readIntLittleEndian(byte[] b, int off){
+ return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8)
+ | ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16;
+ }
+
+ public static byte[] toByteArray(int in,int outSize) {
+ byte[] out = new byte[outSize];
+ byte[] intArray = toByteArray(in);
+ for( int i=0; i> 8);
+ out[2] = (byte)(in >> 16);
+ out[3] = (byte)(in >> 24);
+
+ return out;
+ }
+
+ public static final void writeShortLittleEndian(byte[] array, int pos,
+ short value) {
+ array[pos +1] = (byte) (value >>> 8);
+ array[pos ] = (byte) (value & 0xFF);
+
+ }
+
+ public static final void writeIntLittleEndian(byte[] array, int pos,int value) {
+ array[pos+3] = (byte) (value >>>24);
+ array[pos+2] = (byte) (value >>>16);
+ array[pos+1] = (byte) (value >>>8);
+ array[pos] = (byte) (value &0xFF);
+
+ }
+
+ public static void writeLongLittleEndian(byte[] array, int pos, long value){
+ array[pos+7] = (byte) (value >>>56);
+ array[pos+6] = (byte) (value >>>48);
+ array[pos+5] = (byte) (value >>>40);
+ array[pos+4] = (byte) (value >>>32);
+ array[pos+3] = (byte) (value >>>24);
+ array[pos+2] = (byte) (value >>>16);
+ array[pos+1] = (byte) (value >>>8);
+ array[pos] = (byte) (value &0xFF);
+ }
+
+ public static byte bitArrayToByte(int[] bitArray) throws ZipException {
+ if (bitArray == null) {
+ throw new ZipException("bit array is null, cannot calculate byte from bits");
+ }
+
+ if (bitArray.length != 8) {
+ throw new ZipException("invalid bit array length, cannot calculate byte");
+ }
+
+ if(!checkBits(bitArray)) {
+ throw new ZipException("invalid bits provided, bits contain other values than 0 or 1");
+ }
+
+ int retNum = 0;
+ for (int i = 0; i < bitArray.length; i++) {
+ retNum += Math.pow(2, i) * bitArray[i];
+ }
+
+ return (byte)retNum;
+ }
+
+ private static boolean checkBits(int[] bitArray) {
+ for (int i = 0; i < bitArray.length; i++) {
+ if (bitArray[i] != 0 && bitArray[i] != 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static void prepareBuffAESIVBytes(byte[] buff, int nonce, int length) {
+ buff[0] = (byte)nonce;
+ buff[1] = (byte)(nonce >> 8);
+ buff[2] = (byte)(nonce >> 16);
+ buff[3] = (byte)(nonce >> 24);
+ buff[4] = 0;
+ buff[5] = 0;
+ buff[6] = 0;
+ buff[7] = 0;
+ buff[8] = 0;
+ buff[9] = 0;
+ buff[10] = 0;
+ buff[11] = 0;
+ buff[12] = 0;
+ buff[13] = 0;
+ buff[14] = 0;
+ buff[15] = 0;
+ }
+
+ /**
+ * Converts a char array to byte array
+ * @param charArray
+ * @return byte array representation of the input char array
+ */
+ public static byte[] convertCharArrayToByteArray(char[] charArray) {
+ if (charArray == null) {
+ throw new NullPointerException();
+ }
+
+ byte[] bytes = new byte[charArray.length];
+ for(int i=0;i= 0);
+ }
+
+ public static void setFileReadOnly(File file) throws ZipException {
+ if (file == null) {
+ throw new ZipException("input file is null. cannot set read only file attribute");
+ }
+
+ if (file.exists()) {
+ file.setReadOnly();
+ }
+ }
+
+ public static void setFileHidden(File file) throws ZipException {
+ if (file == null) {
+ throw new ZipException("input file is null. cannot set hidden file attribute");
+ }
+
+ if (!isWindows()) {
+ return;
+ }
+
+ if (file.exists()) {
+ try {
+ Runtime.getRuntime().exec("attrib +H \"" + file.getAbsolutePath() + "\"");
+ } catch (IOException e) {
+ // do nothing as this is not of a higher priority
+ // add log statements here when logging is done
+ }
+ }
+ }
+
+ public static void setFileArchive(File file) throws ZipException {
+ if (file == null) {
+ throw new ZipException("input file is null. cannot set archive file attribute");
+ }
+
+ if (!isWindows()) {
+ return;
+ }
+
+ if (file.exists()) {
+ try {
+ if (file.isDirectory()) {
+ Runtime.getRuntime().exec("attrib +A \"" + file.getAbsolutePath() + "\"");
+ } else {
+ Runtime.getRuntime().exec("attrib +A \"" + file.getAbsolutePath() + "\"");
+ }
+
+ } catch (IOException e) {
+ // do nothing as this is not of a higher priority
+ // add log statements here when logging is done
+ }
+ }
+ }
+
+ public static void setFileSystemMode(File file) throws ZipException {
+ if (file == null) {
+ throw new ZipException("input file is null. cannot set archive file attribute");
+ }
+
+ if (!isWindows()) {
+ return;
+ }
+
+ if (file.exists()) {
+ try {
+ Runtime.getRuntime().exec("attrib +S \"" + file.getAbsolutePath() + "\"");
+ } catch (IOException e) {
+ // do nothing as this is not of a higher priority
+ // add log statements here when logging is done
+ }
+ }
+ }
+
+ public static long getLastModifiedFileTime(File file, TimeZone timeZone) throws ZipException {
+ if (file == null) {
+ throw new ZipException("input file is null, cannot read last modified file time");
+ }
+
+ if (!file.exists()) {
+ throw new ZipException("input file does not exist, cannot read last modified file time");
+ }
+
+ return file.lastModified();
+ }
+
+ public static String getFileNameFromFilePath(File file) throws ZipException {
+ if (file == null) {
+ throw new ZipException("input file is null, cannot get file name");
+ }
+
+ if (file.isDirectory()) {
+ return null;
+ }
+
+ return file.getName();
+ }
+
+ public static long getFileLengh(String file) throws ZipException {
+ if (!isStringNotNullAndNotEmpty(file)) {
+ throw new ZipException("invalid file name");
+ }
+
+ return getFileLengh(new File(file));
+ }
+
+ public static long getFileLengh(File file) throws ZipException {
+ if (file == null) {
+ throw new ZipException("input file is null, cannot calculate file length");
+ }
+
+ if (file.isDirectory()) {
+ return -1;
+ }
+
+ return file.length();
+ }
+
+ /**
+ * Converts input time from Java to DOS format
+ * @param time
+ * @return time in DOS format
+ */
+ public static long javaToDosTime(long time) {
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(time);
+
+ int year = cal.get(Calendar.YEAR);
+ if (year < 1980) {
+ return (1 << 21) | (1 << 16);
+ }
+ return (year - 1980) << 25 | (cal.get(Calendar.MONTH) + 1) << 21 |
+ cal.get(Calendar.DATE) << 16 | cal.get(Calendar.HOUR_OF_DAY) << 11 | cal.get(Calendar.MINUTE) << 5 |
+ cal.get(Calendar.SECOND) >> 1;
+ }
+
+ /**
+ * Converts time in dos format to Java format
+ * @param dosTime
+ * @return time in java format
+ */
+ public static long dosToJavaTme(int dosTime) {
+ int sec = 2 * (dosTime & 0x1f);
+ int min = (dosTime >> 5) & 0x3f;
+ int hrs = (dosTime >> 11) & 0x1f;
+ int day = (dosTime >> 16) & 0x1f;
+ int mon = ((dosTime >> 21) & 0xf) - 1;
+ int year = ((dosTime >> 25) & 0x7f) + 1980;
+
+ Calendar cal = Calendar.getInstance();
+ cal.set(year, mon, day, hrs, min, sec);
+ cal.set(Calendar.MILLISECOND, 0);
+ return cal.getTime().getTime();
+ }
+
+ public static FileHeader getFileHeader(ZipModel zipModel, String fileName) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("zip model is null, cannot determine file header for fileName: " + fileName);
+ }
+
+ if (!isStringNotNullAndNotEmpty(fileName)) {
+ throw new ZipException("file name is null, cannot determine file header for fileName: " + fileName);
+ }
+
+ FileHeader fileHeader = null;
+ fileHeader = getFileHeaderWithExactMatch(zipModel, fileName);
+
+ if (fileHeader == null) {
+ fileName = fileName.replaceAll("\\\\", "/");
+ fileHeader = getFileHeaderWithExactMatch(zipModel, fileName);
+
+ if (fileHeader == null) {
+ fileName = fileName.replaceAll("/", "\\\\");
+ fileHeader = getFileHeaderWithExactMatch(zipModel, fileName);
+ }
+ }
+
+ return fileHeader;
+ }
+
+ public static FileHeader getFileHeaderWithExactMatch(ZipModel zipModel, String fileName) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("zip model is null, cannot determine file header with exact match for fileName: " + fileName);
+ }
+
+ if (!isStringNotNullAndNotEmpty(fileName)) {
+ throw new ZipException("file name is null, cannot determine file header with exact match for fileName: " + fileName);
+ }
+
+ if (zipModel.getCentralDirectory() == null) {
+ throw new ZipException("central directory is null, cannot determine file header with exact match for fileName: " + fileName);
+ }
+
+ if (zipModel.getCentralDirectory().getFileHeaders() == null) {
+ throw new ZipException("file Headers are null, cannot determine file header with exact match for fileName: " + fileName);
+ }
+
+ if (zipModel.getCentralDirectory().getFileHeaders().size() <= 0) {
+ return null;
+ }
+ ArrayList fileHeaders = zipModel.getCentralDirectory().getFileHeaders();
+ for (int i = 0; i < fileHeaders.size(); i++) {
+ FileHeader fileHeader = (FileHeader)fileHeaders.get(i);
+ String fileNameForHdr = fileHeader.getFileName();
+ if (!isStringNotNullAndNotEmpty(fileNameForHdr)) {
+ continue;
+ }
+
+ if (fileName.equalsIgnoreCase(fileNameForHdr)) {
+ return fileHeader;
+ }
+ }
+
+ return null;
+ }
+
+ public static int getIndexOfFileHeader(ZipModel zipModel,
+ FileHeader fileHeader) throws ZipException {
+
+ if (zipModel == null || fileHeader == null) {
+ throw new ZipException("input parameters is null, cannot determine index of file header");
+ }
+
+ if (zipModel.getCentralDirectory() == null) {
+ throw new ZipException("central directory is null, ccannot determine index of file header");
+ }
+
+ if (zipModel.getCentralDirectory().getFileHeaders() == null) {
+ throw new ZipException("file Headers are null, cannot determine index of file header");
+ }
+
+ if (zipModel.getCentralDirectory().getFileHeaders().size() <= 0) {
+ return -1;
+ }
+ String fileName = fileHeader.getFileName();
+
+ if (!isStringNotNullAndNotEmpty(fileName)) {
+ throw new ZipException("file name in file header is empty or null, cannot determine index of file header");
+ }
+
+ ArrayList fileHeaders = zipModel.getCentralDirectory().getFileHeaders();
+ for (int i = 0; i < fileHeaders.size(); i++) {
+ FileHeader fileHeaderTmp = (FileHeader)fileHeaders.get(i);
+ String fileNameForHdr = fileHeaderTmp.getFileName();
+ if (!isStringNotNullAndNotEmpty(fileNameForHdr)) {
+ continue;
+ }
+
+ if (fileName.equalsIgnoreCase(fileNameForHdr)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static ArrayList getFilesInDirectoryRec(File path,
+ boolean readHiddenFiles) throws ZipException {
+
+ if (path == null) {
+ throw new ZipException("input path is null, cannot read files in the directory");
+ }
+
+ ArrayList result = new ArrayList();
+ File[] filesAndDirs = path.listFiles();
+ List filesDirs = Arrays.asList(filesAndDirs);
+
+ if (!path.canRead()) {
+ return result;
+ }
+
+ for(int i = 0; i < filesDirs.size(); i++) {
+ File file = (File)filesDirs.get(i);
+ if (file.isHidden() && !readHiddenFiles) {
+ return result;
+ }
+ result.add(file);
+ if (file.isDirectory()) {
+ List deeperList = getFilesInDirectoryRec(file, readHiddenFiles);
+ result.addAll(deeperList);
+ }
+ }
+ return result;
+ }
+
+ public static String getZipFileNameWithoutExt(String zipFile) throws ZipException {
+ if (!isStringNotNullAndNotEmpty(zipFile)) {
+ throw new ZipException("zip file name is empty or null, cannot determine zip file name");
+ }
+ String tmpFileName = zipFile;
+ if (zipFile.indexOf(System.getProperty("file.separator")) >= 0) {
+ tmpFileName = zipFile.substring(zipFile.lastIndexOf(System.getProperty("file.separator")));
+ }
+
+ if (tmpFileName.indexOf(".") > 0) {
+ tmpFileName = tmpFileName.substring(0, tmpFileName.lastIndexOf("."));
+ }
+ return tmpFileName;
+ }
+
+ public static byte[] convertCharset(String str) throws ZipException {
+ try {
+ byte[] converted = null;
+ String charSet = detectCharSet(str);
+ if (charSet.equals(InternalZipConstants.CHARSET_CP850)) {
+ converted = str.getBytes(InternalZipConstants.CHARSET_CP850);
+ } else if (charSet.equals(InternalZipConstants.CHARSET_UTF8)) {
+ converted = str.getBytes(InternalZipConstants.CHARSET_UTF8);
+ } else {
+ converted = str.getBytes();
+ }
+ return converted;
+ }
+ catch (UnsupportedEncodingException err) {
+ return str.getBytes();
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ /**
+ * Decodes file name based on encoding. If file name is UTF 8 encoded
+ * returns an UTF8 encoded string, else return Cp850 encoded String. If
+ * appropriate charset is not supported, then returns a System default
+ * charset encoded String
+ * @param data
+ * @param isUTF8
+ * @return String
+ */
+ public static String decodeFileName(byte[] data, boolean isUTF8) {
+ if (isUTF8) {
+ try {
+ return new String(data, InternalZipConstants.CHARSET_UTF8);
+ } catch (UnsupportedEncodingException e) {
+ return new String(data);
+ }
+ } else {
+ return getCp850EncodedString(data);
+ }
+ }
+
+ /**
+ * Returns a string in Cp850 encoding from the input bytes.
+ * If this encoding is not supported, then String with the default encoding is returned.
+ * @param data
+ * @return String
+ */
+ public static String getCp850EncodedString(byte[] data) {
+ try {
+ String retString = new String(data, InternalZipConstants.CHARSET_CP850);
+ return retString;
+ } catch (UnsupportedEncodingException e) {
+ return new String(data);
+ }
+ }
+
+ /**
+ * Returns an absoulte path for the given file path
+ * @param filePath
+ * @return String
+ */
+ public static String getAbsoluteFilePath(String filePath) throws ZipException {
+ if (!isStringNotNullAndNotEmpty(filePath)) {
+ throw new ZipException("filePath is null or empty, cannot get absolute file path");
+ }
+
+ File file = new File(filePath);
+ return file.getAbsolutePath();
+ }
+
+ /**
+ * Checks to see if all the elements in the arraylist match the given type
+ * @param sourceList - list to be checked
+ * @param type - type of elements to be present in the list (ex: File, String, etc)
+ * @return true if all elements match the given type, if not returns false
+ */
+ public static boolean checkArrayListTypes(ArrayList sourceList, int type) throws ZipException {
+
+ if (sourceList == null) {
+ throw new ZipException("input arraylist is null, cannot check types");
+ }
+
+ if (sourceList.size() <= 0) {
+ return true;
+ }
+
+ boolean invalidFound = false;
+
+ switch (type) {
+ case InternalZipConstants.LIST_TYPE_FILE:
+ for (int i = 0; i < sourceList.size(); i++) {
+ if (!(sourceList.get(i) instanceof File)) {
+ invalidFound = true;
+ break;
+ }
+ }
+ break;
+ case InternalZipConstants.LIST_TYPE_STRING:
+ for (int i = 0; i < sourceList.size(); i++) {
+ if (!(sourceList.get(i) instanceof String)) {
+ invalidFound = true;
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return !invalidFound;
+ }
+
+ /**
+ * Detects the encoding charset for the input string
+ * @param str
+ * @return String - charset for the String
+ * @throws ZipException - if input string is null. In case of any other exception
+ * this method returns default System charset
+ */
+ public static String detectCharSet(String str) throws ZipException {
+ if (str == null) {
+ throw new ZipException("input string is null, cannot detect charset");
+ }
+
+ try {
+ byte[] byteString = str.getBytes(InternalZipConstants.CHARSET_CP850);
+ String tempString = new String(byteString, InternalZipConstants.CHARSET_CP850);
+
+ if (str.equals(tempString)) {
+ return InternalZipConstants.CHARSET_CP850;
+ }
+
+ byteString = str.getBytes(InternalZipConstants.CHARSET_UTF8);
+ tempString = new String(byteString, InternalZipConstants.CHARSET_UTF8);
+
+ if (str.equals(tempString)) {
+ return InternalZipConstants.CHARSET_UTF8;
+ }
+
+ return InternalZipConstants.CHARSET_DEFAULT;
+ } catch (UnsupportedEncodingException e) {
+ return InternalZipConstants.CHARSET_DEFAULT;
+ } catch (Exception e) {
+ return InternalZipConstants.CHARSET_DEFAULT;
+ }
+ }
+
+ /**
+ * returns the length of the string by wrapping it in a byte buffer with
+ * the appropriate charset of the input string and returns the limit of the
+ * byte buffer
+ * @param str
+ * @return length of the string
+ * @throws ZipException
+ */
+ public static int getEncodedStringLength(String str) throws ZipException {
+ if (!isStringNotNullAndNotEmpty(str)) {
+ throw new ZipException("input string is null, cannot calculate encoded String length");
+ }
+
+ String charset = detectCharSet(str);
+ return getEncodedStringLength(str, charset);
+ }
+
+ /**
+ * returns the length of the string in the input encoding
+ * @param str
+ * @param charset
+ * @return int
+ * @throws ZipException
+ */
+ public static int getEncodedStringLength(String str, String charset) throws ZipException {
+ if (!isStringNotNullAndNotEmpty(str)) {
+ throw new ZipException("input string is null, cannot calculate encoded String length");
+ }
+
+ if (!isStringNotNullAndNotEmpty(charset)) {
+ throw new ZipException("encoding is not defined, cannot calculate string length");
+ }
+
+ ByteBuffer byteBuffer = null;
+
+ try {
+ if (charset.equals(InternalZipConstants.CHARSET_CP850)) {
+ byteBuffer = ByteBuffer.wrap(str.getBytes(InternalZipConstants.CHARSET_CP850));
+ } else if (charset.equals(InternalZipConstants.CHARSET_UTF8)) {
+ byteBuffer = ByteBuffer.wrap(str.getBytes(InternalZipConstants.CHARSET_UTF8));
+ } else {
+ byteBuffer = ByteBuffer.wrap(str.getBytes(charset));
+ }
+ } catch (UnsupportedEncodingException e) {
+ byteBuffer = ByteBuffer.wrap(str.getBytes());
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+
+ return byteBuffer.limit();
+ }
+
+ /**
+ * Checks if the input charset is supported
+ * @param charset
+ * @return boolean
+ * @throws ZipException
+ */
+ public static boolean isSupportedCharset(String charset) throws ZipException {
+ if (!isStringNotNullAndNotEmpty(charset)) {
+ throw new ZipException("charset is null or empty, cannot check if it is supported");
+ }
+
+ try {
+ new String("a".getBytes(), charset);
+ return true;
+ } catch (UnsupportedEncodingException e) {
+ return false;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ }
+ }
+
+ public static ArrayList getSplitZipFiles(ZipModel zipModel) throws ZipException {
+ if (zipModel == null) {
+ throw new ZipException("cannot get split zip files: zipmodel is null");
+ }
+
+ if (zipModel.getEndCentralDirRecord() == null) {
+ return null;
+ }
+
+ ArrayList retList = new ArrayList();
+ String currZipFile = zipModel.getZipFile();
+ String zipFileName = (new File(currZipFile)).getName();
+ String partFile = null;
+
+ if (!isStringNotNullAndNotEmpty(currZipFile)) {
+ throw new ZipException("cannot get split zip files: zipfile is null");
+ }
+
+ if (!zipModel.isSplitArchive()) {
+ retList.add(currZipFile);
+ return retList;
+ }
+
+ int numberOfThisDisk = zipModel.getEndCentralDirRecord().getNoOfThisDisk();
+
+ if (numberOfThisDisk == 0) {
+ retList.add(currZipFile);
+ return retList;
+ } else {
+ for (int i = 0; i <= numberOfThisDisk; i++) {
+ if (i == numberOfThisDisk) {
+ retList.add(zipModel.getZipFile());
+ } else {
+ String fileExt = ".z0";
+ if (i > 9) {
+ fileExt = ".z";
+ }
+ partFile = (zipFileName.indexOf(".") >= 0) ? currZipFile.substring(0, currZipFile.lastIndexOf(".")) : currZipFile + fileExt + (i + 1);
+ retList.add(partFile);
+ }
+ }
+ }
+ return retList;
+ }
+
+ public static String getRelativeFileName(String file, String rootFolderInZip, String rootFolderPath) throws ZipException {
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(file)) {
+ throw new ZipException("input file path/name is empty, cannot calculate relative file name");
+ }
+
+ String fileName = null;
+
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(rootFolderPath)) {
+
+ File rootFolderFile = new File(rootFolderPath);
+
+ String rootFolderFileRef = rootFolderFile.getPath();
+
+ if (!rootFolderFileRef.endsWith(InternalZipConstants.FILE_SEPARATOR)) {
+ rootFolderFileRef += InternalZipConstants.FILE_SEPARATOR;
+ }
+
+ String tmpFileName = file.substring(rootFolderFileRef.length());
+ if (tmpFileName.startsWith(System.getProperty("file.separator"))) {
+ tmpFileName = tmpFileName.substring(1);
+ }
+
+ File tmpFile = new File(file);
+
+ if (tmpFile.isDirectory()) {
+ tmpFileName = tmpFileName.replaceAll("\\\\", "/");
+ tmpFileName += InternalZipConstants.ZIP_FILE_SEPARATOR;
+ } else {
+ String bkFileName = tmpFileName.substring(0, tmpFileName.lastIndexOf(tmpFile.getName()));
+ bkFileName = bkFileName.replaceAll("\\\\", "/");
+ tmpFileName = bkFileName + tmpFile.getName();
+ }
+
+ fileName = tmpFileName;
+ } else {
+ File relFile = new File(file);
+ if (relFile.isDirectory()) {
+ fileName = relFile.getName() + InternalZipConstants.ZIP_FILE_SEPARATOR;
+ } else {
+ fileName = Zip4jUtil.getFileNameFromFilePath(new File(file));
+ }
+ }
+
+ if (Zip4jUtil.isStringNotNullAndNotEmpty(rootFolderInZip)) {
+ fileName = rootFolderInZip + fileName;
+ }
+
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(fileName)) {
+ throw new ZipException("Error determining file name");
+ }
+
+ return fileName;
+ }
+
+ public static long[] getAllHeaderSignatures() {
+ long[] allSigs = new long[11];
+
+ allSigs[0] = InternalZipConstants.LOCSIG;
+ allSigs[1] = InternalZipConstants.EXTSIG;
+ allSigs[2] = InternalZipConstants.CENSIG;
+ allSigs[3] = InternalZipConstants.ENDSIG;
+ allSigs[4] = InternalZipConstants.DIGSIG;
+ allSigs[5] = InternalZipConstants.ARCEXTDATREC;
+ allSigs[6] = InternalZipConstants.SPLITSIG;
+ allSigs[7] = InternalZipConstants.ZIP64ENDCENDIRLOC;
+ allSigs[8] = InternalZipConstants.ZIP64ENDCENDIRREC;
+ allSigs[9] = InternalZipConstants.EXTRAFIELDZIP64LENGTH;
+ allSigs[10] = InternalZipConstants.AESSIG;
+
+ return allSigs;
+ }
+}
diff --git a/harmony/src/main/java/com/zip4j/zip/ZipEngine.java b/harmony/src/main/java/com/zip4j/zip/ZipEngine.java
new file mode 100644
index 0000000..19ee8e4
--- /dev/null
+++ b/harmony/src/main/java/com/zip4j/zip/ZipEngine.java
@@ -0,0 +1,466 @@
+/*
+* Copyright 2010 Srikanth Reddy Lingala
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.zip4j.zip;
+
+import com.zip4j.io.SplitOutputStream;
+import com.zip4j.io.ZipOutputStream;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.zip4j.exception.ZipException;
+import com.zip4j.model.EndCentralDirRecord;
+import com.zip4j.model.FileHeader;
+import com.zip4j.model.ZipModel;
+import com.zip4j.model.ZipParameters;
+import com.zip4j.progress.ProgressMonitor;
+import com.zip4j.util.ArchiveMaintainer;
+import com.zip4j.util.CRCUtil;
+import com.zip4j.util.InternalZipConstants;
+import com.zip4j.util.Zip4jConstants;
+import com.zip4j.util.Zip4jUtil;
+
+public class ZipEngine {
+
+ private ZipModel zipModel;
+
+ public ZipEngine(ZipModel zipModel) throws ZipException {
+
+ if (zipModel == null) {
+ throw new ZipException("zip model is null in ZipEngine constructor");
+ }
+
+ this.zipModel = zipModel;
+ }
+
+ public void addFiles(final ArrayList fileList, final ZipParameters parameters,
+ final ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+
+ if (fileList == null || parameters == null) {
+ throw new ZipException("one of the input parameters is null when adding files");
+ }
+
+ if(fileList.size() <= 0) {
+ throw new ZipException("no files to add");
+ }
+
+ progressMonitor.setTotalWork(calculateTotalWork(fileList, parameters));
+ progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_ADD);
+ progressMonitor.setState(ProgressMonitor.STATE_BUSY);
+ progressMonitor.setResult(ProgressMonitor.RESULT_WORKING);
+
+ if (runInThread) {
+
+ Thread thread = new Thread(InternalZipConstants.THREAD_NAME) {
+ public void run() {
+ try {
+ initAddFiles(fileList, parameters, progressMonitor);
+ } catch (ZipException e) {
+ }
+ }
+ };
+ thread.start();
+
+ } else {
+ initAddFiles(fileList, parameters, progressMonitor);
+ }
+ }
+
+ private void initAddFiles(ArrayList fileList, ZipParameters parameters,
+ ProgressMonitor progressMonitor) throws ZipException {
+
+ if (fileList == null || parameters == null) {
+ throw new ZipException("one of the input parameters is null when adding files");
+ }
+
+ if(fileList.size() <= 0) {
+ throw new ZipException("no files to add");
+ }
+
+ if (zipModel.getEndCentralDirRecord() == null) {
+ zipModel.setEndCentralDirRecord(createEndOfCentralDirectoryRecord());
+ }
+
+ ZipOutputStream outputStream = null;
+ InputStream inputStream = null;
+ try {
+ checkParameters(parameters);
+
+ removeFilesIfExists(fileList, parameters, progressMonitor);
+
+ boolean isZipFileAlreadExists = Zip4jUtil.checkFileExists(zipModel.getZipFile());
+
+ SplitOutputStream splitOutputStream = new SplitOutputStream(new File(zipModel.getZipFile()), zipModel.getSplitLength());
+ outputStream = new ZipOutputStream(splitOutputStream, this.zipModel);
+
+ if (isZipFileAlreadExists) {
+ if (zipModel.getEndCentralDirRecord() == null) {
+ throw new ZipException("invalid end of central directory record");
+ }
+ splitOutputStream.seek(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir());
+ }
+ byte[] readBuff = new byte[InternalZipConstants.BUFF_SIZE];
+ int readLen = -1;
+ for (int i = 0; i < fileList.size(); i++) {
+ ZipParameters fileParameters = (ZipParameters) parameters.clone();
+
+ progressMonitor.setFileName(((File)fileList.get(i)).getAbsolutePath());
+
+ if (!((File)fileList.get(i)).isDirectory()) {
+ if (fileParameters.isEncryptFiles() && fileParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_CALC_CRC);
+ fileParameters.setSourceFileCRC((int)CRCUtil.computeFileCRC(((File)fileList.get(i)).getAbsolutePath(), progressMonitor));
+ progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_ADD);
+ }
+
+ if (Zip4jUtil.getFileLengh((File)fileList.get(i)) == 0) {
+ fileParameters.setCompressionMethod(Zip4jConstants.COMP_STORE);
+ }
+ }
+
+ outputStream.putNextEntry((File)fileList.get(i), fileParameters);
+ if (((File)fileList.get(i)).isDirectory()) {
+ outputStream.closeEntry();
+ continue;
+ }
+
+ inputStream = new FileInputStream((File)fileList.get(i));
+
+ while ((readLen = inputStream.read(readBuff)) != -1) {
+ outputStream.write(readBuff, 0, readLen);
+ progressMonitor.updateWorkCompleted(readLen);
+ }
+
+ outputStream.closeEntry();
+
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+
+ outputStream.finish();
+ progressMonitor.endProgressMonitorSuccess();
+ } catch (ZipException e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw e;
+ } catch (Exception e) {
+ progressMonitor.endProgressMonitorError(e);
+ throw new ZipException(e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ }
+ }
+
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public void addStreamToZip(InputStream inputStream, ZipParameters parameters) throws ZipException {
+ if (inputStream == null || parameters == null) {
+ throw new ZipException("one of the input parameters is null, cannot add stream to zip");
+ }
+
+ ZipOutputStream outputStream = null;
+
+ try {
+ checkParameters(parameters);
+
+ boolean isZipFileAlreadExists = Zip4jUtil.checkFileExists(zipModel.getZipFile());
+
+ SplitOutputStream splitOutputStream = new SplitOutputStream(new File(zipModel.getZipFile()), zipModel.getSplitLength());
+ outputStream = new ZipOutputStream(splitOutputStream, this.zipModel);
+
+ if (isZipFileAlreadExists) {
+ if (zipModel.getEndCentralDirRecord() == null) {
+ throw new ZipException("invalid end of central directory record");
+ }
+ splitOutputStream.seek(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir());
+ }
+
+ byte[] readBuff = new byte[InternalZipConstants.BUFF_SIZE];
+ int readLen = -1;
+
+ outputStream.putNextEntry(null, parameters);
+
+ if (!parameters.getFileNameInZip().endsWith("/") &&
+ !parameters.getFileNameInZip().endsWith("\\")) {
+ while ((readLen = inputStream.read(readBuff)) != -1) {
+ outputStream.write(readBuff, 0, readLen);
+ }
+ }
+
+ outputStream.closeEntry();
+ outputStream.finish();
+
+ } catch (ZipException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ZipException(e);
+ } finally {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ }
+ }
+
+ public void addFolderToZip(File file, ZipParameters parameters,
+ ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
+ if (file == null || parameters == null) {
+ throw new ZipException("one of the input parameters is null, cannot add folder to zip");
+ }
+
+ if (!Zip4jUtil.checkFileExists(file.getAbsolutePath())) {
+ throw new ZipException("input folder does not exist");
+ }
+
+ if (!file.isDirectory()) {
+ throw new ZipException("input file is not a folder, user addFileToZip method to add files");
+ }
+
+ if (!Zip4jUtil.checkFileReadAccess(file.getAbsolutePath())) {
+ throw new ZipException("cannot read folder: " + file.getAbsolutePath());
+ }
+
+ String rootFolderPath = null;
+ if (parameters.isIncludeRootFolder()) {
+ if (file.getAbsolutePath() != null) {
+ rootFolderPath = file.getAbsoluteFile().getParentFile() != null ? file.getAbsoluteFile().getParentFile().getAbsolutePath() : "";
+ } else {
+ rootFolderPath = file.getParentFile() != null ? file.getParentFile().getAbsolutePath() : "";
+ }
+ } else {
+ rootFolderPath = file.getAbsolutePath();
+ }
+
+ parameters.setDefaultFolderPath(rootFolderPath);
+
+ ArrayList fileList = Zip4jUtil.getFilesInDirectoryRec(file, parameters.isReadHiddenFiles());
+
+ if (parameters.isIncludeRootFolder()) {
+ if (fileList == null) {
+ fileList = new ArrayList();
+ }
+ fileList.add(file);
+ }
+
+ addFiles(fileList, parameters, progressMonitor, runInThread);
+ }
+
+
+ private void checkParameters(ZipParameters parameters) throws ZipException {
+
+ if (parameters == null) {
+ throw new ZipException("cannot validate zip parameters");
+ }
+
+ if ((parameters.getCompressionMethod() != Zip4jConstants.COMP_STORE) &&
+ parameters.getCompressionMethod() != Zip4jConstants.COMP_DEFLATE) {
+ throw new ZipException("unsupported compression type");
+ }
+
+ if (parameters.getCompressionMethod() == Zip4jConstants.COMP_DEFLATE) {
+ if (parameters.getCompressionLevel() < 0 && parameters.getCompressionLevel() > 9) {
+ throw new ZipException("invalid compression level. compression level dor deflate should be in the range of 0-9");
+ }
+ }
+
+ if (parameters.isEncryptFiles()) {
+ if (parameters.getEncryptionMethod() != Zip4jConstants.ENC_METHOD_STANDARD &&
+ parameters.getEncryptionMethod() != Zip4jConstants.ENC_METHOD_AES) {
+ throw new ZipException("unsupported encryption method");
+ }
+
+ if (parameters.getPassword() == null || parameters.getPassword().length <= 0) {
+ throw new ZipException("input password is empty or null");
+ }
+ } else {
+ parameters.setAesKeyStrength(-1);
+ parameters.setEncryptionMethod(-1);
+ }
+
+ }
+
+ /**
+ * Before adding a file to a zip file, we check if a file already exists in the zip file
+ * with the same fileName (including path, if exists). If yes, then we remove this file
+ * before adding the file
+ *
+ * Note: Relative path has to be passed as the fileName
+ *
+ * @param zipModel
+ * @param fileName
+ * @throws ZipException
+ */
+ private void removeFilesIfExists(ArrayList fileList, ZipParameters parameters, ProgressMonitor progressMonitor) throws ZipException {
+
+ if (zipModel == null || zipModel.getCentralDirectory() == null ||
+ zipModel.getCentralDirectory().getFileHeaders() == null ||
+ zipModel.getCentralDirectory().getFileHeaders().size() <= 0) {
+ //For a new zip file, this condition satisfies, so do nothing
+ return;
+ }
+ RandomAccessFile outputStream = null;
+
+ try {
+ for (int i = 0; i < fileList.size(); i++) {
+ File file = (File) fileList.get(i);
+
+ String fileName = Zip4jUtil.getRelativeFileName(file.getAbsolutePath(),
+ parameters.getRootFolderInZip(), parameters.getDefaultFolderPath());
+
+ FileHeader fileHeader = Zip4jUtil.getFileHeader(zipModel, fileName);
+ if (fileHeader != null) {
+
+ if (outputStream != null) {
+ outputStream.close();
+ outputStream = null;
+ }
+
+ ArchiveMaintainer archiveMaintainer = new ArchiveMaintainer();
+ progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_REMOVE);
+ HashMap retMap = archiveMaintainer.initRemoveZipFile(zipModel,
+ fileHeader, progressMonitor);
+
+ if (progressMonitor.isCancelAllTasks()) {
+ progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
+ progressMonitor.setState(ProgressMonitor.STATE_READY);
+ return;
+ }
+
+ progressMonitor
+ .setCurrentOperation(ProgressMonitor.OPERATION_ADD);
+
+ if (outputStream == null) {
+ outputStream = prepareFileOutputStream();
+
+ if (retMap != null) {
+ if (retMap.get(InternalZipConstants.OFFSET_CENTRAL_DIR) != null) {
+ long offsetCentralDir = -1;
+ try {
+ offsetCentralDir = Long
+ .parseLong((String) retMap
+ .get(InternalZipConstants.OFFSET_CENTRAL_DIR));
+ } catch (NumberFormatException e) {
+ throw new ZipException(
+ "NumberFormatException while parsing offset central directory. " +
+ "Cannot update already existing file header");
+ } catch (Exception e) {
+ throw new ZipException(
+ "Error while parsing offset central directory. " +
+ "Cannot update already existing file header");
+ }
+
+ if (offsetCentralDir >= 0) {
+ outputStream.seek(offsetCentralDir);
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new ZipException(e);
+ } finally {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ }
+ }
+
+ private RandomAccessFile prepareFileOutputStream() throws ZipException {
+ String outPath = zipModel.getZipFile();
+ if (!Zip4jUtil.isStringNotNullAndNotEmpty(outPath)) {
+ throw new ZipException("invalid output path");
+ }
+
+ try {
+ File outFile = new File(outPath);
+ if (!outFile.getParentFile().exists()) {
+ outFile.getParentFile().mkdirs();
+ }
+ return new RandomAccessFile(outFile, InternalZipConstants.WRITE_MODE);
+ } catch (FileNotFoundException e) {
+ throw new ZipException(e);
+ }
+ }
+
+ private EndCentralDirRecord createEndOfCentralDirectoryRecord() {
+ EndCentralDirRecord endCentralDirRecord = new EndCentralDirRecord();
+ endCentralDirRecord.setSignature(InternalZipConstants.ENDSIG);
+ endCentralDirRecord.setNoOfThisDisk(0);
+ endCentralDirRecord.setTotNoOfEntriesInCentralDir(0);
+ endCentralDirRecord.setTotNoOfEntriesInCentralDirOnThisDisk(0);
+ endCentralDirRecord.setOffsetOfStartOfCentralDir(0);
+ return endCentralDirRecord;
+ }
+
+ private long calculateTotalWork(ArrayList fileList, ZipParameters parameters) throws ZipException {
+ if (fileList == null) {
+ throw new ZipException("file list is null, cannot calculate total work");
+ }
+
+ long totalWork = 0;
+
+ for (int i = 0; i < fileList.size(); i++) {
+ if(fileList.get(i) instanceof File) {
+ if (((File)fileList.get(i)).exists()) {
+ if (parameters.isEncryptFiles() &&
+ parameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
+ totalWork += (Zip4jUtil.getFileLengh((File)fileList.get(i)) * 2);
+ } else {
+ totalWork += Zip4jUtil.getFileLengh((File)fileList.get(i));
+ }
+
+ if (zipModel.getCentralDirectory() != null &&
+ zipModel.getCentralDirectory().getFileHeaders() != null &&
+ zipModel.getCentralDirectory().getFileHeaders().size() > 0) {
+ String relativeFileName = Zip4jUtil.getRelativeFileName(
+ ((File)fileList.get(i)).getAbsolutePath(), parameters.getRootFolderInZip(), parameters.getDefaultFolderPath());
+ FileHeader fileHeader = Zip4jUtil.getFileHeader(zipModel, relativeFileName);
+ if (fileHeader != null) {
+ totalWork += (Zip4jUtil.getFileLengh(new File(zipModel.getZipFile())) - fileHeader.getCompressedSize());
+ }
+ }
+ }
+ }
+ }
+
+ return totalWork;
+ }
+}
diff --git a/harmony/src/main/res/values/strings.xml b/harmony/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a9a6fcf
--- /dev/null
+++ b/harmony/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ harmony
+
diff --git a/harmony/src/test/java/com/jianrmod/plugins/harmony/ExampleUnitTest.java b/harmony/src/test/java/com/jianrmod/plugins/harmony/ExampleUnitTest.java
new file mode 100644
index 0000000..dfa51dc
--- /dev/null
+++ b/harmony/src/test/java/com/jianrmod/plugins/harmony/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.jianrmod.plugins.harmony;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/mylibrary/build.gradle b/mylibrary/build.gradle
index dbb5ccd..541c8a5 100644
--- a/mylibrary/build.gradle
+++ b/mylibrary/build.gradle
@@ -4,7 +4,6 @@ android {
compileSdkVersion 28
-
defaultConfig {
minSdkVersion 26
targetSdkVersion 28
@@ -25,13 +24,12 @@ android {
}
dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
-
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
-
//载入界é¢
implementation 'com.kaopiz:kprogresshud:1.1.0'
+ implementation files('libs/GDTUnionSDK.4.28.902.min.jar')
}
diff --git a/mylibrary/libs/GDTUnionSDK.4.28.902.min.jar b/mylibrary/libs/GDTUnionSDK.4.28.902.min.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f45dd2de904a8b84d150c1c16912eed22d6774eb
GIT binary patch
literal 751203
zcma%jV|bnGwsz3iwr$(CZQGeK8{4*<#%!F%wrwYE)TCjPZ?3JgSNGcIJ3mG;^JiY~
zxW_&2f#-RnEC>Dp76b$Y8YDtDMzJvv>=F<77Y@+Sfu%
z$$|;^!GX!yhDl9RO`Orp-UI;fkDpKkV?hsphjo>@0mD03X2$Du9#dSE4`u<)3+n{K
zfXV}us0F$G`F
z#$(Z#T^$SVGuPVN9?PVbZL@&Jy+$bgrg8N_v9;0#e+bMf(7gn(X4Ro|!X&wtN>gp4
z#FH{nwm6+(am$l-V9t+h&9EptMkN987SK7Zz@TP%>Vb3148XW2%2nVi+P{>ftAePM
z2hYDmYw$#fx9)$1>lCXH6uID#AjkZoi9)LKy{Wm;i
zM8swOj;BL>x8eXZdRT&uG_~^QMC5(YM6ehrNy-@dG}0s(OS8@)4b$=lnI$c=T~IIT
zMQ()8(wj$}LMNx2byo>WAP8+_KK{%(6}eFtrQ0cx&1WGmwV!j?0_)xuoVX#43|e+3kh=;8n&zvkd<@dsCA@OGv(76TiP
z4Y!=tky~=FYfwLG7fx02S%JxfET_k>CPIQc+n~FsKmAUhXW>2ut%QN_4l)Z)VB?2H
z%{H>i-fmjQtlt(LG!bw=9S8Dwqkh>%D}u{kmp$80h-%_~ZTO7A$zS5Cl{M6?p)^3w
zMNu%tttzVHO48uLGDocffE}#dy3nVo`Npv&z>bQ8U&oI}0oVKrO4VP9i`KvnH?h0h
zdZB)ECm4fsM>KbQAU)w`pRLVyIyEQhns&dn(~*b5!`O|cU7`e-A%omi+xp~Ka7@?$
z2)Kd0VEPUgLa=JP7BQ9HS-+Rc^k)n^tpslyfSmgVH2lBHVMUF9kW2+svAn?k?CKf&?k7v2ivXbgrCdq>%TvC4rxig+NJukC4-ZI84@gZ(NEL*F
z>cK|wU%7)_ZohVuizmC>?53Cty<7uo15AnnDg_ghs*DmVuJ%s%$3}@iDt|9XP0QBr
zAMMRgDab_CO&u5>?H%nM1ObbskA=5~pCqRIE&G*3L$Nb}k5&t`zl?;c=3hJ?p$8*0{;y#9Uxmz4SW1$RMR$v+E78G|#fyoP
zn~VF&gPUNIknypD`@6BPxBQZjLH}~l|Gl(&@eoxKvL*L*5M2t3xCHz2@LX72QczM*
z6zB(SZET!2Z0(Qml8}znQ`5chpLnpfMPz?-XofcOlP%DhfdA%@;$JzW2t@mDK6yw&
zVj?I_F-t-w9lq-S>KFZ`?oUjn)kCbdGdGlyBmytC$^=hOl9;BQ{<$C}Go$wEpNOXe
z5#P`32Le9WJJ|~z3J?DW=J@dR!kFN{J)?JMd}|Z%`EG$m1GN7rVIt<@CT=F=nl|PZ
zPRjNs-WINZ7hw0eDa1ZybX_dxkm
z+lGew>5nI~c#Mv3y0$x7t;0@HyT;rkPHUSnn?~CzOG({4##EvehP7Bi_lia}
zbZAu^N0(?+ejFq-ph*TY9uNf}#~9?sp*V0!q^*|DUWVy0&V&oqo^0l<>9&ZTmiw8c
zHN-FZ6pZ&lhC=c$9zDf}BXK(fa#Ui3e!6g2)DcA%|1I&W%xNxKfgYa%?Ju)IL-e2G
z>F`fqmCv0_M7l)RCi~|rBgIMQ5Kuciy36LbV=!-QF$sIcqV!Pcns|1`{8GHXULt|`
zv)iXSsKZEk33iE&*0f$NR|&SxR5)B=(ps$6Mo+b3kpR-
zVG5t1=|eQn&bymZreK*UiTE!z
zN+1;claId!Q%!#zHoN_*pG;l!dk;>YeC4-G#6@X?{f($I82=klM|-t$b%V_X@yNi@
zz}djbkXf0T8<<(0S?igZL2MWyH4CAC=k0C~Ez3L56CGgb!v+5S58nPOcp7mEz)k@<
ztiZ;4T&YN%Sn*?OZI|(y#=0Y|L@^i*oCjq-do5}XB_)cMSsy4jdOxB?5Y~@wu6*ww
z_rGpnb_o}dOOQ|Cx#s9mbeAP%wrS73&c}02kf&6`iFJK9C1fmVzwQ9`71i~nZDsTb
z>rj`p=hbHdM+yXL-;sJ~ze=yb7nKe#A8%FU+G`HZS1{-VCYdy%)$Eqd>b+ezmQ1q7
z;x#tBcJ?}7ypFr^Zri6zo>bOyep1hZ<98QINL^V(XB@**OK;Fjs~@)xudoxq_@tNq
zsc0zYizE^bcKq%RwKEJf$Oucgzzp-Lcho_C3V0pz&o1sqv>vF}-_kvsIWyH72-hEy
z^p~)SiTtBs_@4^!?}EgMpfp4MUnB`Fr@o0yS(})yfnG(3iiOEwM~H@wqy2-ms$zg?
zv>EKbN4F6eUFLca9r)j#@6-j$DT(aaoG}t_-!2#hRCVZOiBfLNx9b&2f4@QSdZvF@3V~0tKQ!kOcvS
z*{ool9GAt6c#oK!Q
zYv2W>=XMh)L-hm)hL2=QFwU%dcY~*yQul7(*N1@IlAGXKYMIZGW8!p-`$eGc*%th)
zg;?XM^ckUhoHnwoiuP9LxXvh2yoE)_bhRrYgQ%ElQ(z)$x$|qBettUj$_J#XtAMH0
z5^SFw{^Grygun@$X&W<3K*eg^Y{vdhL7tYqR<;6-e!3mS3S_40WAms=YpIv1w~C^Z
zG|L>T-L6DYc*J%PU^T?QcV27L5W1)&av5~NYB0qGvW7ek3IU;UdVe>kK^kkg+<
zg1kfdbri@A7$E)xf0b5$h{Qi-Q6s*=v7Z?^{M}Ae)mp^f-af7Wu!u~JmY0|WTliae
zu5$4{c}rTfpl*bqFi~ZHOQ^Wh)0@%5nOjfbju40;i)_-TQ?z3H7OKIud9&=A_k7jP!}
zV~Wv86^f>S&yUtHQI;C@l<)`}U2S>fcVZ7t$dZgbXU_}u+XlFb(4kO|XG5W&q`fyV
zGP}qwyKFu|2?0p@zctnH#z!DEK-bTJ_Lp@*@n14&F*yYrEQ}7Z3z*7%3oY~sx95mS
zmn8mzQi>`1QUQs>qCnu=hAAM7gi*l|yw7;qP89Ltw?zb!UXSAg?;j4@=k_`j#OxBR
zz4@W8S=Osi_B$u<`_j^hqB!o2G8S#zru)XX2wK^&T3DIP{6@GL0HZen<7;JG>pe`9
z(DoO>x38N-X5RB3=*9-u_|U$op2y9yj>A&O#TXZYS3)t%_ajp(U!in-X^=Dl{VmY?
z2?G+7Ko60Cf&S}i@ISMIgJP#5aCXRrKsymZ*2rE=lK&zhODl&mnhYVGzG>tK*Ump3
z58&Qkp?(ArgxzB+j!b3nuw-Ye`t|no3dTIbEskv}3|)N4NF`sau49lP2{1bx_3pgv
zW)U#sUq13(YHy-u7$>-Tpx;a;pks*B9=RdqfTGeoXXN5h&4?24w-Y*<>W
zAb(_ZfdY+njrJjw#D{_8
z&Shy2I)lKF>^jNl&vYj!cK1&MqVPu_@RxGbSoQR#5kJaetvMZ6r=h62YhbIHg}GhmDho
zE&MSmEGZ}?C@ct6x`~ZBB=k4!gjt&r!UKl$;=kqY-&?tVv~Ma3a=@7StJ%#rYm{hq
zdHo1EY>wnogB>!2g#bq&?5g>Wsybh~Wgqs+f%FCvn97|FyR;gX$Hn$?ED-+d(a#sk
zIf4xjiWAYn4ca$fh}?}okUgbNwUjkxX!Vpl2E0FvEBg@8pbtyIofRWN`n(Xt&U#${
zv$DNBD@Gebx|;6omsJsEl7&}C`&PH(z|~O)dq>N;MGfl{G?O$-Xqh#C(LxJ7%^k&N
zqp6Fct2Cy5WI`)f26MuvNuq!0)&9(}qdAc#A5%cWrTIw+p^Qb=N~PUcC`S)mLw+@@
zN%;h>OhLxl8H0k-BKpOXFQ&KPc~Eet1cf2N3O2Z+TAkRCMuEm1W7Z7DI;08A8k$lV=w1z7YCaFJZl^n}=1q#f#&
z;+V#Qm-DU~u3wKuOTGdX5dlIE1_0yDB<#{6u$XrTjjS=cQRqcvo_Nv6wzD40!lMHCNR!#Qor
zcy0CT4RWp*jA0Ns5=h-ZmPmREuMn_X%YyIrTdMu};-NLqyAu%b1Pd~==K{8jb}b#E
z&TI5}k5bZo65kwFF)PK})TVbHV$%Ln70+Zw%tvFE5+hH(=sdr0K}{UF#b3QI7A4+o
zpp;13GLW2G@!<3W
zj(dr)zlgce{w<9snh$SgqKsFHri^~5O;$MkBRYh-0nNyVamZCnF&I{QyO*32?BqdC
z8^~`d7u1(w?p+0m%WhPf>~iO%&C?_N8X7tR%8oO_?bt0%Y;b3{@Ly`yY=}I*2$dg%
zudQutYskeUYaSa-C2EK5OvX9$u8Xx3deZ72r>GI{@S~WjJ6bQ0XX&pdU!A!y1-!D{
z<14Jupf_J|c;aohN2E*;sQ)ks-_C>@3XzRd_th#Uu_idG59~_lb|I7H_&)NkITa%m
z>&MezFF+uM<{M(BefO<%x2nlnRZ4ShS3G1YNuex6tBqEFd(K$I<2sTT*Pc)Ow5&CJ
z!_*yXV+$!qh$eyZE9x-VIOY{mu~`}WWtk8JKx;LfIxHGMI6|g0v`D~#u>R*v6tbjtzu4>mv*of5*ri8
zgtnGaJM&rb%e+y6M?A9iK=GoOcD9tq@JX=
z--r$ekxfezRT0aCKpxSC`7{8qFSKD_d3aa2#A35{2I6|1d2~uuiR9x_Mzx=nYt-W
zs&Q}QUhlGMgG!i{85-UPlw~}*ZZh?O=U)~^3TgPCOX=$LezoNhqD>aOtMnj8~N_SFQ3TRV3_+*RlIAz3>jb4Ch(kg&EJ|;e#JfK{u$RC%Yo}X`I
zeS*>9;!{Gngx0se>PPrP`s7{s+f57PJ2ZIyc2Y|DiaA#nx3-o0)3F%6uO|8|Si9D#
z5*Dovi81{u-5l5xb*7j&6d*Hj$f4RoA|r1VFo!oN7v8EoeH{>PFno*!yAM7rf3nti
z+60@3@cP@M$W_dB0R&_m>aR%R?q*}pq~_*o<7lM;{P=g0#A)oPp=qJN
zIik7_Gzx=7JFvJfwTK3bH>~T@O9W7}7b!;$M=m=iQ_z!lvd|BGeJHRddg+HldIVF<
zO5);OcrUK=zo=T^6tMaMp2$o#>G!aGl?!;_6FNRlcn|PJ*oBk@B?rq)%S_Qul$I@}
zAe5t*ntadm>{BW(N?x^w`uYiZIWstI@F47hONaivkaA~
zdV_3KDZK}WA=g$zIx}LisnHN^f&mdu8WIKr>SXlZEL>kF)KL6hI3myIF+-0%0A_Wr
zF4B7208wtQ`((26UKEo6!FW%AWQ&XJvk}$biPY{bHiKi4A+X`enm>fVexRgmb|EL{
zmejlC>Ba@ev(j^BrIaG3^T57Wu5)rk8J^NErFid}b?CabJ8=J`p(5
zEN>;=%jI|if4+PeQ?24EzcJ65E)$o|uxb~c?sB2&u|ZtOp1b^E9SdKeleZ)znO=I=
zN0sRWLFz!i*=1{HrvK#NlD0gvVT`{%`LW6p~ld@Qh_NAo{nmEKN^|4
z;7d%y$&8s~Ea%WF?-oj@P?G%W6aNG(7Yk8(DynYUS
zdM;6g`@N%j#L(!}%DiSmi%j9gX~QKTR^BB*++$_zY)$g$ygKI=h0|?RQ7?Xv5|jVX
zl8sJsnMYDXt@=WcwDlVBsNxf-K>7WsFbyt&o&c5w5J#glw=^ldw$6G5?*reT6q6#=7Vv?)??{)dOYt482%mb{%zX
zC@iB^jE>Nx(K3E<(jW=ES<1uN1!|qi`o_V#H4@`-xO?^oBth`LCNo$9-UC+G9TL4-
zuBeLuI(dtPDN+!-|Jkoc!q=`dO3KXbp%Jz@f?S0Wq(a2R7pjV>@f~o!HdLzTNy=35
zjL5H~7#nAg0sKB6XZ2nM%ebOpHl^?cmP;~2_G{rv_fYb8ac%(89VeCNG}KPBKaoO1
zs|zz0WgjU7zC&w_W}PC2a|VWi@!KDkuu|gvR9=e5>6nsIdVr4y!&iIjyS@b!evh6g-!Iep01MEd
zQ98f2wDT?3m%|d?P}{lE9#JWz*2JZj{$4kRXBwLyb3Op1JoPs@HuVL%?Ih>B`?|K?
zZp8Ok&C9`~MPYXS!3!nB2A!uA_Q2*XP35A$IM&O9&of^j@hT;t7Ub8Gjl;b3>MX5kS;ozGv
zGm^xdVCR}M)9mpdQ_$~2Kt_jZF;nbukdt>!?{5#Wc`95h<+TD-$;nUX$%d(FzJ9o2t%%7IbDVbr#f#Qa+dn~MqnK41R1hS?~f0|;p
zSTj<2L=juyc|>HSywA(@c>fA$#doQWtJ2=S-g3l!6k|ma*K51GWfnoxj`seBi08Bb7P+
zG-k=6oqUMGYU#CJrjvqAGRWheJg$AV9Y7)=KCaJmwl^m}DP4BQAJKw)>Ef{aK}J^k
zg@;|hX6gHEHF&v><>}gOwZ0}P(&EgNGL%{e4oNlyBBN@_T5yGBN^`My%lIlg=@Kuu
zG+qx(2N~X!H7A$TJgz}r$=l}D2J})TrGn;2_}cxCz1^=uzV^?t`RqiQmGcfYzn;`(
z_+N|o>$jfE4(sm&1<}uSB+Ij{&PY{A7IiXg24f)vgo+yhs0tS`mLKC0cu
zS1NeEXVPfWQ}x`!{3IsU*r0rZ@h!Iv|js5d?Ov2Fh>EG_?FwIKf;jV2o-ax
z*ARIGZt;4(hZq~`$W
zHG3sb+i@kqc}1VXcsxLoR=kgv?%kof`~gWjtjXPDf*
zj_%E{blYUg}lV*?&e`G)G%L
zM0>(YNqdAJ5qBI!)wYVej=`I7nYmdXEikUF>`1>~LF!RwDrJxJ`oTfe3B~0UM>(Q&7`p-?B
zks|9Jv6mkU($KWCpx+>!*6j1N!}%p!?6!Wvm6exlM=*yoV7`1NL@LHEuTA^@fknkR
zo6#}tiKgwt1*?kG`Uf{%f}WVE>i12b6j@C&ntoO}XAM~fopP0|o#Epw`nq;GJMqby
zK(X*8DB?4ZcX=wSl#6{>rj7k*gX4o}v6c}G5syI(70mtU4U(A9V0|U(am9Y0HHp(y
zE|d;)t|D?S6wXmrlaj71=afzSca)6@BkH%`#++6bL9rv?$S?{H0z t?b_&kH&O
z>4`~;gKMRHqdxGANS9Jg8+w|);5*p_00Mr!y@B__Zhxw9hHHV86Vn-hL#X5EnyW7+
zi{pd6q|MZ-FEmye!M4OecZ)Ss>=PisnZ)*rg@TlAt1}+!rQ=ssRu}fvX|Ecuq^=4s
z?k(1-{4!AM?`Gb~8CSanC_(G_{?oplGLcj61q`CegIK9_D=En8YAA`w%Z~QVPu%YV
zK+5=+fc>lwK!9q(cqwG8ishR=t-L9i}<
zli_fvOxQrMkT*n)sJRW%#F4vW@y{P2`g5IMS|)WE1`{HAy6G>@EJS(5vgI
zZD=CDAtw8qlirhSM#LqJ>ZF;mqHuJkWp9u-OQuR
zgUl1J$%erjju9^W9aq1R8-a+vhH6ns(MUN_zf#gr7Yq|}4d>EOX*d_d$XH~eN@Zus
z$O&3fZmK~DjLKCT?i$i)7z~z%n7wLbERM|qDp9{m6UbJM!_a34N9#w_6kJSaIWa;l
z4*tQ^aH&V_0j;*;BI&~aDwxFK0CYYbo&(^WrJrh6UEtOMD6t#@HaT+9u-m5wk`$yY(
zmeAJz@Eh-Go$WAdr&OGuC>43-&h(O#4p7w1)s(cOZjWnVNI*uJiFUFaxJwU%eqnin
z<-eyARZTfkWr%HIZj4e1^z=fTGskmT9!^~JgDFc~AD+|qL++t%8gqlY!m1q=BH>I*
zmpsIb|JC)QwNMG!z;PP!eym4?P8$89JB;(r4KJd@vVvz}>(g7IIQuEJF^J5hchxKx
zwLnqKmyg-4teaHr&~LcoJ;wMbh;K;4qI`Gh<6p;%oTYmaC5chQ<5WU1Z`ccn^`ShR
zO%sSg*0bBTZFbq8Pmyv<5r+AVzxpjp4~w|^RX14qE(1~IEy$5{K4=>wLKPy(ux=>jyJ%>
zOQy2QQBdp}=6y$D8s<=$qs`#QJSg4lJnq^1{$~;BXT1x51}1+juv!rRwFroqOSrl^
z{o^)>L&6mBdKr4yjKchIOUbVW&3Zad8)VBpRTOc3babrXmu3W#G6idVe_AY9%)T(B
zU9l)D)0h~K@b(kKuY6F`4>KoNAJy=mS0Co8M0PhC)gnSF`x$Xz9oR8=tzkBenbF-s
z`li@@;_v9kQT?5w+2y8XABj&P1{ADC?)j%w0VLP8w=(p|yzVVe(t}*6_;Ekl4e8yg
z*Cy{%WUQWDT@BE^Nt9*m$A8XLHxN1zp^04V+&i+zjX;h}9x_)9OmJI_h^r@r(*J
zf2k0y2_^d6)khe!1pDwbRmRDH_#)S~H-@_H(-5;BKEt<}fgvdD5BJkhF7~(Oc&DDx
zPA?EISRiiyd93!g3Tr}c@{f(2Qa5bBF??6^l5Nd9X$Rlw9ZY<5h+^>!QJCZjZJ@*NonyAO2rK?
z$G|yp^SfmCCU&VgeU*4%hI2JeUPA+L*~}=k;T8MUm;j(J5bI}1=$FinS)!j^6?PV}
z-R}ba%$OA00O&{Hc<2c{7WmJjw7+})k0q1lKL*24`$)MM6z@oF2merU1L9CAkqvTk
zY+`9?kP!binPqbqnSM*~!LLL=9f(?%*RwpHi}Dn$szV$gB(2t+e$56J8R_A_M<{-8
zhat5!&dz(C?mo%&oo+wLe|bFE!RrBY-fe^tK|@DVr7Tq#%`D7H?PEvkmwG#sQ3
zN8{9N)@hiG+j6)Bn=?fgE5s0z5z>*3{*<#M9HyltBB-D*66cG&a!ffk|I}_9&ONp<
zv$Z5yKD!x}AAC6WhY#e)me=$l+#w5KuH1dX7R(IBh$xUOpg*|BcG1BNdME5x>AVbk
zs}do)AZ22fElIeWf(%8ZfujTAiE(`gPDRsm46hDP
z`U1KD6XN(2A?2E*0oS9Kn{51G$j5=ZlaZGAsf$
zM_iuuoW|r>8fn-$Xc5N<_a{S{=`*J`b0y}P^8`B|g~o0lnRj;l=BM_AVPtIZ
zTBbM#WEcdfBsAKQrOWSwvMkwLV~?oB-4@utH7h!{z+)_@gpD(^j#Da2#m$c~|D>gn
z5Rx4_lo_!=L@-p4vqO`SNE@+G;K@Uxi87Iyu_0&kJu!2Y)S9oLhw3Yk9m|$VYt+f=
z_=2b5JV3ed$hLbWMBx18&R3M}7fIma9M)ZjK_5#-Rd?X((uoUQpNJ>vD%WQ@2bGh@
zTsBwB{mc=LrCJ29V^If(JdNUkvmE=2^o?HxS;{$`7xvbl9N71!bFn8NQ1i}YjO>&>
zCtInX#8)D76Dprf*4WN3r60jv=dl#WHENQk&om3rC*@LbI<1IZ`T5bUu?9vU*|y9o
z+Kv_ABNZzqSor48Pu(Q>4;*ZbJ^DE+5vm+{76f_q({96uA(Q)>rfUNf?sz%|0B?d?
zvl22)LVCK)4W(39BJr0n-|VyvSM?iQ)E-4iw9xv9>OB#qe81)R#imtBl$)m
zU9D+J=rv_j4ApGiZIPM46E*3)$TnUtUZG7MsP*04%dLzrO-p68yFjlb
z;)lC3!?2Rmvj}l;FWC6vFCXzBS&m20^bixiP^3?Prix~BPFcPpVM*r#|ESiKTsf$v
z6yiSqB1>K1+~Vd7XVcAR%&`fjC?RX988eq=HoxFWWlzjI)pz^QPS?uh*XrtGa`d{?
zlkT+nya(xUOZwxdmQmfSUs4BM##f!En+-iJCKW8-Q`y?02vHn=Hv`p)en+Wde%dHD
zSVWm~4Kxd(0VOZ{qFGwAjH}8!Z4>@kYrWtpOB$o$pbKl(y8xsEaqqgC8(t3sG7a!<
z82Zm8%kVT#VrvBFuKaA%C6`30E_d0ZdyADzKB)^(&`=pS;=PtaZO|8K!B;ikaE+FD
zWq2Wr4d>T=aWIaw?0bw?GCniwH>_pU2c8!Cqd+BWi#rVVGwnGqkm}NZl87`{YBqtgK2EC;R+_8r?iTGupX=DTbH|78k$rqgA~(d!OBw__tS?RlIL7aY
z`i{~$)b`<=Ko`3)buHtDyHz;6lDOD{0YZZwf?<+sY6qIlw>VMh_&fcRDb4pUELx&7akW#-U_+D?ZRSf#20H
z!UQ#duYX`18o~s)#y~VY*+oG7I^D;`{Rj#rbhs;p|C$-%$@2&UC3LiFh5wox0z3gX
z^omY4<*0^$q8M4MM;xc&sSJN9OiTt(#aACV*+r&^py3gfiNZxK5=OY#!;$@382-|y
zFt%VTUHq23x`0JfQCO;rFm_ELo3ADYn_5YN5xrQCqM!P$yi^q-XHP~p|5K?vf~2$R
zEjz`Gs%lw~2e*g}0mTV*MR{pbGA6=ZpAAA6ER*#v2e%ANe_ekJ?jx?jPs%U8XxZ%7
z3a~vAW35FZ;7%ec$bN<5zG2Jm^ZqMUPCl
zZ#ClS&iOVL;JnlM76I{ahIYhpvHuCKm)nA>MaLk%>z8JUe=E3HF4oBFI(}Ov@Rc2o
zM%~*Z+$p3gR=BML_trrL6)m#wZ|Ju&6^c!Szs}1OrKB5w@)AJNC!FY59Qc+q$J47F
zA1G-9HZMNqvT5jhup($5pVDQ!@&fP$vEv#~1#`BJ70HdI$6YplZfq)9FaZQm?Epi(
zHAqb^MtHQpB7VbsSbktBzGmSx$goy>Z#A1ilX5%d7a_Wj12E_LcrZK^P{#g6atY(0r}+r}mmy%U3-)oz>I7
zTJUYFS=PU6mT#iM62Tl3FQvu%_(;7lLHyv%T*VdRM!L#68T=tIUqO4O-~Mi%(esq5
zu6aU7i>N1kFZEh(R|>!?t!$j@V+E_~?^v~{#j89R?;YmSf5f5OAnY76I31jyW^KQj
zUo)i{Ek+X~j&9K!%{Rq$UsH>NZa{8hbN;A_8ER^jm71Jv%lds>dTKqXoxvf4hK`0V
zt#|&6!4sPcH^dU|6EmBod6}b_=ZAFpqOJ7oW|NiWsVtld5u%Cm`t^pX%(M==dy;b+
z8UhBkRtg8vjhtXE;d;eAWYOX>6QK`|dB~r;k^#;_g_Z_%xS3W*8vTc2?yWl
zlq{ktn2_l5&e(N+csRvDi=?HTIGYSKAJhfdM+9_zcmT9Bc(hI=s-C*?-L_tCq@bds
zL}tBBiHK!ZZ#I+AZ8b%4jG{AtSH`2&{b#DgyvC*4oy$oW{0`JTtO`mz+E
z573_qP-*FN>CL~aKZHEapfObzaWdD6FYD!X!HAIkDekXGzvD*>;NDV^%Lq
zatDZT1->}F5s4nJ#sI&^^U=K}%-wXyr%fv!2A)%+C{1p6T`J!T2s$Za4`I|(Ek_pZ4j$%eKx!{DWeGAy4{3T@x@m903gVYf=8a9Y1D
z2ScT()|*)g6jXmjax;?>
z3i0yl)1+&SD?Sg7t6mv*y0w8b+$e|;E6~$mc0!g`YBoG~)tx|h+;bsBhb-;YSQI^6
zf7CuVu{=d!vZBnGUXB?$l8?WFxH}e_N0~frk>)0eS*p^kF?3%+Sgjr=jI_8nePL-R
zW<2#i`!IGG=BaYRW!myQJh_bQC0R|4X-8W&O!*!bomj^G%8yB2QTP$lh!a^!-T0`%
z^sSg}+atlNdBF4YvTuvXH{K%xK}Lh`$a1uwZ^F<@+B6MW*Aw2O{mPisBCPPrN`N8NPqXg5R~Y>eUD#(}N_no6Mwp3V$na-sMb&0nRyUSb^;a{Fex@7R
z7yDWp&~Oy%)*#dM8r*8Z%&bn4Mx7L&7PI7Ts(_)k2dBpGJEdnxOXzTifCPnCjxc4e
z+(?{=g6LBSEAGtPfs
znd1U|B9b9#8cPPDrU+Z|rAnIy4C-9HReNC=R|IbQJfVt_=x2szQvr>tQ&!z+>4R!}
zT`P77if?6EO7|mRXh(mhrT0i)T24iEEL{ouD#c%xN{@_SUx}jOs7$8z5(CQ4W|9D_
zE6-hZ>!UUxcMC65om#|0f|=pF@zBSfw&Z2M&(dA`QCUL?}M>>mzIE`5;)`tYv0cMe7o$8bxLbf&Xl-ytg~Ym
zLe0WvChnOm+ID&0y3IAdkHE=b(ST{7P!K(2cZR@z%m^jEJtmALvQ5a17gI|VhXq=r
zc~iU%VNb)%h@l)Te(FETGf|f419OpiRr|NhNE8KrlHiE9w9m;7lb1YfR{az
zp`nvv>6cZl4Ur}Z<(foKt;Sy~rUbz+b|UvA;z=`aXzF9wACR<^u_3IOvsXmt{6OVO
zMIED+G7j2_JjbDjn>8zxpu^dXWX#6>z>i_rwB=z^NPRHKWU-15$jD?vrw+(elfR^p
zsxAoie8|=ezL|agb{IjjRE^9|*`%tRGf8XmX$+rXk~NFRq=m*HKEo-z?J=y1%{sFn
z!8TOygCO$8WAAnvxSIHf7Hm}#HuOx8%7k4HL|`K<+At?#Yr&FqRG4VSTNp@SNqx71
zD^hkvaz-fK&nwUsW;_&ojNxC8w6O~35la~;!SqdbB!b`1tCRqB%BDMU0r6p!f6sll
zzheWuBeVI&bvrlS^_R3CQhY3^V91t2koRM1tRGsYu`h^FbP+{K=&eCL(l=_WD2!ft
z4xEq=)vQpS;CsXt@CB*pE+3n0QY}_Q*us6~*@NrHKC$eIh*;rRfGlRqT9_SSUk)$N
zR|GPsw|Ly4#5Ho#2oFi=Xkhuf?%-U6#uL!WqZ15sM-<;{cQwtMV3lGCN4a5OxlKbEeO--QIHn
zzyS#$3+)yEaai2jW-5gnc7&^gNJ7J?snnp2Vu){_##$f$Q`)dBbw!+={u|_>PMw8<
zT1{CEgz~c)Z$;2;j;eh6=bTe8stq-$Le2p?jm!UX$6
z_t6KEce?Qvl6SoQtkL)Q$MUvgl0%!(smC%w57Kz4c)JqU>lfw#gU2BfE#)=F!&9bn
zmP4x~B$7a+C+6G011`*8CAq#iLsQ)$n;)0~ABVUW27bAvjQU>p-TcY6GqJ?Ufy=czuQvCnoNs)8fxztMj8^-tL-w$(
zt`?T(vb@UjetxOm=PaIV#LEr43U%eYzIGf!SayZ%0mLSsu8wSqWwb!#D~LB$;y~Vh6F#lc>i|(Rp&6hL
z+;TC20sb;bLmptBFw$bEHpx7yt3ttKo4j4gLw2-!oFg_IbtZm~3GM~{l7TrwtrJQucGQ${*FwJ;be`{rO8hini}DM
zZU{|@D$0Ys+-lnSZgbk6y!
z``2@u4pGK{$#i5)%fU@dJ>FQ0$p{C7CS{0i%_1iis{msH?74`Y_E9I*&?=62nDM2u
zoQ1uvQ%=XH^_Y2^iL`Z|n(L{IgHyP^2!Ipm(^qqueO8p)UBN^`0CKqJ+s2QZIsczL
z*|j_^(_*92(KjGW^>OTt$S6!_`9^zFXemdSZ0Ol2X`XVBnu0r~_mB@UyEU`T+$qwL
z;op1y9PA!?&}Twmu(5!_rutuk{kIbfF(-42e@6Kq|3p9rcLBNZs2sV46cRQPK9;z&
zl386;-ITg1M|*dkyT;6miIN7*lgkMkhZ=k#IzW93h8~60cXJGD@m9ZkGptn`!+(9^skOw~%+p+W_s>)3qUUv*G_qsHomeeK
zc!qq1mrQvJHx<@s`W&=Ahxcd)%B{`dmbWaZzX-i597!>cp_+pTRYB6TF&j)5-BQ&uVIZ8oh
zJ-8fsLs@q|lydIsNalJM>iWl{Fw(xgoZR(3VE|u`38bZ!QDD9iNbUhMTs>EiXAn}5
zQV>&+6HH^MKMWK_wMlm@>^S^die*8OGYjJI7)3}aqBnXNikKo?1fiG&TnM*INa@w{
z!{HaqtICe6JNkjWbPHX0c`ND*kzI-GtKkMLwWTi(EqU>_YkoQ!HaXL2j@2EN1Y%2%
z2u_|ES>Z#S%Qa@1wi6A$*~1PwCV-^Z1wVG^@^Sl5Ua$~q{ieg(Sx7yZT~AI^H|Gss
zqF#1szv9KB|8U$#K+P?Z)&ql0YzTq;98NFR8v8xfYP3}~pQ5guU+QbdE&r3_I2=05
z4aOj?#W?`#wp@u?KonmWtU=rp1(lQd{kQwbkkt%jHoze00lCipe+q=6g`1X>tKGjH
z*M%kh{oToTlrPbdUJ_k7ZOT}j!!;}-_|4Jk6X^Su73$92#1D!K_AY|5KpmM8=gh&8+f3PyLiJh_+g1`p0fg~m{Q%8od8&%Sk(J~_ysPOUyN7&YmPbutr(}twISlyi+P>RR
zNnbB8bi}~x6|DcK&?&iDTe$vv;F3+0&{(iX-nTxUZ94B7E2;~t%w_7wI>n8(V|?DI
z&DeG^gI-2sg2idA&k0~M%EG<}e=Les%F0RO+K$=j=_&tyU;BN=$S4rRD|(7Qaf9s?
z=Nn!ZD>pMYyO%>D!Zx-a+oR%W9fot=#}^2G>U~;F*|u;BLFcJg%y+FBJz25
z9L{H#bPAk-`2rLy;7t~<)wIg8fqbDnqtp_lu|%YZ5E3yCYZxP`U{81ApXFxborV)B
zTW8_wWOIkATosGyjsVr#*olCR?+UUPQXy(5-+;4`Z7%BYdoIrjT)gN0P2hW(%~
zl+ci(km3&C?RObh_I|X)BH?!X_A!kKK&r;Xk5n%Df+GC}zx3Pq
zBS2~^Lyvg7uAM9t3
z@0|C?9BbWUto7rbvu5F{s;gd&$J&!Is5of5g*56rMCeSe$Zx-?pikkYdOtuUO*10)
z(;37$;2T%uzT^C>*@B}v%cK01hrha=nf{;TLBht|>3@nt>}T5jFA|2!A68b4wW?9v
zy}=)9_B2A`)w@D6Y(<;E6-aY)lr)j;@uZ!;zY@t3mW14<_9k5{U@Q~=k#x7bNC9z4
z9(5SGQZO~}O)>T_a>I(Dhw_$e__XyE<4Iea;LnD;eBfP>Ri`j|Xs
z;rH78b8OBiX%7*+1*V*92uH_gg_J~jJ_D;wY=hAv-J!7qd+Xv~u3UR@Tp;7nKsKM1
zfVi+72hopEDybm|k9YDnZvkc|Q0gzb7pc!}|M!lL|8@-pjpX&6oQxf8l-=$A=`s9w
z%gFyy`>p|p^;ww=)^c}?dKQ!DLDya
zqaEj|X&QQo2^tuw2`Y#?EM6?Epg*|6JpJ1H@Vwr5n>zbZLfRxkNO@wEc^&c!hBcHV
zV2FtXN2hyd(<3=)8QJAO)Jw9<5RDE%VM2`ue??P)gprYzin!`_tcxkCYECb`TmC
zgm4rd^(**27N&Y*6HAS)(U-pyE;P%}*|B_X*v{wr-|YW?`_oFI%E}V5-~ZFq`lrQ-
zlY+|w)h)M?)(%Y{YFTJ8pJhdspsmSN{zeqcEc4U<*}-`4FG-Core0~WC+B+YW;teO
zxhAN#{X%bVWw1Tq4>LXx=dT0i35ih7C||0NrvmKLu1l{P05Ijx58Z~m8At<=ZX=iWDe+78D5
zMOO$rJ384~%UhcM*FpT_0IcKJZRQ0&oAt@eFF9=zQfGv7Sa;>)1=?qH6^T?v@xF?b
z1+@rkt=Hk=wl_8lcA!DQkkY$-!G~qyW2?OhHZ?hZTh7w?@O^*(1-}DMLEWUnqcE#B
zqjV$CD-02X%AoRgK7b>4o5*8j^~Rr$NWF$<<~duq;c0Dgl0I^gK{
zS@^oP`d5CO?4NTwEq5%!%>v|o&BRLDYvu8O0*tVOm{F^=RW3FuOH%LcZ$_U%^)uIZ
z``FC8^yAX~2_$yMLu0B)uqKKLG8MM+-02`EYY#%CuGj3nf#2wY8aesW!eVm|S7^3<
zNRC{VB8atQ6X*<6oWyEVVAlta-Uz*{=YMN0M2BS2hR8nRfs(n#3kB$xy7&4OdRD@$9WgI
zr-_9|s7=^40X=Qpmj8&K6~tO}{M8X@bMR0SX>;V{CX9nsiP`M%(g3dN@KOO@hg#-z
z_q!nk>jG;Gk+mz|MmeDZ=UifoaVfsz=N9wKY<-B>d|@ZBSo#n{BK^S_Z~<!O
zVPQpTMoNpJ=MP76l7z&KmrR<4D1^+?=ktomVg4C8p%CL|`4t?e_h9Ld4Pk*kqFYq%
zR4INv=Nyb~yS=J#vJprKTNeV0M$ML+?hMCK_6sFgwOkkVRl+!9a7vIzp@bFwAO~oF
zrdBVWTWBtS(SA*v
zFBElD@fJ%puE%W7g^>gj{8hfB6o+%Js+$aE+rH4gxu0chm(Q5&dJ|oAhVgnyNr_RL
zdXpZ`*+Qnux*3O)(uLW^EFFrAJ^U)!6+u+sz%{r6L)OCn6=qsh?$=?ttV8C&E28J`
z_VHX>cBn!a&&(~3kD}o&JRTZ-r`YiPeTl4eGJ9y6H~YCqh%5nyK;5GUgvTrbW5Fj}
zIQZWeNpML49k&+9a*9^*SN{AWc9%|=m4AwTID~tN*fY+^cuBx-G))B`F-WNoIkru=rSaF3TNv(^MHkFl@1bLSxY-f)dJH~vx3hi!It121
zl^osIVH>1&C)JPv(Rx&vA$00-f=4l;IgJodVCpIH?j{78<;E5H2dFRYEo=+5
z{nqp`a&-WILJM>k#jX1d6f#E7=YPO`e(b^Dz=xsTfQ6;>K9li5y{qoA>}Vl(gS?CF
zA;0j3IYM1adZDXHzBBmkTZ=5orJh`D@}FdGQ+-e>57}cK(>$?rPbo~c?VhZQ{%#0ni9i<(q9jzU)JcDpffgz5uP}L?D
za`fdn%%lP{n+XdE>kHFi1NKks^Ie|O
z#yH2WwZhJyExJYZKYz6w9$u?;4oh&3L8ChItZ$${%?2uuVH7KK`vf#c6GI>EN?_Hu|%kk>uZWsEDnr
zjg_swk&>~4i@D)Hox>#1EG*F)Ac)zMlr4}VI(Je5
z`lLT$5_S!)Ijk=`W1bLS`U0yKA